]> git.cworth.org Git - notmuch/commitdiff
Merge tag 'debian/0.31.2-3' into debian/buster-backports
authorDavid Bremner <david@tethera.net>
Sun, 13 Dec 2020 14:38:31 +0000 (10:38 -0400)
committerDavid Bremner <david@tethera.net>
Sun, 13 Dec 2020 14:38:31 +0000 (10:38 -0400)
notmuch release 0.31.2-3 for unstable (sid) [dgit]

[dgit distro=debian no-split --quilt=linear]

277 files changed:
.dir-locals.el
.gitignore
.travis.yml
AUTHORS
INSTALL
Makefile.global
Makefile.local
NEWS
bindings/Makefile.local
bindings/python-cffi/MANIFEST.in [new file with mode: 0644]
bindings/python-cffi/notmuch2/__init__.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_base.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_build.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_config.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_database.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_errors.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_message.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_query.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_tags.py [new file with mode: 0644]
bindings/python-cffi/notmuch2/_thread.py [new file with mode: 0644]
bindings/python-cffi/setup.py [new file with mode: 0644]
bindings/python-cffi/tests/conftest.py [new file with mode: 0644]
bindings/python-cffi/tests/test_base.py [new file with mode: 0644]
bindings/python-cffi/tests/test_config.py [new file with mode: 0644]
bindings/python-cffi/tests/test_database.py [new file with mode: 0644]
bindings/python-cffi/tests/test_message.py [new file with mode: 0644]
bindings/python-cffi/tests/test_tags.py [new file with mode: 0644]
bindings/python-cffi/tests/test_thread.py [new file with mode: 0644]
bindings/python-cffi/tox.ini [new file with mode: 0644]
bindings/python-cffi/version.txt [new file with mode: 0644]
bindings/python/notmuch/database.py
bindings/python/notmuch/message.py
bindings/python/notmuch/messages.py
bindings/python/notmuch/query.py
bindings/python/notmuch/version.py
bindings/ruby/message.c
command-line-arguments.c
command-line-arguments.h
compat/Makefile.local
compat/canonicalize_file_name.c
compat/check_asctime.c
compat/check_getpwuid.c
compat/compat.h
compat/function-attributes.h
compat/gen_zlib_pc.c
compat/getdelim.c
compat/getline.c
compat/have_canonicalize_file_name.c
compat/have_d_type.c
compat/have_getline.c
compat/have_strcasestr.c
compat/have_strsep.c
compat/have_timegm.c
compat/strcasestr.c
compat/strsep.c
compat/timegm.c
completion/Makefile.local
configure
contrib/notmuch-mutt/Makefile
contrib/notmuch-mutt/notmuch-mutt
debian/changelog
debian/compat [deleted file]
debian/control
debian/copyright
debian/elpa-notmuch.elpa
debian/elpa-notmuch.info [new file with mode: 0644]
debian/elpa-notmuch.lintian-overrides [new file with mode: 0644]
debian/libnotmuch-dev.manpages [new file with mode: 0644]
debian/libnotmuch5.symbols
debian/not-installed [new file with mode: 0644]
debian/notmuch-mutt.install
debian/notmuch-vim.dirs
debian/notmuch-vim.install
debian/notmuch.install
debian/notmuch.manpages
debian/python-notmuch.install [deleted file]
debian/rules
debian/upstream/metadata [new file with mode: 0644]
debugger.c
devel/STYLE
devel/author-scan.sh [new file with mode: 0644]
devel/emacs-keybindings.org
devel/gen-testdb.sh [deleted file]
devel/nmbug/nmbug
devel/nmbug/notmuch-report
devel/printmimestructure [deleted file]
devel/release-checks.sh
devel/try-emacs-mua
devel/uncrustify.cfg
doc/Makefile.local
doc/conf.py
doc/doxygen.cfg
doc/index.rst
doc/man1/notmuch-config.rst
doc/man1/notmuch.rst
doc/man7/notmuch-properties.rst
doc/man7/notmuch-search-terms.rst
doc/notmuch-emacs.rst
doc/python-bindings.rst [new file with mode: 0644]
emacs/Makefile.local
emacs/coolj.el
emacs/make-deps.el
emacs/notmuch-address.el
emacs/notmuch-company.el
emacs/notmuch-compat.el
emacs/notmuch-crypto.el
emacs/notmuch-draft.el
emacs/notmuch-emacs-mua.desktop
emacs/notmuch-hello.el
emacs/notmuch-jump.el
emacs/notmuch-lib.el
emacs/notmuch-maildir-fcc.el
emacs/notmuch-message.el
emacs/notmuch-mua.el
emacs/notmuch-parser.el
emacs/notmuch-pkg.el.tmpl
emacs/notmuch-print.el
emacs/notmuch-query.el
emacs/notmuch-show.el
emacs/notmuch-tag.el
emacs/notmuch-tree.el
emacs/notmuch-version.el.tmpl
emacs/notmuch-wash.el
emacs/notmuch.el
emacs/rstdoc.el
gmime-filter-reply.c
gmime-filter-reply.h
hooks.c
lib/Makefile.local
lib/add-message.cc
lib/built-with.c
lib/config.cc
lib/database-private.h
lib/database.cc
lib/directory.cc
lib/index.cc
lib/indexopts.c
lib/message-file.c
lib/message-id.c
lib/message-property.cc
lib/message.cc
lib/messages.c
lib/notmuch-private.h
lib/notmuch.h
lib/parse-time-vrp.cc
lib/parse-time-vrp.h
lib/query-fp.cc
lib/query-fp.h
lib/query.cc
lib/regexp-fields.cc
lib/regexp-fields.h
lib/sha1.c
lib/string-list.c
lib/thread-fp.cc
lib/thread-fp.h
lib/thread.cc
mime-node.c
notmuch-client.h
notmuch-config.c
notmuch-count.c
notmuch-dump.c
notmuch-insert.c
notmuch-new.c
notmuch-reindex.c
notmuch-reply.c
notmuch-restore.c
notmuch-search.c
notmuch-setup.c
notmuch-show.c
notmuch-time.c
notmuch.c
parse-time-string/Makefile.local
parse-time-string/parse-time-string.c
parse-time-string/parse-time-string.h
performance-test/M00-new.sh
performance-test/M01-dump-restore.sh
performance-test/M02-show.sh
performance-test/M03-search.sh
performance-test/M04-reply.sh
performance-test/M05-reindex.sh
performance-test/M06-insert.sh
performance-test/Makefile.local
performance-test/T00-new.sh
performance-test/T01-dump-restore.sh
performance-test/T02-tag.sh
performance-test/T03-reindex.sh
performance-test/T04-thread-subquery.sh
sprinter-json.c
sprinter-sexp.c
sprinter-text.c
sprinter.h
status.c
tag-util.c
tag-util.h
test/Makefile.local
test/README
test/T020-compact.sh
test/T050-new.sh
test/T060-count.sh
test/T070-insert.sh
test/T150-tagging.sh
test/T160-json.sh
test/T210-raw.sh
test/T310-emacs.sh
test/T351-pgpmime-mangling.sh [new file with mode: 0755]
test/T355-smime.sh
test/T356-protected-headers.sh
test/T357-index-decryption.sh
test/T360-symbol-hiding.sh
test/T391-python-cffi.sh [new file with mode: 0755]
test/T450-emacs-show.sh
test/T500-search-date.sh
test/T530-upgrade.sh [deleted file]
test/T560-lib-error.sh
test/T562-lib-database.sh [new file with mode: 0755]
test/T563-lib-directory.sh [new file with mode: 0755]
test/T564-lib-query.sh [new file with mode: 0755]
test/T566-lib-message.sh [new file with mode: 0755]
test/T568-lib-thread.sh [new file with mode: 0755]
test/T585-thread-subquery.sh
test/T590-libconfig.sh
test/T600-named-queries.sh
test/T610-message-property.sh
test/T650-regexp-query.sh
test/T670-duplicate-mid.sh
test/T700-reindex.sh
test/T710-message-id.sh
test/aggregate-results.sh
test/arg-test.c
test/corpora/crypto/encrypted-signed.eml [new file with mode: 0644]
test/corpora/mangling/mixed-up.eml [new file with mode: 0644]
test/corpora/pkcs7/smime-onepart-signed.eml [new file with mode: 0644]
test/corpora/protected-headers/protected-with-legacy-display.eml [new file with mode: 0644]
test/corpora/protected-headers/smime-enc+legacy-disp.eml [new file with mode: 0644]
test/corpora/protected-headers/smime-multipart-signed.eml [new file with mode: 0644]
test/corpora/protected-headers/smime-onepart-signed.eml [new file with mode: 0644]
test/corpora/protected-headers/smime-sign+enc+legacy-disp.eml [new file with mode: 0644]
test/corpora/protected-headers/smime-sign+enc.eml [new file with mode: 0644]
test/emacs-address-cleaning.el
test/emacs-attachment-warnings.el
test/emacs-show.expected-output/notmuch-show-indent-thread-content-off
test/ghost-report.cc
test/hex-xcode.c
test/make-db-version.cc
test/notmuch-test.h
test/parse-time.c
test/random-corpus.c
test/smime/README
test/smime/bob.p12 [new file with mode: 0644]
test/smime/ca.crt [new file with mode: 0644]
test/smtp-dummy.c
test/symbol-test.cc
test/test-databases/.gitignore [deleted file]
test/test-databases/Makefile [deleted file]
test/test-databases/Makefile.local [deleted file]
test/test-databases/database-v1.tar.xz.sha256 [deleted file]
test/test-lib.el
test/test-lib.sh
util/Makefile.local
util/crypto.c
util/crypto.h
util/error_util.h
util/gmime-extra.c
util/gmime-extra.h
util/hex-escape.c
util/hex-escape.h
util/repair.c [new file with mode: 0644]
util/repair.h [new file with mode: 0644]
util/string-util.c
util/string-util.h
util/unicode-util.c
util/util.c
util/xutil.c
util/zlib-extra.c
util/zlib-extra.h
version [deleted file]
version.txt [new file with mode: 0644]

index fc75ae615bd5a73b25eedabdfe0619286c0436b3..b3ddffe8ad15cf91ca68938e58264c60c239980c 100644 (file)
@@ -15,7 +15,7 @@
  (emacs-lisp-mode
   (indent-tabs-mode . t)
   (tab-width . 8))
- (shell-mode
+ (sh-mode
   (indent-tabs-mode . t)
   (tab-width . 8)
   (sh-basic-offset . 4)
index 468b660a70971fbac2269d914a70c6314b7bd453..3edd1768ed85aac3dc6bfa94a91107a3df75c1cb 100644 (file)
@@ -1,18 +1,20 @@
+*.[ao]
+*.stamp
+*cscope*
+*~
+.*.swp
+/.deps
 /.first-build-message
+/.stamps
 /Makefile.config
+/bindings/python-cffi/build/
+/lib/libnotmuch*.dylib
+/lib/libnotmuch.so*
+/notmuch
+/notmuch-shared
+/releases
 /sh.config
+/sphinx.config
 /version.stamp
 TAGS
 tags
-*cscope*
-/.deps
-/notmuch
-/notmuch-shared
-/lib/libnotmuch.so*
-/lib/libnotmuch*.dylib
-*.[ao]
-*~
-.*.swp
-/releases
-/.stamps
-*.stamp
index f9516bdee73f5c1f96b60fb9b7ac99322ee43053..9dcec1ffc63a90aa34a9b11c2ee209aac70da994 100644 (file)
@@ -1,23 +1,25 @@
 language: c
 
-dist: xenial
+dist: bionic
 
 addons:
   apt:
     sources:
     - sourceline: 'ppa:xapian-backports/ppa'
-    - sourceline: 'ppa:notmuch/notmuch'
     packages:
     - dtach
     - libxapian-dev
     - libgmime-3.0-dev
     - libtalloc-dev
     - python3-sphinx
+    - python3-cffi
+    - python3-pytest
+    - python3-setuptools
+    - libpython3-all-dev
     - gpgsm
 
 script:
   - ./configure
-  - make download-test-databases
   - make test
 
 notifications:
diff --git a/AUTHORS b/AUTHORS
index 5fe5006f58ce99916e94361346e1e19593605397..6e872084f8dfed91dc1d29fac93f9113452020e3 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,5 +1,6 @@
-Carl Worth <cworth@cworth.org> is the primary author of Notmuch.
-But there's really not much that he's done. There's been a lot of
+Carl Worth <cworth@cworth.org> was the original author of Notmuch.
+David Bremner has maintained Notmuch since release 0.6 (2011).  But
+there's really not much that they've done. There's been a lot of
 standing on shoulders here:
 
 William Morgan deserves credit for providing the primary inspiration
@@ -21,10 +22,108 @@ engine that does the really heavy lifting, as well as the various
 system libraries, compilers, and the kernel that make it all work
 (thanks GNU, thanks Linux). Thanks to everyone who has played a part!
 
+The following list of people have at least 15 lines of code in the
+Notmuch 0.31 release (calculated by devel/author-scan.sh).
+
+ David Bremner
+ Carl Worth
+ Jani Nikula
+ Austin Clements
+ Daniel Kahn Gillmor
+ Mark Walters
+ Floris Bruynooghe
+ David Edmondson
+ Tomi Ollila
+ Sebastian Spaeth
+ Ali Polatel
+ Michal Sojka
+ Justus Winter
+ Sebastien Binet
+ W. Trevor King
+ Jameson Graef Rollins
+ Felipe Contreras
+ Jonas Bernoulli
+ Pieter Praet
+ Peter Feigl
+ Dmitry Kurochkin
+ Peter Wang
+ Gregor Zattler
+ Daniel Schoepe
+ Keith Packard
+ Adam Wolfe Gordon
+ Stefano Zacchiroli
+ Vincent Breitmoser
+ laochailan
+ Ben Gamari
+ Aaron Ecay
+ l-m-h@web.de
+ Thomas Jost
+ Jesse Rosenthal
+ Dirk Hohndel
+ Blake Jones
+ Damien Cassou
+ Anton Khirnov
+ Matt Armstrong
+ Vladimir Panteleev
+ William Casarin
+ Örjan Ekeberg
+ Jan Janak
+ Patrick Totzke
+ Ruben Pollan
+ rhn
+ Ioan-Adrian Ratiu
+ Ethan Glasser-Camp
+ Chunyang Xu
+ Todd
+ Chris Wilson
+ Yuri Volchkov
+ Cédric Cabessa
+ Mark Anderson
+ Jed Brown
+ Maxime Coste
+ Ludovic LANGE
+ Sebastian Poeplau
+ Mikhail
+ Keith Amidon
+ Gaute Hope
+ martin f. krafft
+ Jeffrey C. Ollie
+ Jameson Rollins
+ Scott Henson
+ Bart Trojanowski
+ Vladimir Marek
+ Servilio Afre Puentes
+ Tomas Carnecky
+ Kevin McCarthy
+ Kevin J. McCarthy
+ Scott Robinson
+ Wael M. Nasreddine
+ Charles Celerier
+ Olly Betts
+ Istvan Marko
+ Florian Klink
+ Thibaut Horel
+ Joel Borggrén-Franck
+ Ingmar Vanhassel
+ Olivier Taïbi
+ Ian Main
+ Alexander Botero-Lowry
+ Luis Ressel
+ Sergei Shilovsky
+ Trevor Jim
+ Uli Scholler
+ Matthew Lear
+ Jinwoo Lee
+ Amadeusz Żołnowski
+
 Here is an incomplete list of other people that have made
 contributions to Notmuch (whether by code, bug reporting/fixes,
 ideas, inspiration, testing or feedback):
 
-Martin Krafft
-Keith Packard
-Jamey Sharp
+ Martin Krafft
+ Jamey Sharp
+
+The Notmuch project acknowledges the contributions of the following
+organizations via their employees
+
+ Google LLC
diff --git a/INSTALL b/INSTALL
index f1236e713e11085b626f79c5b22c89719cdd6b6d..40ea377af97d6b72325016044a8db37433671d3e 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -95,7 +95,7 @@ dependencies with a single simple command line. For example:
 
   For Fedora and similar:
 
-       sudo yum install xapian-core-devel gmime-devel libtalloc-devel zlib-devel python3-sphinx texinfo info
+       sudo dnf install xapian-core-devel gmime30-devel libtalloc-devel zlib-devel python3-sphinx texinfo info
 
 On other systems, a similar command can be used, but the details of
 the package names may be different.
index 0aee5876532bf84a5d4495a1046e884d1686602e..8477468dcdac8e703d49e0adb7f0eaac4c8b40ff 100644 (file)
@@ -1,3 +1,4 @@
+# -*- makefile-gmake -*-
 # Here's the (hopefully simple) versioning scheme.
 #
 # Releases of notmuch have a two-digit version (0.1, 0.2, etc.). We
@@ -16,7 +17,7 @@ else
 DATE:=$(shell date +%F)
 endif
 
-VERSION:=$(shell cat ${srcdir}/version)
+VERSION:=$(shell cat ${srcdir}/version.txt)
 ELPA_VERSION:=$(subst ~,_,$(VERSION))
 ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),)
 ifeq ($(IS_GIT),yes)
@@ -39,6 +40,7 @@ DEB_TAG=debian/$(UPSTREAM_TAG)-1
 
 RELEASE_HOST=notmuchmail.org
 RELEASE_DIR=/srv/notmuchmail.org/www/releases
+DOC_DIR=/srv/notmuchmail.org/www/doc/latest
 RELEASE_URL=https://notmuchmail.org/releases
 TAR_FILE=$(PACKAGE)-$(VERSION).tar.xz
 ELPA_FILE:=$(PACKAGE)-emacs-$(ELPA_VERSION).tar
@@ -49,8 +51,7 @@ DETACHED_SIG_FILE=$(TAR_FILE).asc
 PV_FILE=bindings/python/notmuch/version.py
 
 # Smash together user's values with our extra values
-STD_CFLAGS := -std=gnu99
-FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(STD_CFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS)
+FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS)
 FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS)
 FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lnotmuch_util -Llib -lnotmuch
 ifeq ($(LIBDIR_IN_LDCONFIG),0)
index 3c6dacbccc4b3af1e43d89fcf8e69dd23997aed0..fb12629420224d41ea981bbecd1007616f28727c 100644 (file)
@@ -1,7 +1,7 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 .PHONY: all
-all: notmuch notmuch-shared build-man build-info ruby-bindings
+all: notmuch notmuch-shared build-man build-info ruby-bindings python-cffi-bindings
 ifeq ($(MAKECMDGOALS),)
 ifeq ($(shell cat .first-build-message 2>/dev/null),)
        @NOTMUCH_FIRST_BUILD=1 $(MAKE) --no-print-directory all
@@ -19,7 +19,7 @@ endif
 
 # Depend (also) on the file 'version'. In case of ifeq ($(IS_GIT),yes)
 # this file may already have been updated.
-version.stamp: $(srcdir)/version
+version.stamp: $(srcdir)/version.txt
        echo $(VERSION) > $@
 
 $(TAR_FILE):
@@ -30,12 +30,12 @@ $(TAR_FILE):
           echo "Warning: No signed tag for $(VERSION)"; \
        fi ; \
        git archive --format=tar --prefix=$(PACKAGE)-$(VERSION)/ $$ref > $(TAR_FILE).tmp
-       echo $(VERSION) > version.tmp
+       echo $(VERSION) > version.txt.tmp
        ct=`git --no-pager log -1 --pretty=format:%ct $$ref` ; \
        tar --owner root --group root --append -f $(TAR_FILE).tmp \
                --transform s_^_$(PACKAGE)-$(VERSION)/_  \
-               --transform 's_.tmp$$__' --mtime=@$$ct version.tmp
-       rm version.tmp
+               --transform 's_.tmp$$__' --mtime=@$$ct version.txt.tmp
+       rm version.txt.tmp
        xz -C sha256 -9 < $(TAR_FILE).tmp > $(TAR_FILE)
        @echo "Source is ready for release in $(TAR_FILE)"
 
@@ -54,6 +54,7 @@ update-versions:
        sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \
            -e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \
            ${PV_FILE}
+       cp version.txt bindings/python-cffi
 
 # We invoke make recursively only to force ordering of our phony
 # targets in the case of parallel invocation of make (-j).
@@ -66,6 +67,7 @@ update-versions:
 release: verify-source-tree-and-version
        $(MAKE) VERSION=$(VERSION) verify-newer
        $(MAKE) VERSION=$(VERSION) clean
+       $(MAKE) VERSION=$(VERSION) sphinx-html
        $(MAKE) VERSION=$(VERSION) test
        git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG)
        $(MAKE) VERSION=$(VERSION) $(SHA256_FILE) $(DETACHED_SIG_FILE)
@@ -79,6 +81,7 @@ ifeq ($(REALLY_UPLOAD),yes)
        git push origin $(VERSION) $(DEB_TAG) release pristine-tar
        cd releases && scp $(TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) $(RELEASE_HOST):$(RELEASE_DIR)
        ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(TAR_FILE)"
+       rsync --verbose --delete --recursive doc/_build/html/ $(RELEASE_HOST):$(DOC_DIR)
 endif
        @echo "Please send a release announcement using $(PACKAGE)-$(VERSION).announce as a template."
 
@@ -88,7 +91,7 @@ pre-release:
        $(MAKE) VERSION=$(VERSION) test
        git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG)
        git tag -s -m "$(PACKAGE) Debian $(VERSION)-1 upload (same as $(VERSION))" $(DEB_TAG)
-       $(MAKE) VERSION=$(VERSION) $(TAR_FILE)
+       $(MAKE) VERSION=$(VERSION) $(SHA256_FILE) $(DETACHED_SIG_FILE)
        ln -sf $(TAR_FILE) $(DEB_TAR_FILE)
        pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG)
        mkdir -p releases
@@ -97,14 +100,16 @@ pre-release:
 .PHONY: debian-snapshot
 debian-snapshot:
        make VERSION=$(VERSION) clean
-       TMPFILE=$$(mktemp /tmp/notmuch.XXXXXX);         \
-         cp debian/changelog $${TMPFILE};              \
-         EDITOR=/bin/true dch -b -v $(VERSION)+1       \
-           -D UNRELEASED 'test build, not for upload'; \
-         echo '3.0 (native)' > debian/source/format;   \
-         debuild -us -uc;                              \
-         mv -f $${TMPFILE} debian/changelog;           \
-         echo '3.0 (quilt)' > debian/source/format
+       RETVAL=0 &&                                             \
+         TMPFILE=$$(mktemp /tmp/notmuch.XXXXXX) &&             \
+         cp debian/changelog $${TMPFILE} &&                    \
+         (EDITOR=/bin/true dch -b -v $(VERSION)+1              \
+           -D UNRELEASED 'test build, not for upload' &&       \
+         echo '3.0 (native)' > debian/source/format &&         \
+         debuild -us -uc); RETVAL=$$?                          \
+         mv -f $${TMPFILE} debian/changelog;                   \
+         echo '3.0 (quilt)' > debian/source/format;            \
+         exit $$RETVAL
 
 .PHONY: release-message
 release-message:
@@ -290,7 +295,7 @@ 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
+DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config sh.config sphinx.config
 
 CPPCHECK_STAMPS := $(SRCS:%=.stamps/cppcheck/%)
 .PHONY: cppcheck
diff --git a/NEWS b/NEWS
index 66bb69f1558852662c723a5994785a2e5305bf5d..677c507dc6d90a84d5c7392dbd5b8537224a8dd7 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,154 @@
+Notmuch 0.31.2 (2020-11-08)
+===========================
+
+Build
+-----
+
+Catch one more occurence of "version" in the build system, which
+caused the file to be regenerated in the release tarball.
+
+Notmuch 0.31.1 (2020-11-08)
+===========================
+
+Library
+-------
+
+Fix a memory initialization bug in notmuch_database_get_config_list.
+
+Build
+-----
+
+Rename file 'version' to 'version.txt'. The old file name conflicted
+with a C++ header for some compilers.
+
+Replace use of coreutils `realpath` in configure.
+
+Notmuch 0.31 (2020-09-05)
+=========================
+
+Emacs
+-----
+
+Notmuch now supports Emacs 27.1. You may need to set
+`mml-secure-openpgp-sign-with-sender` and/or
+`mml-secure-smime-sign-with-sender` to continue signing messages.
+
+The minimum supported major version of GNU Emacs is now 25.1.
+
+Add support for moving between threads after notmuch-tree-from-search-thread.
+
+New `notmuch-unthreaded` mode (added in Notmuch 0.30)
+
+  Unthreaded view is a mode where each matching message is shown on a
+  separate line.
+
+  The main key entries to unthreaded view are
+
+  'u' enter a query to view in unthreaded mode (works in hello,
+      search, show and tree mode)
+
+  'U' view the current query in unthreaded mode (works from search,
+      show and tree)
+
+  Saved searches can also specify that they should open in unthreaded
+  view.
+
+  Currently it is not possible to specify the sort order: it will
+  always be newest first.
+
+Notmuch-Mutt
+------------
+
+The shell pipeline executed by notmuch-mutt, which symlinked matched
+files to a maildir for mutt to access is replaced with internal perl
+processing. This search operation is now more portable, and somewhat
+faster.
+
+Library
+-------
+
+Improve exception handling in the library. This should
+largely eliminate terminations inside the library due to uncaught
+exceptions or internal errors.  No doubt there are a few uncovered
+code paths still; please report them as bugs.
+
+Add `notmuch_message_get_flag_st` and
+`notmuch_message_has_maildir_flag_st`, and deprecate the existing
+non-status providing versions.
+
+Move memory de-allocation from `notmuch_database_close` to
+`notmuch_database_destroy`.
+
+Handle relative filenames in `notmuch_database_index_file`, as
+promised in the documentation.
+
+Python Bindings
+---------------
+
+Documentation for the python bindings is merged into the main
+sphinx-doc documentation tree. The merged documentation can be built
+with e.g. `make sphinx-html`
+
+Dependencies
+------------
+
+We now support building notmuch against Xapian 1.5 (the current
+development version).
+
+Test Suite
+----------
+
+Test suite fixes for compatibility with Emacs 27.1.
+
+Build System
+------------
+
+Man pages are now compressed reproducibly.
+
+Notmuch 0.30 (2020-07-10)
+=========================
+
+S/MIME
+------
+
+Handle S/MIME (PKCS#7) messages -- one-part signed messages, encrypted
+messages, and multilayer messages. Treat them symmetrically to
+OpenPGP messages. This includes handling protected headers
+gracefully.
+
+If you're using Notmuch with S/MIME, you currently need to configure
+gpgsm appropriately.
+
+Mixed-up MIME Repair
+--------------------
+
+Detect and automatically repair a common form of message mangling
+created by Microsoft Exchange (see index.repaired=mixedup in
+notmuch-properties(7)).
+
+Protected Headers
+-----------------
+
+Avoid indexing the legacy-display part of an encrypted message that
+has protected headers (see
+index.repaired=skip-protected-headers-legacy-display in
+notmuch-properties(7)).
+
+Python
+------
+
+Drop support for python2, focus on python3.
+
+Introduce new CFFI-based python bindings in the python module named
+"notmuch2".  Officially deprecate (but still support) the older
+"notmuch" module.
+
+Dependencies
+------------
+
+Support for Xapian 1.2 is removed. The minimum supported version of
+Xapian is now 1.4.0.
+
 Notmuch 0.29.3 (2019-11-27)
 ===========================
 
@@ -72,7 +223,7 @@ information about cryptographic protections for the Subject header.
 Emacs
 -----
 
-Optionally check for missing attachements in outgoing messages (see
+Optionally check for missing attachments in outgoing messages (see
 function `notmuch-mua-attachment-check`).
 
 Bind `B` to browse URLs in current message.
index 18f958359bf0c58e23b7dee7b3c9586dcc9c29ec..bc960bbceabe592bb75522c25c20bbc2c9c70e10 100644 (file)
@@ -1,4 +1,4 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := bindings
 
@@ -13,6 +13,13 @@ ifeq ($(HAVE_RUBY_DEV),1)
        $(MAKE) -C $(dir)/ruby
 endif
 
+python-cffi-bindings: lib/$(LINKER_NAME)
+ifeq ($(HAVE_PYTHON3_CFFI),1)
+       cd $(dir)/python-cffi && \
+               ${PYTHON} setup.py build --build-lib build/stage && \
+               mkdir -p build/stage/tests && cp tests/*.py build/stage/tests
+endif
+
 CLEAN += $(patsubst %,$(dir)/ruby/%, \
        .RUBYARCHDIR.time \
        Makefile database.o directory.o filenames.o\
@@ -20,3 +27,5 @@ CLEAN += $(patsubst %,$(dir)/ruby/%, \
        status.o tags.o thread.o threads.o)
 
 CLEAN += bindings/ruby/.vendorarchdir.time
+
+CLEAN += bindings/python-cffi/build
diff --git a/bindings/python-cffi/MANIFEST.in b/bindings/python-cffi/MANIFEST.in
new file mode 100644 (file)
index 0000000..9ef81f2
--- /dev/null
@@ -0,0 +1,2 @@
+include MANIFEST.in
+include tox.ini
diff --git a/bindings/python-cffi/notmuch2/__init__.py b/bindings/python-cffi/notmuch2/__init__.py
new file mode 100644 (file)
index 0000000..f281edc
--- /dev/null
@@ -0,0 +1,62 @@
+"""Pythonic API to the notmuch database.
+
+Creating Objects
+================
+
+Only the :class:`Database` object is meant to be created by the user.
+All other objects should be created from this initial object.  Users
+should consider their signatures implementation details.
+
+Errors
+======
+
+All errors occurring due to errors from the underlying notmuch database
+are subclasses of the :exc:`NotmuchError`.  Due to memory management
+it is possible to try and use an object after it has been freed.  In
+this case a :exc:`ObjectDestroyedError` will be raised.
+
+Memory Management
+=================
+
+Libnotmuch uses a hierarchical memory allocator, this means all
+objects have a strict parent-child relationship and when the parent is
+freed all the children are freed as well.  This has some implications
+for these Python bindings as parent objects need to be kept alive.
+This is normally schielded entirely from the user however and the
+Python objects automatically make sure the right references are kept
+alive.  It is however the reason the :class:`BaseObject` exists as it
+defines the API all Python objects need to implement to work
+correctly.
+
+Collections and Containers
+==========================
+
+Libnotmuch exposes nearly all collections of things as iterators only.
+In these python bindings they have sometimes been exposed as
+:class:`collections.abc.Container` instances or subclasses of this
+like :class:`collections.abc.Set` or :class:`collections.abc.Mapping`
+etc.  This gives a more natural API to work with, e.g. being able to
+treat tags as sets.  However it does mean that the
+:meth:`__contains__`, :meth:`__len__` and frieds methods on these are
+usually more and essentially O(n) rather than O(1) as you might
+usually expect from Python containers.
+"""
+
+from notmuch2 import _capi
+from notmuch2._base import *
+from notmuch2._database import *
+from notmuch2._errors import *
+from notmuch2._message import *
+from notmuch2._tags import *
+from notmuch2._thread import *
+
+
+NOTMUCH_TAG_MAX = _capi.lib.NOTMUCH_TAG_MAX
+del _capi
+
+
+# Re-home all the objects to the package.  This leaves __qualname__ intact.
+for x in locals().copy().values():
+    if hasattr(x, '__module__'):
+        x.__module__ = __name__
+del x
diff --git a/bindings/python-cffi/notmuch2/_base.py b/bindings/python-cffi/notmuch2/_base.py
new file mode 100644 (file)
index 0000000..1cf03c8
--- /dev/null
@@ -0,0 +1,238 @@
+import abc
+import collections.abc
+
+from notmuch2 import _capi as capi
+from notmuch2 import _errors as errors
+
+
+__all__ = ['NotmuchObject', 'BinString']
+
+
+class NotmuchObject(metaclass=abc.ABCMeta):
+    """Base notmuch object syntax.
+
+    This base class exists to define the memory management handling
+    required to use the notmuch library.  It is meant as an interface
+    definition rather than a base class, though you can use it as a
+    base class to ensure you don't forget part of the interface.  It
+    only concerns you if you are implementing this package itself
+    rather then using it.
+
+    libnotmuch uses a hierarchical memory allocator, where freeing the
+    memory of a parent object also frees the memory of all child
+    objects.  To make this work seamlessly in Python this package
+    keeps references to parent objects which makes them stay alive
+    correctly under normal circumstances.  When an object finally gets
+    deleted the :meth:`__del__` method will be called to free the
+    memory.
+
+    However during some peculiar situations, e.g. interpreter
+    shutdown, it is possible for the :meth:`__del__` method to have
+    been called, whele there are still references to an object.  This
+    could result in child objects asking their memory to be freed
+    after the parent has already freed the memory, making things
+    rather unhappy as double frees are not taken lightly in C.  To
+    handle this case all objects need to follow the same protocol to
+    destroy themselves, see :meth:`destroy`.
+
+    Once an object has been destroyed trying to use it should raise
+    the :exc:`ObjectDestroyedError` exception.  For this see also the
+    convenience :class:`MemoryPointer` descriptor in this module which
+    can be used as a pointer to libnotmuch memory.
+    """
+
+    @abc.abstractmethod
+    def __init__(self, parent, *args, **kwargs):
+        """Create a new object.
+
+        Other then for the toplevel :class:`Database` object
+        constructors are only ever called by internal code and not by
+        the user.  Per convention their signature always takes the
+        parent object as first argument.  Feel free to make the rest
+        of the signature match the object's requirement.  The object
+        needs to keep a reference to the parent, so it can check the
+        parent is still alive.
+        """
+
+    @property
+    @abc.abstractmethod
+    def alive(self):
+        """Whether the object is still alive.
+
+        This indicates whether the object is still alive.  The first
+        thing this needs to check is whether the parent object is
+        still alive, if it is not then this object can not be alive
+        either.  If the parent is alive then it depends on whether the
+        memory for this object has been freed yet or not.
+        """
+
+    def __del__(self):
+        self._destroy()
+
+    @abc.abstractmethod
+    def _destroy(self):
+        """Destroy the object, freeing all memory.
+
+        This method needs to destroy the object on the
+        libnotmuch-level.  It must ensure it's not been destroyed by
+        it's parent object yet before doing so.  It also must be
+        idempotent.
+        """
+
+
+class MemoryPointer:
+    """Data Descriptor to handle accessing libnotmuch pointers.
+
+    Most :class:`NotmuchObject` instances will have one or more CFFI
+    pointers to C-objects.  Once an object is destroyed this pointer
+    should no longer be used and a :exc:`ObjectDestroyedError`
+    exception should be raised on trying to access it.  This
+    descriptor simplifies implementing this, allowing the creation of
+    an attribute which can be assigned to, but when accessed when the
+    stored value is *None* it will raise the
+    :exc:`ObjectDestroyedError` exception::
+
+       class SomeOjb:
+           _ptr = MemoryPointer()
+
+           def __init__(self, ptr):
+               self._ptr = ptr
+
+           def destroy(self):
+               somehow_free(self._ptr)
+               self._ptr = None
+
+           def do_something(self):
+               return some_libnotmuch_call(self._ptr)
+    """
+
+    def __get__(self, instance, owner):
+        try:
+            val = getattr(instance, self.attr_name, None)
+        except AttributeError:
+            # We're not on 3.6+ and self.attr_name does not exist
+            self.__set_name__(instance, 'dummy')
+            val = getattr(instance, self.attr_name, None)
+        if val is None:
+            raise errors.ObjectDestroyedError()
+        return val
+
+    def __set__(self, instance, value):
+        try:
+            setattr(instance, self.attr_name, value)
+        except AttributeError:
+            # We're not on 3.6+ and self.attr_name does not exist
+            self.__set_name__(instance, 'dummy')
+            setattr(instance, self.attr_name, value)
+
+    def __set_name__(self, instance, name):
+        self.attr_name = '_memptr_{}_{:x}'.format(name, id(instance))
+
+
+class BinString(str):
+    """A str subclass with binary data.
+
+    Most data in libnotmuch should be valid ASCII or valid UTF-8.
+    However since it is a C library these are represented as
+    bytestrings instead which means on an API level we can not
+    guarantee that decoding this to UTF-8 will both succeed and be
+    lossless.  This string type converts bytes to unicode in a lossy
+    way, but also makes the raw bytes available.
+
+    This object is a normal unicode string for most intents and
+    purposes, but you can get the original bytestring back by calling
+    ``bytes()`` on it.
+    """
+
+    def __new__(cls, data, encoding='utf-8', errors='ignore'):
+        if not isinstance(data, bytes):
+            data = bytes(data, encoding=encoding)
+        strdata = str(data, encoding=encoding, errors=errors)
+        inst = super().__new__(cls, strdata)
+        inst._bindata = data
+        return inst
+
+    @classmethod
+    def from_cffi(cls, cdata):
+        """Create a new string from a CFFI cdata pointer."""
+        return cls(capi.ffi.string(cdata))
+
+    def __bytes__(self):
+        return self._bindata
+
+
+class NotmuchIter(NotmuchObject, collections.abc.Iterator):
+    """An iterator for libnotmuch iterators.
+
+    It is tempting to use a generator function instead, but this would
+    not correctly respect the :class:`NotmuchObject` memory handling
+    protocol and in some unsuspecting cornercases cause memory
+    trouble.  You probably want to sublcass this in order to wrap the
+    value returned by :meth:`__next__`.
+
+    :param parent: The parent object.
+    :type parent: NotmuchObject
+    :param iter_p: The CFFI pointer to the C iterator.
+    :type iter_p: cffi.cdata
+    :param fn_destory: The CFFI notmuch_*_destroy function.
+    :param fn_valid: The CFFI notmuch_*_valid function.
+    :param fn_get: The CFFI notmuch_*_get function.
+    :param fn_next: The CFFI notmuch_*_move_to_next function.
+    """
+    _iter_p = MemoryPointer()
+
+    def __init__(self, parent, iter_p,
+                 *, fn_destroy, fn_valid, fn_get, fn_next):
+        self._parent = parent
+        self._iter_p = iter_p
+        self._fn_destroy = fn_destroy
+        self._fn_valid = fn_valid
+        self._fn_get = fn_get
+        self._fn_next = fn_next
+
+    def __del__(self):
+        self._destroy()
+
+    @property
+    def alive(self):
+        if not self._parent.alive:
+            return False
+        try:
+            self._iter_p
+        except errors.ObjectDestroyedError:
+            return False
+        else:
+            return True
+
+    def _destroy(self):
+        if self.alive:
+            try:
+                self._fn_destroy(self._iter_p)
+            except errors.ObjectDestroyedError:
+                pass
+        self._iter_p = None
+
+    def __iter__(self):
+        """Return the iterator itself.
+
+        Note that as this is an iterator and not a container this will
+        not return a new iterator.  Thus any elements already consumed
+        will not be yielded by the :meth:`__next__` method anymore.
+        """
+        return self
+
+    def __next__(self):
+        if not self._fn_valid(self._iter_p):
+            self._destroy()
+            raise StopIteration()
+        obj_p = self._fn_get(self._iter_p)
+        self._fn_next(self._iter_p)
+        return obj_p
+
+    def __repr__(self):
+        try:
+            self._iter_p
+        except errors.ObjectDestroyedError:
+            return '<NotmuchIter (exhausted)>'
+        else:
+            return '<NotmuchIter>'
diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py
new file mode 100644 (file)
index 0000000..f269f2a
--- /dev/null
@@ -0,0 +1,339 @@
+import cffi
+
+
+ffibuilder = cffi.FFI()
+ffibuilder.set_source(
+    'notmuch2._capi',
+    r"""
+    #include <stdlib.h>
+    #include <time.h>
+    #include <notmuch.h>
+
+    #if LIBNOTMUCH_MAJOR_VERSION < 5
+        #error libnotmuch version not supported by notmuch2 python bindings
+    #endif
+    #if LIBNOTMUCH_MINOR_VERSION < 1
+        #ERROR libnotmuch  version < 5.1 not supported
+    #endif
+    """,
+    include_dirs=['../../lib'],
+    library_dirs=['../../lib'],
+    libraries=['notmuch'],
+)
+ffibuilder.cdef(
+    r"""
+    void free(void *ptr);
+    typedef int... time_t;
+
+    #define LIBNOTMUCH_MAJOR_VERSION ...
+    #define LIBNOTMUCH_MINOR_VERSION ...
+    #define LIBNOTMUCH_MICRO_VERSION ...
+
+    #define NOTMUCH_TAG_MAX ...
+
+    typedef enum _notmuch_status {
+        NOTMUCH_STATUS_SUCCESS = 0,
+        NOTMUCH_STATUS_OUT_OF_MEMORY,
+        NOTMUCH_STATUS_READ_ONLY_DATABASE,
+        NOTMUCH_STATUS_XAPIAN_EXCEPTION,
+        NOTMUCH_STATUS_FILE_ERROR,
+        NOTMUCH_STATUS_FILE_NOT_EMAIL,
+        NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID,
+        NOTMUCH_STATUS_NULL_POINTER,
+        NOTMUCH_STATUS_TAG_TOO_LONG,
+        NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
+        NOTMUCH_STATUS_UNBALANCED_ATOMIC,
+        NOTMUCH_STATUS_UNSUPPORTED_OPERATION,
+        NOTMUCH_STATUS_UPGRADE_REQUIRED,
+        NOTMUCH_STATUS_PATH_ERROR,
+        NOTMUCH_STATUS_ILLEGAL_ARGUMENT,
+        NOTMUCH_STATUS_LAST_STATUS
+    } notmuch_status_t;
+    typedef enum {
+        NOTMUCH_DATABASE_MODE_READ_ONLY = 0,
+        NOTMUCH_DATABASE_MODE_READ_WRITE
+    } notmuch_database_mode_t;
+    typedef int notmuch_bool_t;
+    typedef enum _notmuch_message_flag {
+        NOTMUCH_MESSAGE_FLAG_MATCH,
+        NOTMUCH_MESSAGE_FLAG_EXCLUDED,
+        NOTMUCH_MESSAGE_FLAG_GHOST,
+    } notmuch_message_flag_t;
+    typedef enum {
+        NOTMUCH_SORT_OLDEST_FIRST,
+        NOTMUCH_SORT_NEWEST_FIRST,
+        NOTMUCH_SORT_MESSAGE_ID,
+        NOTMUCH_SORT_UNSORTED
+    } notmuch_sort_t;
+    typedef enum {
+        NOTMUCH_EXCLUDE_FLAG,
+        NOTMUCH_EXCLUDE_TRUE,
+        NOTMUCH_EXCLUDE_FALSE,
+        NOTMUCH_EXCLUDE_ALL
+    } notmuch_exclude_t;
+    typedef enum {
+        NOTMUCH_DECRYPT_FALSE,
+        NOTMUCH_DECRYPT_TRUE,
+        NOTMUCH_DECRYPT_AUTO,
+        NOTMUCH_DECRYPT_NOSTASH,
+    } notmuch_decryption_policy_t;
+
+    // These are fully opaque types for us, we only ever use pointers.
+    typedef struct _notmuch_database notmuch_database_t;
+    typedef struct _notmuch_query notmuch_query_t;
+    typedef struct _notmuch_threads notmuch_threads_t;
+    typedef struct _notmuch_thread notmuch_thread_t;
+    typedef struct _notmuch_messages notmuch_messages_t;
+    typedef struct _notmuch_message notmuch_message_t;
+    typedef struct _notmuch_tags notmuch_tags_t;
+    typedef struct _notmuch_string_map_iterator notmuch_message_properties_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;
+
+    const char *
+    notmuch_status_to_string (notmuch_status_t status);
+
+    notmuch_status_t
+    notmuch_database_create_verbose (const char *path,
+                                     notmuch_database_t **database,
+                                     char **error_message);
+    notmuch_status_t
+    notmuch_database_create (const char *path, notmuch_database_t **database);
+    notmuch_status_t
+    notmuch_database_open_verbose (const char *path,
+                                   notmuch_database_mode_t mode,
+                                   notmuch_database_t **database,
+                                   char **error_message);
+    notmuch_status_t
+    notmuch_database_open (const char *path,
+                           notmuch_database_mode_t mode,
+                           notmuch_database_t **database);
+    notmuch_status_t
+    notmuch_database_close (notmuch_database_t *database);
+    notmuch_status_t
+    notmuch_database_destroy (notmuch_database_t *database);
+    const char *
+    notmuch_database_get_path (notmuch_database_t *database);
+    unsigned int
+    notmuch_database_get_version (notmuch_database_t *database);
+    notmuch_bool_t
+    notmuch_database_needs_upgrade (notmuch_database_t *database);
+    notmuch_status_t
+    notmuch_database_begin_atomic (notmuch_database_t *notmuch);
+    notmuch_status_t
+    notmuch_database_end_atomic (notmuch_database_t *notmuch);
+    unsigned long
+    notmuch_database_get_revision (notmuch_database_t *notmuch,
+                                   const char **uuid);
+    notmuch_status_t
+    notmuch_database_index_file (notmuch_database_t *database,
+                                 const char *filename,
+                                 notmuch_indexopts_t *indexopts,
+                                 notmuch_message_t **message);
+    notmuch_status_t
+    notmuch_database_remove_message (notmuch_database_t *database,
+                                     const char *filename);
+    notmuch_status_t
+    notmuch_database_find_message (notmuch_database_t *database,
+                                   const char *message_id,
+                                   notmuch_message_t **message);
+    notmuch_status_t
+    notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
+                                               const char *filename,
+                                               notmuch_message_t **message);
+    notmuch_tags_t *
+    notmuch_database_get_all_tags (notmuch_database_t *db);
+
+    notmuch_query_t *
+    notmuch_query_create (notmuch_database_t *database,
+                          const char *query_string);
+    const char *
+    notmuch_query_get_query_string (const notmuch_query_t *query);
+    notmuch_database_t *
+    notmuch_query_get_database (const notmuch_query_t *query);
+    void
+    notmuch_query_set_omit_excluded (notmuch_query_t *query,
+                                     notmuch_exclude_t omit_excluded);
+    void
+    notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort);
+    notmuch_sort_t
+    notmuch_query_get_sort (const notmuch_query_t *query);
+    notmuch_status_t
+    notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag);
+    notmuch_status_t
+    notmuch_query_search_threads (notmuch_query_t *query,
+                                  notmuch_threads_t **out);
+    notmuch_status_t
+    notmuch_query_search_messages (notmuch_query_t *query,
+                                   notmuch_messages_t **out);
+    notmuch_status_t
+    notmuch_query_count_messages (notmuch_query_t *query, unsigned int *count);
+    notmuch_status_t
+    notmuch_query_count_threads (notmuch_query_t *query, unsigned *count);
+    void
+    notmuch_query_destroy (notmuch_query_t *query);
+
+    notmuch_bool_t
+    notmuch_threads_valid (notmuch_threads_t *threads);
+    notmuch_thread_t *
+    notmuch_threads_get (notmuch_threads_t *threads);
+    void
+    notmuch_threads_move_to_next (notmuch_threads_t *threads);
+    void
+    notmuch_threads_destroy (notmuch_threads_t *threads);
+
+    const char *
+    notmuch_thread_get_thread_id (notmuch_thread_t *thread);
+    notmuch_messages_t *
+    notmuch_message_get_replies (notmuch_message_t *message);
+    int
+    notmuch_thread_get_total_messages (notmuch_thread_t *thread);
+    notmuch_messages_t *
+    notmuch_thread_get_toplevel_messages (notmuch_thread_t *thread);
+    notmuch_messages_t *
+    notmuch_thread_get_messages (notmuch_thread_t *thread);
+    int
+    notmuch_thread_get_matched_messages (notmuch_thread_t *thread);
+    const char *
+    notmuch_thread_get_authors (notmuch_thread_t *thread);
+    const char *
+    notmuch_thread_get_subject (notmuch_thread_t *thread);
+    time_t
+    notmuch_thread_get_oldest_date (notmuch_thread_t *thread);
+    time_t
+    notmuch_thread_get_newest_date (notmuch_thread_t *thread);
+    notmuch_tags_t *
+    notmuch_thread_get_tags (notmuch_thread_t *thread);
+    void
+    notmuch_thread_destroy (notmuch_thread_t *thread);
+
+    notmuch_bool_t
+    notmuch_messages_valid (notmuch_messages_t *messages);
+    notmuch_message_t *
+    notmuch_messages_get (notmuch_messages_t *messages);
+    void
+    notmuch_messages_move_to_next (notmuch_messages_t *messages);
+    void
+    notmuch_messages_destroy (notmuch_messages_t *messages);
+    notmuch_tags_t *
+    notmuch_messages_collect_tags (notmuch_messages_t *messages);
+
+    const char *
+    notmuch_message_get_message_id (notmuch_message_t *message);
+    const char *
+    notmuch_message_get_thread_id (notmuch_message_t *message);
+    const char *
+    notmuch_message_get_filename (notmuch_message_t *message);
+    notmuch_filenames_t *
+    notmuch_message_get_filenames (notmuch_message_t *message);
+    notmuch_bool_t
+    notmuch_message_get_flag (notmuch_message_t *message,
+                              notmuch_message_flag_t flag);
+    void
+    notmuch_message_set_flag (notmuch_message_t *message,
+                              notmuch_message_flag_t flag,
+                              notmuch_bool_t value);
+    time_t
+    notmuch_message_get_date  (notmuch_message_t *message);
+    const char *
+    notmuch_message_get_header (notmuch_message_t *message,
+                                const char *header);
+    notmuch_tags_t *
+    notmuch_message_get_tags (notmuch_message_t *message);
+    notmuch_status_t
+    notmuch_message_add_tag (notmuch_message_t *message, const char *tag);
+    notmuch_status_t
+    notmuch_message_remove_tag (notmuch_message_t *message, const char *tag);
+    notmuch_status_t
+    notmuch_message_remove_all_tags (notmuch_message_t *message);
+    notmuch_status_t
+    notmuch_message_maildir_flags_to_tags (notmuch_message_t *message);
+    notmuch_status_t
+    notmuch_message_tags_to_maildir_flags (notmuch_message_t *message);
+    notmuch_status_t
+    notmuch_message_freeze (notmuch_message_t *message);
+    notmuch_status_t
+    notmuch_message_thaw (notmuch_message_t *message);
+    notmuch_status_t
+    notmuch_message_get_property (notmuch_message_t *message,
+                                  const char *key, const char **value);
+    notmuch_status_t
+    notmuch_message_add_property (notmuch_message_t *message,
+                                  const char *key, const char *value);
+    notmuch_status_t
+    notmuch_message_remove_property (notmuch_message_t *message,
+                                     const char *key, const char *value);
+    notmuch_status_t
+    notmuch_message_remove_all_properties (notmuch_message_t *message,
+                                           const char *key);
+    notmuch_message_properties_t *
+    notmuch_message_get_properties (notmuch_message_t *message,
+                                    const char *key, notmuch_bool_t exact);
+    notmuch_bool_t
+    notmuch_message_properties_valid (notmuch_message_properties_t
+                                          *properties);
+    void
+    notmuch_message_properties_move_to_next (notmuch_message_properties_t
+                                                 *properties);
+    const char *
+    notmuch_message_properties_key (notmuch_message_properties_t *properties);
+    const char *
+    notmuch_message_properties_value (notmuch_message_properties_t
+                                          *properties);
+    void
+    notmuch_message_properties_destroy (notmuch_message_properties_t
+                                            *properties);
+    void
+    notmuch_message_destroy (notmuch_message_t *message);
+
+    notmuch_bool_t
+    notmuch_tags_valid (notmuch_tags_t *tags);
+    const char *
+    notmuch_tags_get (notmuch_tags_t *tags);
+    void
+    notmuch_tags_move_to_next (notmuch_tags_t *tags);
+    void
+    notmuch_tags_destroy (notmuch_tags_t *tags);
+
+    notmuch_bool_t
+    notmuch_filenames_valid (notmuch_filenames_t *filenames);
+    const char *
+    notmuch_filenames_get (notmuch_filenames_t *filenames);
+    void
+    notmuch_filenames_move_to_next (notmuch_filenames_t *filenames);
+    void
+    notmuch_filenames_destroy (notmuch_filenames_t *filenames);
+    notmuch_indexopts_t *
+    notmuch_database_get_default_indexopts (notmuch_database_t *db);
+    notmuch_status_t
+    notmuch_indexopts_set_decrypt_policy (notmuch_indexopts_t *indexopts,
+                                          notmuch_decryption_policy_t decrypt_policy);
+    notmuch_decryption_policy_t
+    notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts);
+    void
+    notmuch_indexopts_destroy (notmuch_indexopts_t *options);
+
+    notmuch_status_t
+    notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value);
+    notmuch_status_t
+    notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value);
+    notmuch_status_t
+    notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix, notmuch_config_list_t **out);
+    notmuch_bool_t
+    notmuch_config_list_valid (notmuch_config_list_t *config_list);
+    const char *
+    notmuch_config_list_key (notmuch_config_list_t *config_list);
+    const char *
+    notmuch_config_list_value (notmuch_config_list_t *config_list);
+    void
+    notmuch_config_list_move_to_next (notmuch_config_list_t *config_list);
+    void
+    notmuch_config_list_destroy (notmuch_config_list_t *config_list);
+    """
+)
+
+
+if __name__ == '__main__':
+    ffibuilder.compile(verbose=True)
diff --git a/bindings/python-cffi/notmuch2/_config.py b/bindings/python-cffi/notmuch2/_config.py
new file mode 100644 (file)
index 0000000..29de649
--- /dev/null
@@ -0,0 +1,87 @@
+import collections.abc
+
+import notmuch2._base as base
+import notmuch2._capi as capi
+import notmuch2._errors as errors
+
+
+__all__ = ['ConfigMapping']
+
+
+class ConfigIter(base.NotmuchIter):
+
+    def __init__(self, parent, iter_p):
+        super().__init__(
+            parent, iter_p,
+            fn_destroy=capi.lib.notmuch_config_list_destroy,
+            fn_valid=capi.lib.notmuch_config_list_valid,
+            fn_get=capi.lib.notmuch_config_list_key,
+            fn_next=capi.lib.notmuch_config_list_move_to_next)
+
+    def __next__(self):
+        item = super().__next__()
+        return base.BinString.from_cffi(item)
+
+
+class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
+    """The config key/value pairs stored in the database.
+
+    The entries are exposed as a :class:`collections.abc.MutableMapping` object.
+    Note that setting a value to an empty string is the same as deleting it.
+
+    :param parent: the parent object
+    :param ptr_name: the name of the attribute on the parent which will
+       return the memory pointer.  This allows this object to
+       access the pointer via the parent's descriptor and thus
+       trigger :class:`MemoryPointer`'s memory safety.
+    """
+
+    def __init__(self, parent, ptr_name):
+        self._parent = parent
+        self._ptr = lambda: getattr(parent, ptr_name)
+
+    @property
+    def alive(self):
+        return self._parent.alive
+
+    def _destroy(self):
+        pass
+
+    def __getitem__(self, key):
+        if isinstance(key, str):
+            key = key.encode('utf-8')
+        val_pp = capi.ffi.new('char**')
+        ret = capi.lib.notmuch_database_get_config(self._ptr(), key, val_pp)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        val = base.BinString.from_cffi(val_pp[0])
+        capi.lib.free(val_pp[0])
+        if val == '':
+            raise KeyError
+        return val
+
+    def __setitem__(self, key, val):
+        if isinstance(key, str):
+            key = key.encode('utf-8')
+        if isinstance(val, str):
+            val = val.encode('utf-8')
+        ret = capi.lib.notmuch_database_set_config(self._ptr(), key, val)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def __delitem__(self, key):
+        self[key] = ""
+
+    def __iter__(self):
+        """Return an iterator over the config items.
+
+        :raises NullPointerError: If the iterator can not be created.
+        """
+        configlist_pp = capi.ffi.new('notmuch_config_list_t**')
+        ret = capi.lib.notmuch_database_get_config_list(self._ptr(), b'', configlist_pp)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        return ConfigIter(self._parent, configlist_pp[0])
+
+    def __len__(self):
+        return sum(1 for t in self)
diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py
new file mode 100644 (file)
index 0000000..5ab0f20
--- /dev/null
@@ -0,0 +1,822 @@
+import collections
+import configparser
+import enum
+import functools
+import os
+import pathlib
+import weakref
+
+import notmuch2._base as base
+import notmuch2._config as config
+import notmuch2._capi as capi
+import notmuch2._errors as errors
+import notmuch2._message as message
+import notmuch2._query as querymod
+import notmuch2._tags as tags
+
+
+__all__ = ['Database', 'AtomicContext', 'DbRevision']
+
+
+def _config_pathname():
+    """Return the path of the configuration file.
+
+    :rtype: pathlib.Path
+    """
+    cfgfname = os.getenv('NOTMUCH_CONFIG', '~/.notmuch-config')
+    return pathlib.Path(os.path.expanduser(cfgfname))
+
+
+class Mode(enum.Enum):
+    READ_ONLY = capi.lib.NOTMUCH_DATABASE_MODE_READ_ONLY
+    READ_WRITE = capi.lib.NOTMUCH_DATABASE_MODE_READ_WRITE
+
+
+class QuerySortOrder(enum.Enum):
+    OLDEST_FIRST = capi.lib.NOTMUCH_SORT_OLDEST_FIRST
+    NEWEST_FIRST = capi.lib.NOTMUCH_SORT_NEWEST_FIRST
+    MESSAGE_ID = capi.lib.NOTMUCH_SORT_MESSAGE_ID
+    UNSORTED = capi.lib.NOTMUCH_SORT_UNSORTED
+
+
+class QueryExclude(enum.Enum):
+    TRUE = capi.lib.NOTMUCH_EXCLUDE_TRUE
+    FLAG = capi.lib.NOTMUCH_EXCLUDE_FLAG
+    FALSE = capi.lib.NOTMUCH_EXCLUDE_FALSE
+    ALL = capi.lib.NOTMUCH_EXCLUDE_ALL
+
+
+class DecryptionPolicy(enum.Enum):
+    FALSE = capi.lib.NOTMUCH_DECRYPT_FALSE
+    TRUE = capi.lib.NOTMUCH_DECRYPT_TRUE
+    AUTO = capi.lib.NOTMUCH_DECRYPT_AUTO
+    NOSTASH = capi.lib.NOTMUCH_DECRYPT_NOSTASH
+
+
+class Database(base.NotmuchObject):
+    """Toplevel access to notmuch.
+
+    A :class:`Database` can be opened read-only or read-write.
+    Modifications are not atomic by default, use :meth:`begin_atomic`
+    for atomic updates.  If the underlying database has been modified
+    outside of this class a :exc:`XapianError` will be raised and the
+    instance must be closed and a new one created.
+
+    You can use an instance of this class as a context-manager.
+
+    :cvar MODE: The mode a database can be opened with, an enumeration
+       of ``READ_ONLY`` and ``READ_WRITE``
+    :cvar SORT: The sort order for search results, ``OLDEST_FIRST``,
+       ``NEWEST_FIRST``, ``MESSAGE_ID`` or ``UNSORTED``.
+    :cvar EXCLUDE: Which messages to exclude from queries, ``TRUE``,
+       ``FLAG``, ``FALSE`` or ``ALL``.  See the query documentation
+       for details.
+    :cvar AddedMessage: A namedtuple ``(msg, dup)`` used by
+       :meth:`add` as return value.
+    :cvar STR_MODE_MAP: A map mapping strings to :attr:`MODE` items.
+       This is used to implement the ``ro`` and ``rw`` string
+       variants.
+
+    :ivar closed: Boolean indicating if the database is closed or
+       still open.
+
+    :param path: The directory of where the database is stored.  If
+       ``None`` the location will be read from the user's
+       configuration file, respecting the ``NOTMUCH_CONFIG``
+       environment variable if set.
+    :type path: str, bytes, os.PathLike or pathlib.Path
+    :param mode: The mode to open the database in.  One of
+       :attr:`MODE.READ_ONLY` OR :attr:`MODE.READ_WRITE`.  For
+       convenience you can also use the strings ``ro`` for
+       :attr:`MODE.READ_ONLY` and ``rw`` for :attr:`MODE.READ_WRITE`.
+    :type mode: :attr:`MODE` or str.
+
+    :raises KeyError: if an unknown mode string is used.
+    :raises OSError: or subclasses if the configuration file can not
+       be opened.
+    :raises configparser.Error: or subclasses if the configuration
+       file can not be parsed.
+    :raises NotmuchError: or subclasses for other failures.
+    """
+
+    MODE = Mode
+    SORT = QuerySortOrder
+    EXCLUDE = QueryExclude
+    AddedMessage = collections.namedtuple('AddedMessage', ['msg', 'dup'])
+    _db_p = base.MemoryPointer()
+    STR_MODE_MAP = {
+        'ro': MODE.READ_ONLY,
+        'rw': MODE.READ_WRITE,
+    }
+
+    def __init__(self, path=None, mode=MODE.READ_ONLY):
+        if isinstance(mode, str):
+            mode = self.STR_MODE_MAP[mode]
+        self.mode = mode
+        if path is None:
+            path = self.default_path()
+        if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
+            path = bytes(path)
+        db_pp = capi.ffi.new('notmuch_database_t **')
+        cmsg = capi.ffi.new('char**')
+        ret = capi.lib.notmuch_database_open_verbose(os.fsencode(path),
+                                                     mode.value, db_pp, cmsg)
+        if cmsg[0]:
+            msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
+            capi.lib.free(cmsg[0])
+        else:
+            msg = None
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret, msg)
+        self._db_p = db_pp[0]
+        self.closed = False
+
+    @classmethod
+    def create(cls, path=None):
+        """Create and open database in READ_WRITE mode.
+
+        This is creates a new notmuch database and returns an opened
+        instance in :attr:`MODE.READ_WRITE` mode.
+
+        :param path: The directory of where the database is stored.  If
+           ``None`` the location will be read from the user's
+           configuration file, respecting the ``NOTMUCH_CONFIG``
+           environment variable if set.
+        :type path: str, bytes or os.PathLike
+
+        :raises OSError: or subclasses if the configuration file can not
+           be opened.
+        :raises configparser.Error: or subclasses if the configuration
+           file can not be parsed.
+        :raises NotmuchError: if the config file does not have the
+           database.path setting.
+        :raises FileError: if the database already exists.
+
+        :returns: The newly created instance.
+        """
+        if path is None:
+            path = cls.default_path()
+        if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
+            path = bytes(path)
+        db_pp = capi.ffi.new('notmuch_database_t **')
+        cmsg = capi.ffi.new('char**')
+        ret = capi.lib.notmuch_database_create_verbose(os.fsencode(path),
+                                                       db_pp, cmsg)
+        if cmsg[0]:
+            msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
+            capi.lib.free(cmsg[0])
+        else:
+            msg = None
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret, msg)
+
+        # Now close the db and let __init__ open it.  Inefficient but
+        # creating is not a hot loop while this allows us to have a
+        # clean API.
+        ret = capi.lib.notmuch_database_destroy(db_pp[0])
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        return cls(path, cls.MODE.READ_WRITE)
+
+    @staticmethod
+    def default_path(cfg_path=None):
+        """Return the path of the user's default database.
+
+        This reads the user's configuration file and returns the
+        default path of the database.
+
+        :param cfg_path: The pathname of the notmuch configuration file.
+           If not specified tries to use the pathname provided in the
+           :env:`NOTMUCH_CONFIG` environment variable and falls back
+           to :file:`~/.notmuch-config.
+        :type cfg_path: str, bytes, os.PathLike or pathlib.Path.
+
+        :returns: The path of the database, which does not necessarily
+           exists.
+        :rtype: pathlib.Path
+        :raises OSError: or subclasses if the configuration file can not
+           be opened.
+        :raises configparser.Error: or subclasses if the configuration
+           file can not be parsed.
+        :raises NotmuchError if the config file does not have the
+           database.path setting.
+        """
+        if not cfg_path:
+            cfg_path = _config_pathname()
+        if not hasattr(os, 'PathLike') and isinstance(cfg_path, pathlib.Path):
+            cfg_path = bytes(cfg_path)
+        parser = configparser.ConfigParser()
+        with open(cfg_path) as fp:
+            parser.read_file(fp)
+        try:
+            return pathlib.Path(parser.get('database', 'path'))
+        except configparser.Error:
+            raise errors.NotmuchError(
+                'No database.path setting in {}'.format(cfg_path))
+
+    def __del__(self):
+        self._destroy()
+
+    @property
+    def alive(self):
+        try:
+            self._db_p
+        except errors.ObjectDestroyedError:
+            return False
+        else:
+            return True
+
+    def _destroy(self):
+        try:
+            ret = capi.lib.notmuch_database_destroy(self._db_p)
+        except errors.ObjectDestroyedError:
+            ret = capi.lib.NOTMUCH_STATUS_SUCCESS
+        else:
+            self._db_p = None
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def close(self):
+        """Close the notmuch database.
+
+        Once closed most operations will fail.  This can still be
+        useful however to explicitly close a database which is opened
+        read-write as this would otherwise stop other processes from
+        reading the database while it is open.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_database_close(self._db_p)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        self.closed = True
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.close()
+
+    @property
+    def path(self):
+        """The pathname of the notmuch database.
+
+        This is returned as a :class:`pathlib.Path` instance.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        try:
+            return self._cache_path
+        except AttributeError:
+            ret = capi.lib.notmuch_database_get_path(self._db_p)
+            self._cache_path = pathlib.Path(os.fsdecode(capi.ffi.string(ret)))
+            return self._cache_path
+
+    @property
+    def version(self):
+        """The database format version.
+
+        This is a positive integer.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        try:
+            return self._cache_version
+        except AttributeError:
+            ret = capi.lib.notmuch_database_get_version(self._db_p)
+            self._cache_version = ret
+            return ret
+
+    @property
+    def needs_upgrade(self):
+        """Whether the database should be upgraded.
+
+        If *True* the database can be upgraded using :meth:`upgrade`.
+        Not doing so may result in some operations raising
+        :exc:`UpgradeRequiredError`.
+
+        A read-only database will never be upgradable.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_database_needs_upgrade(self._db_p)
+        return bool(ret)
+
+    def upgrade(self, progress_cb=None):
+        """Upgrade the database to the latest version.
+
+        Upgrade the database, optionally with a progress callback
+        which should be a callable which will be called with a
+        floating point number in the range of [0.0 .. 1.0].
+        """
+        raise NotImplementedError
+
+    def atomic(self):
+        """Return a context manager to perform atomic operations.
+
+        The returned context manager can be used to perform atomic
+        operations on the database.
+
+        .. note:: Unlinke a traditional RDBMS transaction this does
+           not imply durability, it only ensures the changes are
+           performed atomically.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ctx = AtomicContext(self, '_db_p')
+        return ctx
+
+    def revision(self):
+        """The currently committed revision in the database.
+
+        Returned as a ``(revision, uuid)`` namedtuple.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        raw_uuid = capi.ffi.new('char**')
+        rev = capi.lib.notmuch_database_get_revision(self._db_p, raw_uuid)
+        return DbRevision(rev, capi.ffi.string(raw_uuid[0]))
+
+    def get_directory(self, path):
+        raise NotImplementedError
+
+    def default_indexopts(self):
+        """Returns default index options for the database.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+
+        :returns: :class:`IndexOptions`.
+        """
+        opts = capi.lib.notmuch_database_get_default_indexopts(self._db_p)
+        return IndexOptions(self, opts)
+
+    def add(self, filename, *, sync_flags=False, indexopts=None):
+        """Add a message to the database.
+
+        Add a new message to the notmuch database.  The message is
+        referred to by the pathname of the maildir file.  If the
+        message ID of the new message already exists in the database,
+        this adds ``pathname`` to the list of list of files for the
+        existing message.
+
+        :param filename: The path of the file containing the message.
+        :type filename: str, bytes, os.PathLike or pathlib.Path.
+        :param sync_flags: Whether to sync the known maildir flags to
+           notmuch tags.  See :meth:`Message.flags_to_tags` for
+           details.
+        :type sync_flags: bool
+        :param indexopts: The indexing options, see
+           :meth:`default_indexopts`.  Leave as `None` to use the
+           default options configured in the database.
+        :type indexopts: :class:`IndexOptions` or `None`
+
+        :returns: A tuple where the first item is the newly inserted
+           messages as a :class:`Message` instance, and the second
+           item is a boolean indicating if the message inserted was a
+           duplicate.  This is the namedtuple ``AddedMessage(msg,
+           dup)``.
+        :rtype: Database.AddedMessage
+
+        If an exception is raised, no message was added.
+
+        :raises XapianError: A Xapian exception occurred.
+        :raises FileError: The file referred to by ``pathname`` could
+           not be opened.
+        :raises FileNotEmailError: The file referreed to by
+           ``pathname`` is not recognised as an email message.
+        :raises ReadOnlyDatabaseError: The database is opened in
+           READ_ONLY mode.
+        :raises UpgradeRequiredError: The database must be upgraded
+           first.
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path):
+            filename = bytes(filename)
+        msg_pp = capi.ffi.new('notmuch_message_t **')
+        opts_p = indexopts._opts_p if indexopts else capi.ffi.NULL
+        ret = capi.lib.notmuch_database_index_file(
+            self._db_p, os.fsencode(filename), opts_p, msg_pp)
+        ok = [capi.lib.NOTMUCH_STATUS_SUCCESS,
+              capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID]
+        if ret not in ok:
+            raise errors.NotmuchError(ret)
+        msg = message.Message(self, msg_pp[0], db=self)
+        if sync_flags:
+            msg.tags.from_maildir_flags()
+        return self.AddedMessage(
+            msg, ret == capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
+
+    def remove(self, filename):
+        """Remove a message from the notmuch database.
+
+        Removing a message which is not in the database is just a
+        silent nop-operation.
+
+        :param filename: The pathname of the file containing the
+           message to be removed.
+        :type filename: str, bytes, os.PathLike or pathlib.Path.
+
+        :returns: True if the message is still in the database.  This
+           can happen when multiple files contain the same message ID.
+           The true/false distinction is fairly arbitrary, but think
+           of it as ``dup = db.remove_message(name); if dup: ...``.
+        :rtype: bool
+
+        :raises XapianError: A Xapian exception occurred.
+        :raises ReadOnlyDatabaseError: The database is opened in
+           READ_ONLY mode.
+        :raises UpgradeRequiredError: The database must be upgraded
+           first.
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path):
+            filename = bytes(filename)
+        ret = capi.lib.notmuch_database_remove_message(self._db_p,
+                                                       os.fsencode(filename))
+        ok = [capi.lib.NOTMUCH_STATUS_SUCCESS,
+              capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID]
+        if ret not in ok:
+            raise errors.NotmuchError(ret)
+        if ret == capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+            return True
+        else:
+            return False
+
+    def find(self, msgid):
+        """Return the message matching the given message ID.
+
+        If a message with the given message ID is found a
+        :class:`Message` instance is returned.  Otherwise a
+        :exc:`LookupError` is raised.
+
+        :param msgid: The message ID to look for.
+        :type msgid: str
+
+        :returns: The message instance.
+        :rtype: Message
+
+        :raises LookupError: If no message was found.
+        :raises OutOfMemoryError: When there is no memory to allocate
+           the message instance.
+        :raises XapianError: A Xapian exception occurred.
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        msg_pp = capi.ffi.new('notmuch_message_t **')
+        ret = capi.lib.notmuch_database_find_message(self._db_p,
+                                                     msgid.encode(), msg_pp)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        msg_p = msg_pp[0]
+        if msg_p == capi.ffi.NULL:
+            raise LookupError
+        msg = message.Message(self, msg_p, db=self)
+        return msg
+
+    def get(self, filename):
+        """Return the :class:`Message` given a pathname.
+
+        If a message with the given pathname exists in the database
+        return the :class:`Message` instance for the message.
+        Otherwise raise a :exc:`LookupError` exception.
+
+        :param filename: The pathname of the message.
+        :type filename: str, bytes, os.PathLike or pathlib.Path
+
+        :returns: The message instance.
+        :rtype: Message
+
+        :raises LookupError: If no message was found.  This is also
+           a subclass of :exc:`KeyError`.
+        :raises OutOfMemoryError: When there is no memory to allocate
+           the message instance.
+        :raises XapianError: A Xapian exception occurred.
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path):
+            filename = bytes(filename)
+        msg_pp = capi.ffi.new('notmuch_message_t **')
+        ret = capi.lib.notmuch_database_find_message_by_filename(
+            self._db_p, os.fsencode(filename), msg_pp)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        msg_p = msg_pp[0]
+        if msg_p == capi.ffi.NULL:
+            raise LookupError
+        msg = message.Message(self, msg_p, db=self)
+        return msg
+
+    @property
+    def tags(self):
+        """Return an immutable set with all tags used in this database.
+
+        This returns an immutable set-like object implementing the
+        collections.abc.Set Abstract Base Class.  Due to the
+        underlying libnotmuch implementation some operations have
+        different performance characteristics then plain set objects.
+        Mainly any lookup operation is O(n) rather then O(1).
+
+        Normal usage treats tags as UTF-8 encoded unicode strings so
+        they are exposed to Python as normal unicode string objects.
+        If you need to handle tags stored in libnotmuch which are not
+        valid unicode do check the :class:`ImmutableTagSet` docs for
+        how to handle this.
+
+        :rtype: ImmutableTagSet
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        try:
+            ref = self._cached_tagset
+        except AttributeError:
+            tagset = None
+        else:
+            tagset = ref()
+        if tagset is None:
+            tagset = tags.ImmutableTagSet(
+                self, '_db_p', capi.lib.notmuch_database_get_all_tags)
+            self._cached_tagset = weakref.ref(tagset)
+        return tagset
+
+    @property
+    def config(self):
+        """Return a mutable mapping with the settings stored in this database.
+
+        This returns an mutable dict-like object implementing the
+        collections.abc.MutableMapping Abstract Base Class.
+
+        :rtype: Config
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        try:
+            ref = self._cached_config
+        except AttributeError:
+            config_mapping = None
+        else:
+            config_mapping = ref()
+        if config_mapping is None:
+            config_mapping = config.ConfigMapping(self, '_db_p')
+            self._cached_config = weakref.ref(config_mapping)
+        return config_mapping
+
+    def _create_query(self, query, *,
+                      omit_excluded=EXCLUDE.TRUE,
+                      sort=SORT.UNSORTED,  # Check this default
+                      exclude_tags=None):
+        """Create an internal query object.
+
+        :raises OutOfMemoryError: if no memory is available to
+           allocate the query.
+        """
+        if isinstance(query, str):
+            query = query.encode('utf-8')
+        query_p = capi.lib.notmuch_query_create(self._db_p, query)
+        if query_p == capi.ffi.NULL:
+            raise errors.OutOfMemoryError()
+        capi.lib.notmuch_query_set_omit_excluded(query_p, omit_excluded.value)
+        capi.lib.notmuch_query_set_sort(query_p, sort.value)
+        if exclude_tags is not None:
+            for tag in exclude_tags:
+                if isinstance(tag, str):
+                    tag = str.encode('utf-8')
+                capi.lib.notmuch_query_add_tag_exclude(query_p, tag)
+        return querymod.Query(self, query_p)
+
+    def messages(self, query, *,
+                 omit_excluded=EXCLUDE.TRUE,
+                 sort=SORT.UNSORTED,  # Check this default
+                 exclude_tags=None):
+        """Search the database for messages.
+
+        :returns: An iterator over the messages found.
+        :rtype: MessageIter
+
+        :raises OutOfMemoryError: if no memory is available to
+           allocate the query.
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        query = self._create_query(query,
+                                   omit_excluded=omit_excluded,
+                                   sort=sort,
+                                   exclude_tags=exclude_tags)
+        return query.messages()
+
+    def count_messages(self, query, *,
+                       omit_excluded=EXCLUDE.TRUE,
+                       sort=SORT.UNSORTED,  # Check this default
+                       exclude_tags=None):
+        """Search the database for messages.
+
+        :returns: An iterator over the messages found.
+        :rtype: MessageIter
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        query = self._create_query(query,
+                                   omit_excluded=omit_excluded,
+                                   sort=sort,
+                                   exclude_tags=exclude_tags)
+        return query.count_messages()
+
+    def threads(self,  query, *,
+                omit_excluded=EXCLUDE.TRUE,
+                sort=SORT.UNSORTED,  # Check this default
+                exclude_tags=None):
+        query = self._create_query(query,
+                                   omit_excluded=omit_excluded,
+                                   sort=sort,
+                                   exclude_tags=exclude_tags)
+        return query.threads()
+
+    def count_threads(self, query, *,
+                      omit_excluded=EXCLUDE.TRUE,
+                      sort=SORT.UNSORTED,  # Check this default
+                      exclude_tags=None):
+        query = self._create_query(query,
+                                   omit_excluded=omit_excluded,
+                                   sort=sort,
+                                   exclude_tags=exclude_tags)
+        return query.count_threads()
+
+    def status_string(self):
+        raise NotImplementedError
+
+    def __repr__(self):
+        return 'Database(path={self.path}, mode={self.mode})'.format(self=self)
+
+
+class AtomicContext:
+    """Context manager for atomic support.
+
+    This supports the notmuch_database_begin_atomic and
+    notmuch_database_end_atomic API calls.  The object can not be
+    directly instantiated by the user, only via ``Database.atomic``.
+    It does keep a reference to the :class:`Database` instance to keep
+    the C memory alive.
+
+    :raises XapianError: When this is raised at enter time the atomic
+       section is not active.  When it is raised at exit time the
+       atomic section is still active and you may need to try using
+       :meth:`force_end`.
+    :raises ObjectDestroyedError: if used after destroyed.
+    """
+
+    def __init__(self, db, ptr_name):
+        self._db = db
+        self._ptr = lambda: getattr(db, ptr_name)
+        self._exit_fn = lambda: None
+
+    def __del__(self):
+        self._destroy()
+
+    @property
+    def alive(self):
+        return self.parent.alive
+
+    def _destroy(self):
+        pass
+
+    def __enter__(self):
+        ret = capi.lib.notmuch_database_begin_atomic(self._ptr())
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        self._exit_fn = self._end_atomic
+        return self
+
+    def _end_atomic(self):
+        ret = capi.lib.notmuch_database_end_atomic(self._ptr())
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self._exit_fn()
+
+    def force_end(self):
+        """Force ending the atomic section.
+
+        This can only be called once __exit__ has been called.  It
+        will attempt to close the atomic section (again).  This is
+        useful if the original exit raised an exception and the atomic
+        section is still open.  But things are pretty ugly by now.
+
+        :raises XapianError: If exiting fails, the atomic section is
+           not ended.
+        :raises UnbalancedAtomicError: If the database was currently
+           not in an atomic section.
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_database_end_atomic(self._ptr())
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def abort(self):
+        """Abort the transaction.
+
+        Aborting a transaction will not commit any of the changes, but
+        will also implicitly close the database.
+        """
+        self._exit_fn = lambda: None
+        self._db.close()
+
+
+@functools.total_ordering
+class DbRevision:
+    """A database revision.
+
+    The database revision number increases monotonically with each
+    commit to the database.  Which means user-visible changes can be
+    ordered.  This object is sortable with other revisions.  It
+    carries the UUID of the database to ensure it is only ever
+    compared with revisions from the same database.
+    """
+
+    def __init__(self, rev, uuid):
+        self._rev = rev
+        self._uuid = uuid
+
+    @property
+    def rev(self):
+        """The revision number, a positive integer."""
+        return self._rev
+
+    @property
+    def uuid(self):
+        """The UUID of the database, consider this opaque."""
+        return self._uuid
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            if self.uuid != other.uuid:
+                return False
+            return self.rev == other.rev
+        else:
+            return NotImplemented
+
+    def __lt__(self, other):
+        if self.__class__ is other.__class__:
+            if self.uuid != other.uuid:
+                return False
+            return self.rev < other.rev
+        else:
+            return NotImplemented
+
+    def __repr__(self):
+        return 'DbRevision(rev={self.rev}, uuid={self.uuid})'.format(self=self)
+
+
+class IndexOptions(base.NotmuchObject):
+    """Indexing options.
+
+    This represents the indexing options which can be used to index a
+    message.  See :meth:`Database.default_indexopts` to create an
+    instance of this.  It can be used e.g. when indexing a new message
+    using :meth:`Database.add`.
+    """
+    _opts_p = base.MemoryPointer()
+
+    def __init__(self, parent, opts_p):
+        self._parent = parent
+        self._opts_p = opts_p
+
+    @property
+    def alive(self):
+        if not self._parent.alive:
+            return False
+        try:
+            self._opts_p
+        except errors.ObjectDestroyedError:
+            return False
+        else:
+            return True
+
+    def _destroy(self):
+        if self.alive:
+            capi.lib.notmuch_indexopts_destroy(self._opts_p)
+        self._opts_p = None
+
+    @property
+    def decrypt_policy(self):
+        """The decryption policy.
+
+        This is an enum from the :class:`DecryptionPolicy`.  See the
+        `index.decrypt` section in :man:`notmuch-config` for details
+        on the options.  **Do not set this to
+        :attr:`DecryptionPolicy.TRUE`** without considering the
+        security of your index.
+
+        You can change this policy by assigning a new
+        :class:`DecryptionPolicy` to this property.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+
+        :returns: A :class:`DecryptionPolicy` enum instance.
+        """
+        raw = capi.lib.notmuch_indexopts_get_decrypt_policy(self._opts_p)
+        return DecryptionPolicy(raw)
+
+    @decrypt_policy.setter
+    def decrypt_policy(self, val):
+        ret = capi.lib.notmuch_indexopts_set_decrypt_policy(
+            self._opts_p, val.value)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret, msg)
diff --git a/bindings/python-cffi/notmuch2/_errors.py b/bindings/python-cffi/notmuch2/_errors.py
new file mode 100644 (file)
index 0000000..1336944
--- /dev/null
@@ -0,0 +1,112 @@
+from notmuch2 import _capi as capi
+
+
+class NotmuchError(Exception):
+    """Base exception for errors originating from the notmuch library.
+
+    Usually this will have two attributes:
+
+    :status: This is a numeric status code corresponding to the error
+       code in the notmuch library.  This is normally fairly
+       meaningless, it can also often be ``None``.  This exists mostly
+       to easily create new errors from notmuch status codes and
+       should not normally be used by users.
+
+    :message: A user-facing message for the error.  This can
+       occasionally also be ``None``.  Usually you'll want to call
+       ``str()`` on the error object instead to get a sensible
+       message.
+    """
+
+    @classmethod
+    def exc_type(cls, status):
+        """Return correct exception type for notmuch status."""
+        types = {
+            capi.lib.NOTMUCH_STATUS_OUT_OF_MEMORY:
+                OutOfMemoryError,
+            capi.lib.NOTMUCH_STATUS_READ_ONLY_DATABASE:
+                ReadOnlyDatabaseError,
+            capi.lib.NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+                XapianError,
+            capi.lib.NOTMUCH_STATUS_FILE_ERROR:
+                FileError,
+            capi.lib.NOTMUCH_STATUS_FILE_NOT_EMAIL:
+                FileNotEmailError,
+            capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+                DuplicateMessageIdError,
+            capi.lib.NOTMUCH_STATUS_NULL_POINTER:
+                NullPointerError,
+            capi.lib.NOTMUCH_STATUS_TAG_TOO_LONG:
+                TagTooLongError,
+            capi.lib.NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
+                UnbalancedFreezeThawError,
+            capi.lib.NOTMUCH_STATUS_UNBALANCED_ATOMIC:
+                UnbalancedAtomicError,
+            capi.lib.NOTMUCH_STATUS_UNSUPPORTED_OPERATION:
+                UnsupportedOperationError,
+            capi.lib.NOTMUCH_STATUS_UPGRADE_REQUIRED:
+                UpgradeRequiredError,
+            capi.lib.NOTMUCH_STATUS_PATH_ERROR:
+                PathError,
+            capi.lib.NOTMUCH_STATUS_ILLEGAL_ARGUMENT:
+                IllegalArgumentError,
+        }
+        return types[status]
+
+    def __new__(cls, *args, **kwargs):
+        """Return the correct subclass based on status."""
+        # This is simplistic, but the actual __init__ will fail if the
+        # signature is wrong anyway.
+        if args:
+            status = args[0]
+        else:
+            status = kwargs.get('status', None)
+        if status and cls == NotmuchError:
+            exc = cls.exc_type(status)
+            return exc.__new__(exc, *args, **kwargs)
+        else:
+            return super().__new__(cls)
+
+    def __init__(self, status=None, message=None):
+        self.status = status
+        self.message = message
+
+    def __str__(self):
+        if self.message:
+            return self.message
+        elif self.status:
+            return capi.lib.notmuch_status_to_string(self.status)
+        else:
+            return 'Unknown error'
+
+
+class OutOfMemoryError(NotmuchError): pass
+class ReadOnlyDatabaseError(NotmuchError): pass
+class XapianError(NotmuchError): pass
+class FileError(NotmuchError): pass
+class FileNotEmailError(NotmuchError): pass
+class DuplicateMessageIdError(NotmuchError): pass
+class NullPointerError(NotmuchError): pass
+class TagTooLongError(NotmuchError): pass
+class UnbalancedFreezeThawError(NotmuchError): pass
+class UnbalancedAtomicError(NotmuchError): pass
+class UnsupportedOperationError(NotmuchError): pass
+class UpgradeRequiredError(NotmuchError): pass
+class PathError(NotmuchError): pass
+class IllegalArgumentError(NotmuchError): pass
+
+
+class ObjectDestroyedError(NotmuchError):
+    """The object has already been destroyed and it's memory freed.
+
+    This occurs when :meth:`destroy` has been called on the object but
+    you still happen to have access to the object.  This should not
+    normally occur since you should never call :meth:`destroy` by
+    hand.
+    """
+
+    def __str__(self):
+        if self.message:
+            return self.message
+        else:
+            return 'Memory already freed'
diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py
new file mode 100644 (file)
index 0000000..2f23207
--- /dev/null
@@ -0,0 +1,710 @@
+import collections
+import contextlib
+import os
+import pathlib
+import weakref
+
+import notmuch2._base as base
+import notmuch2._capi as capi
+import notmuch2._errors as errors
+import notmuch2._tags as tags
+
+
+__all__ = ['Message']
+
+
+class Message(base.NotmuchObject):
+    """An email message stored in the notmuch database retrieved via a query.
+
+    This should not be directly created, instead it will be returned
+    by calling methods on :class:`Database`.  A message keeps a
+    reference to the database object since the database object can not
+    be released while the message is in use.
+
+    Note that this represents a message in the notmuch database.  For
+    full email functionality you may want to use the :mod:`email`
+    package from Python's standard library.  You could e.g. create
+    this as such::
+
+       notmuch_msg = db.get_message(msgid)  # or from a query
+       parser = email.parser.BytesParser(policy=email.policy.default)
+       with notmuch_msg.path.open('rb) as fp:
+           email_msg = parser.parse(fp)
+
+    Most commonly the functionality provided by notmuch is sufficient
+    to read email however.
+
+    Messages are considered equal when they have the same message ID.
+    This is how libnotmuch treats messages as well, the
+    :meth:`pathnames` function returns multiple results for
+    duplicates.
+
+    :param parent: The parent object.  This is probably one off a
+       :class:`Database`, :class:`Thread` or :class:`Query`.
+    :type parent: NotmuchObject
+    :param db: The database instance this message is associated with.
+       This could be the same as the parent.
+    :type db: Database
+    :param msg_p: The C pointer to the ``notmuch_message_t``.
+    :type msg_p: <cdata>
+    :param dup: Whether the message was a duplicate on insertion.
+    :type dup: None or bool
+    """
+    _msg_p = base.MemoryPointer()
+
+    def __init__(self, parent, msg_p, *, db):
+        self._parent = parent
+        self._msg_p = msg_p
+        self._db = db
+
+    @property
+    def alive(self):
+        if not self._parent.alive:
+            return False
+        try:
+            self._msg_p
+        except errors.ObjectDestroyedError:
+            return False
+        else:
+            return True
+
+    def __del__(self):
+        self._destroy()
+
+    def _destroy(self):
+        if self.alive:
+            capi.lib.notmuch_message_destroy(self._msg_p)
+        self._msg_p = None
+
+    @property
+    def messageid(self):
+        """The message ID as a string.
+
+        The message ID is decoded with the ignore error handler.  This
+        is fine as long as the message ID is well formed.  If it is
+        not valid ASCII then this will be lossy.  So if you need to be
+        able to write the exact same message ID back you should use
+        :attr:`messageidb`.
+
+        Note that notmuch will decode the message ID value and thus
+        strip off the surrounding ``<`` and ``>`` characters.  This is
+        different from Python's :mod:`email` package behaviour which
+        leaves these characters in place.
+
+        :returns: The message ID.
+        :rtype: :class:`BinString`, this is a normal str but calling
+           bytes() on it will return the original bytes used to create
+           it.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_message_get_message_id(self._msg_p)
+        return base.BinString(capi.ffi.string(ret))
+
+    @property
+    def threadid(self):
+        """The thread ID.
+
+        The thread ID is decoded with the surrogateescape error
+        handler so that it is possible to reconstruct the original
+        thread ID if it is not valid UTF-8.
+
+        :returns: The thread ID.
+        :rtype: :class:`BinString`, this is a normal str but calling
+           bytes() on it will return the original bytes used to create
+           it.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_message_get_thread_id(self._msg_p)
+        return base.BinString(capi.ffi.string(ret))
+
+    @property
+    def path(self):
+        """A pathname of the message as a pathlib.Path instance.
+
+        If multiple files in the database contain the same message ID
+        this will be just one of the files, chosen at random.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_message_get_filename(self._msg_p)
+        return pathlib.Path(os.fsdecode(capi.ffi.string(ret)))
+
+    @property
+    def pathb(self):
+        """A pathname of the message as a bytes object.
+
+        See :attr:`path` for details, this is the same but does return
+        the path as a bytes object which is faster but less convenient.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_message_get_filename(self._msg_p)
+        return capi.ffi.string(ret)
+
+    def filenames(self):
+        """Return an iterator of all files for this message.
+
+        If multiple files contained the same message ID they will all
+        be returned here.  The files are returned as instances of
+        :class:`pathlib.Path`.
+
+        :returns: Iterator yielding :class:`pathlib.Path` instances.
+        :rtype: iter
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        fnames_p = capi.lib.notmuch_message_get_filenames(self._msg_p)
+        return PathIter(self, fnames_p)
+
+    def filenamesb(self):
+        """Return an iterator of all files for this message.
+
+        This is like :meth:`pathnames` but the files are returned as
+        byte objects instead.
+
+        :returns: Iterator yielding :class:`bytes` instances.
+        :rtype: iter
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        fnames_p = capi.lib.notmuch_message_get_filenames(self._msg_p)
+        return FilenamesIter(self, fnames_p)
+
+    @property
+    def ghost(self):
+        """Indicates whether this message is a ghost message.
+
+        A ghost message if a message which we know exists, but it has
+        no files or content associated with it.  This can happen if
+        it was referenced by some other message.  Only the
+        :attr:`messageid` and :attr:`threadid` attributes are valid
+        for it.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_message_get_flag(
+            self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_GHOST)
+        return bool(ret)
+
+    @property
+    def excluded(self):
+        """Indicates whether this message was excluded from the query.
+
+        When a message is created from a search, sometimes messages
+        that where excluded by the search query could still be
+        returned by it, e.g. because they are part of a thread
+        matching the query.  the :meth:`Database.query` method allows
+        these messages to be flagged, which results in this property
+        being set to *True*.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_message_get_flag(
+            self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_EXCLUDED)
+        return bool(ret)
+
+    @property
+    def date(self):
+        """The message date as an integer.
+
+        The time the message was sent as an integer number of seconds
+        since the *epoch*, 1 Jan 1970.  This is derived from the
+        message's header, you can get the original header value with
+        :meth:`header`.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        return capi.lib.notmuch_message_get_date(self._msg_p)
+
+    def header(self, name):
+        """Return the value of the named header.
+
+        Returns the header from notmuch, some common headers are
+        stored in the database, others are read from the file.
+        Headers are returned with their newlines stripped and
+        collapsed concatenated together if they occur multiple times.
+        You may be better off using the standard library email
+        package's ``email.message_from_file(msg.path.open())`` if that
+        is not sufficient for you.
+
+        :param header: Case-insensitive header name to retrieve.
+        :type header: str or bytes
+
+        :returns: The header value, an empty string if the header is
+           not present.
+        :rtype: str
+
+        :raises LookupError: if the header is not present.
+        :raises NullPointerError: For unexpected notmuch errors.
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        # The returned is supposedly guaranteed to be UTF-8.  Header
+        # names must be ASCII as per RFC x822.
+        if isinstance(name, str):
+            name = name.encode('ascii')
+        ret = capi.lib.notmuch_message_get_header(self._msg_p, name)
+        if ret == capi.ffi.NULL:
+            raise errors.NullPointerError()
+        hdr = capi.ffi.string(ret)
+        if not hdr:
+            raise LookupError
+        return hdr.decode(encoding='utf-8')
+
+    @property
+    def tags(self):
+        """The tags associated with the message.
+
+        This behaves as a set.  But removing and adding items to the
+        set removes and adds them to the message in the database.
+
+        :raises ReadOnlyDatabaseError: When manipulating tags on a
+           database opened in read-only mode.
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        try:
+            ref = self._cached_tagset
+        except AttributeError:
+            tagset = None
+        else:
+            tagset = ref()
+        if tagset is None:
+            tagset = tags.MutableTagSet(
+                self, '_msg_p', capi.lib.notmuch_message_get_tags)
+            self._cached_tagset = weakref.ref(tagset)
+        return tagset
+
+    @contextlib.contextmanager
+    def frozen(self):
+        """Context manager to freeze the message state.
+
+        This allows you to perform atomic tag updates::
+
+           with msg.frozen():
+               msg.tags.clear()
+               msg.tags.add('foo')
+
+        Using This would ensure the message never ends up with no tags
+        applied at all.
+
+        It is safe to nest calls to this context manager.
+
+        :raises ReadOnlyDatabaseError: if the database is opened in
+           read-only mode.
+        :raises UnbalancedFreezeThawError: if you somehow managed to
+           call __exit__ of this context manager more than once.  Why
+           did you do that?
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_message_freeze(self._msg_p)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        self._frozen = True
+        try:
+            yield
+        except Exception:
+            # Only way to "rollback" these changes is to destroy
+            # ourselves and re-create.  Behold.
+            msgid = self.messageid
+            self._destroy()
+            with contextlib.suppress(Exception):
+                new = self._db.find(msgid)
+                self._msg_p = new._msg_p
+                new._msg_p = None
+                del new
+            raise
+        else:
+            ret = capi.lib.notmuch_message_thaw(self._msg_p)
+            if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+                raise errors.NotmuchError(ret)
+            self._frozen = False
+
+    @property
+    def properties(self):
+        """A map of arbitrary key-value pairs associated with the message.
+
+        Be aware that properties may be used by other extensions to
+        store state in.  So delete or modify with care.
+
+        The properties map is somewhat special.  It is essentially a
+        multimap-like structure where each key can have multiple
+        values.  Therefore accessing a single item using
+        :meth:`PropertiesMap.get` or :meth:`PropertiesMap.__getitem__`
+        will only return you the *first* item if there are multiple
+        and thus are only recommended if you know there to be only one
+        value.
+
+        Instead the map has an additional :meth:`PropertiesMap.all`
+        method which can be used to retrieve all properties of a given
+        key.  This method also allows iterating of a a subset of the
+        keys starting with a given prefix.
+        """
+        try:
+            ref = self._cached_props
+        except AttributeError:
+            props = None
+        else:
+            props = ref()
+        if props is None:
+            props = PropertiesMap(self, '_msg_p')
+            self._cached_props = weakref.ref(props)
+        return props
+
+    def replies(self):
+        """Return an iterator of all replies to this message.
+
+        This method will only work if the message was created from a
+        thread.  Otherwise it will yield no results.
+
+        :returns: An iterator yielding :class:`Message` instances.
+        :rtype: MessageIter
+        """
+        # The notmuch_messages_valid call accepts NULL and this will
+        # become an empty iterator, raising StopIteration immediately.
+        # Hence no return value checking here.
+        msgs_p = capi.lib.notmuch_message_get_replies(self._msg_p)
+        return MessageIter(self, msgs_p, db=self._db)
+
+    def __hash__(self):
+        return hash(self.messageid)
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self.messageid == other.messageid
+
+
+class OwnedMessage(Message):
+    """An email message owned by parent thread object.
+
+    This subclass of Message is used for messages that are retrieved
+    from the notmuch database via a parent :class:`notmuch2.Thread`
+    object, which "owns" this message.  This means that when this
+    message object is destroyed, by calling :func:`del` or
+    :meth:`_destroy` directly or indirectly, the message is not freed
+    in the notmuch API and the parent :class:`notmuch2.Thread` object
+    can return the same object again when needed.
+    """
+
+    @property
+    def alive(self):
+        return self._parent.alive
+
+    def _destroy(self):
+        pass
+
+
+class FilenamesIter(base.NotmuchIter):
+    """Iterator for binary filenames objects."""
+
+    def __init__(self, parent, iter_p):
+        super().__init__(parent, iter_p,
+                         fn_destroy=capi.lib.notmuch_filenames_destroy,
+                         fn_valid=capi.lib.notmuch_filenames_valid,
+                         fn_get=capi.lib.notmuch_filenames_get,
+                         fn_next=capi.lib.notmuch_filenames_move_to_next)
+
+    def __next__(self):
+        fname = super().__next__()
+        return capi.ffi.string(fname)
+
+
+class PathIter(FilenamesIter):
+    """Iterator for pathlib.Path objects."""
+
+    def __next__(self):
+        fname = super().__next__()
+        return pathlib.Path(os.fsdecode(fname))
+
+
+class PropertiesMap(base.NotmuchObject, collections.abc.MutableMapping):
+    """A mutable mapping to manage properties.
+
+    Both keys and values of properties are supposed to be UTF-8
+    strings in libnotmuch.  However since the uderlying API uses
+    bytestrings you can use either str or bytes to represent keys and
+    all returned keys and values use :class:`BinString`.
+
+    Also be aware that ``iter(this_map)`` will return duplicate keys,
+    while the :class:`collections.abc.KeysView` returned by
+    :meth:`keys` is a :class:`collections.abc.Set` subclass.  This
+    means the former will yield duplicate keys while the latter won't.
+    It also means ``len(list(iter(this_map)))`` could be different
+    than ``len(this_map.keys())``.  ``len(this_map)`` will correspond
+    with the length of the default iterator.
+
+    Be aware that libnotmuch exposes all of this as iterators, so
+    quite a few operations have O(n) performance instead of the usual
+    O(1).
+    """
+    Property = collections.namedtuple('Property', ['key', 'value'])
+    _marker = object()
+
+    def __init__(self, msg, ptr_name):
+        self._msg = msg
+        self._ptr = lambda: getattr(msg, ptr_name)
+
+    @property
+    def alive(self):
+        if not self._msg.alive:
+            return False
+        try:
+            self._ptr
+        except errors.ObjectDestroyedError:
+            return False
+        else:
+            return True
+
+    def _destroy(self):
+        pass
+
+    def __iter__(self):
+        """Return an iterator which iterates over the keys.
+
+        Be aware that a single key may have multiple values associated
+        with it, if so it will appear multiple times here.
+        """
+        iter_p = capi.lib.notmuch_message_get_properties(self._ptr(), b'', 0)
+        return PropertiesKeyIter(self, iter_p)
+
+    def __len__(self):
+        iter_p = capi.lib.notmuch_message_get_properties(self._ptr(), b'', 0)
+        it = base.NotmuchIter(
+            self, iter_p,
+            fn_destroy=capi.lib.notmuch_message_properties_destroy,
+            fn_valid=capi.lib.notmuch_message_properties_valid,
+            fn_get=capi.lib.notmuch_message_properties_key,
+            fn_next=capi.lib.notmuch_message_properties_move_to_next,
+        )
+        return len(list(it))
+
+    def __getitem__(self, key):
+        """Return **the first** peroperty associated with a key."""
+        if isinstance(key, str):
+            key = key.encode('utf-8')
+        value_pp = capi.ffi.new('char**')
+        ret = capi.lib.notmuch_message_get_property(self._ptr(), key, value_pp)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        if value_pp[0] == capi.ffi.NULL:
+            raise KeyError
+        return base.BinString.from_cffi(value_pp[0])
+
+    def keys(self):
+        """Return a :class:`collections.abc.KeysView` for this map.
+
+        Even when keys occur multiple times this is a subset of set()
+        so will only contain them once.
+        """
+        return collections.abc.KeysView({k: None for k in self})
+
+    def items(self):
+        """Return a :class:`collections.abc.ItemsView` for this map.
+
+        The ItemsView treats a ``(key, value)`` pair as unique, so
+        dupcliate ``(key, value)`` pairs will be merged together.
+        However duplicate keys with different values will be returned.
+        """
+        items = set()
+        props_p = capi.lib.notmuch_message_get_properties(self._ptr(), b'', 0)
+        while capi.lib.notmuch_message_properties_valid(props_p):
+            key = capi.lib.notmuch_message_properties_key(props_p)
+            value = capi.lib.notmuch_message_properties_value(props_p)
+            items.add((base.BinString.from_cffi(key),
+                       base.BinString.from_cffi(value)))
+            capi.lib.notmuch_message_properties_move_to_next(props_p)
+        capi.lib.notmuch_message_properties_destroy(props_p)
+        return PropertiesItemsView(items)
+
+    def values(self):
+        """Return a :class:`collecions.abc.ValuesView` for this map.
+
+        All unique property values are included in the view.
+        """
+        values = set()
+        props_p = capi.lib.notmuch_message_get_properties(self._ptr(), b'', 0)
+        while capi.lib.notmuch_message_properties_valid(props_p):
+            value = capi.lib.notmuch_message_properties_value(props_p)
+            values.add(base.BinString.from_cffi(value))
+            capi.lib.notmuch_message_properties_move_to_next(props_p)
+        capi.lib.notmuch_message_properties_destroy(props_p)
+        return PropertiesValuesView(values)
+
+    def __setitem__(self, key, value):
+        """Add a key-value pair to the properties.
+
+        You may prefer to use :meth:`add` for clarity since this
+        method usually implies implicit overwriting of an existing key
+        if it exists, while for properties this is not the case.
+        """
+        self.add(key, value)
+
+    def add(self, key, value):
+        """Add a key-value pair to the properties."""
+        if isinstance(key, str):
+            key = key.encode('utf-8')
+        if isinstance(value, str):
+            value = value.encode('utf-8')
+        ret = capi.lib.notmuch_message_add_property(self._ptr(), key, value)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def __delitem__(self, key):
+        """Remove all properties with this key."""
+        if isinstance(key, str):
+            key = key.encode('utf-8')
+        ret = capi.lib.notmuch_message_remove_all_properties(self._ptr(), key)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def remove(self, key, value):
+        """Remove a key-value pair from the properties."""
+        if isinstance(key, str):
+            key = key.encode('utf-8')
+        if isinstance(value, str):
+            value = value.encode('utf-8')
+        ret = capi.lib.notmuch_message_remove_property(self._ptr(), key, value)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def pop(self, key, default=_marker):
+        try:
+            value = self[key]
+        except KeyError:
+            if default is self._marker:
+                raise
+            else:
+                return default
+        else:
+            self.remove(key, value)
+            return value
+
+    def popitem(self):
+        try:
+            key = next(iter(self))
+        except StopIteration:
+            raise KeyError
+        value = self.pop(key)
+        return (key, value)
+
+    def clear(self):
+        ret = capi.lib.notmuch_message_remove_all_properties(self._ptr(),
+                                                             capi.ffi.NULL)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def getall(self, prefix='', *, exact=False):
+        """Return an iterator yielding all properties for a given key prefix.
+
+        The returned iterator yields all peroperties which start with
+        a given key prefix as ``(key, value)`` namedtuples.  If called
+        with ``exact=True`` then only properties which exactly match
+        the prefix are returned, those a key longer than the prefix
+        will not be included.
+
+        :param prefix: The prefix of the key.
+        """
+        if isinstance(prefix, str):
+            prefix = prefix.encode('utf-8')
+        props_p = capi.lib.notmuch_message_get_properties(self._ptr(),
+                                                          prefix, exact)
+        return PropertiesIter(self, props_p)
+
+
+class PropertiesKeyIter(base.NotmuchIter):
+
+    def __init__(self, parent, iter_p):
+        super().__init__(
+            parent,
+            iter_p,
+            fn_destroy=capi.lib.notmuch_message_properties_destroy,
+            fn_valid=capi.lib.notmuch_message_properties_valid,
+            fn_get=capi.lib.notmuch_message_properties_key,
+            fn_next=capi.lib.notmuch_message_properties_move_to_next)
+
+    def __next__(self):
+        item = super().__next__()
+        return base.BinString.from_cffi(item)
+
+
+class PropertiesIter(base.NotmuchIter):
+
+    def __init__(self, parent, iter_p):
+        super().__init__(
+            parent,
+            iter_p,
+            fn_destroy=capi.lib.notmuch_message_properties_destroy,
+            fn_valid=capi.lib.notmuch_message_properties_valid,
+            fn_get=capi.lib.notmuch_message_properties_key,
+            fn_next=capi.lib.notmuch_message_properties_move_to_next,
+        )
+
+    def __next__(self):
+        if not self._fn_valid(self._iter_p):
+            self._destroy()
+            raise StopIteration
+        key = capi.lib.notmuch_message_properties_key(self._iter_p)
+        value = capi.lib.notmuch_message_properties_value(self._iter_p)
+        capi.lib.notmuch_message_properties_move_to_next(self._iter_p)
+        return PropertiesMap.Property(base.BinString.from_cffi(key),
+                                      base.BinString.from_cffi(value))
+
+
+class PropertiesItemsView(collections.abc.Set):
+
+    __slots__ = ('_items',)
+
+    def __init__(self, items):
+        self._items = items
+
+    @classmethod
+    def _from_iterable(self, it):
+        return set(it)
+
+    def __len__(self):
+        return len(self._items)
+
+    def __contains__(self, item):
+        return item in self._items
+
+    def __iter__(self):
+        yield from self._items
+
+
+collections.abc.ItemsView.register(PropertiesItemsView)
+
+
+class PropertiesValuesView(collections.abc.Set):
+
+    __slots__ = ('_values',)
+
+    def __init__(self, values):
+        self._values = values
+
+    def __len__(self):
+        return len(self._values)
+
+    def __contains__(self, value):
+        return value in self._values
+
+    def __iter__(self):
+        yield from self._values
+
+
+collections.abc.ValuesView.register(PropertiesValuesView)
+
+
+class MessageIter(base.NotmuchIter):
+
+    def __init__(self, parent, msgs_p, *, db, msg_cls=Message):
+        self._db = db
+        self._msg_cls = msg_cls
+        super().__init__(parent, msgs_p,
+                         fn_destroy=capi.lib.notmuch_messages_destroy,
+                         fn_valid=capi.lib.notmuch_messages_valid,
+                         fn_get=capi.lib.notmuch_messages_get,
+                         fn_next=capi.lib.notmuch_messages_move_to_next)
+
+    def __next__(self):
+        msg_p = super().__next__()
+        return self._msg_cls(self, msg_p, db=self._db)
diff --git a/bindings/python-cffi/notmuch2/_query.py b/bindings/python-cffi/notmuch2/_query.py
new file mode 100644 (file)
index 0000000..1db6ec9
--- /dev/null
@@ -0,0 +1,83 @@
+from notmuch2 import _base as base
+from notmuch2 import _capi as capi
+from notmuch2 import _errors as errors
+from notmuch2 import _message as message
+from notmuch2 import _thread as thread
+
+
+__all__ = []
+
+
+class Query(base.NotmuchObject):
+    """Private, minimal query object.
+
+    This is not meant for users and is not a full implementation of
+    the query API.  It is only an intermediate used internally to
+    match libnotmuch's memory management.
+    """
+    _query_p = base.MemoryPointer()
+
+    def __init__(self, db, query_p):
+        self._db = db
+        self._query_p = query_p
+
+    @property
+    def alive(self):
+        if not self._db.alive:
+            return False
+        try:
+            self._query_p
+        except errors.ObjectDestroyedError:
+            return False
+        else:
+            return True
+
+    def __del__(self):
+        self._destroy()
+
+    def _destroy(self):
+        if self.alive:
+            capi.lib.notmuch_query_destroy(self._query_p)
+        self._query_p = None
+
+    @property
+    def query(self):
+        """The query string as seen by libnotmuch."""
+        q = capi.lib.notmuch_query_get_query_string(self._query_p)
+        return base.BinString.from_cffi(q)
+
+    def messages(self):
+        """Return an iterator over all the messages found by the query.
+
+        This executes the query and returns an iterator over the
+        :class:`Message` objects found.
+        """
+        msgs_pp = capi.ffi.new('notmuch_messages_t**')
+        ret = capi.lib.notmuch_query_search_messages(self._query_p, msgs_pp)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        return message.MessageIter(self, msgs_pp[0], db=self._db)
+
+    def count_messages(self):
+        """Return the number of messages matching this query."""
+        count_p = capi.ffi.new('unsigned int *')
+        ret = capi.lib.notmuch_query_count_messages(self._query_p, count_p)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        return count_p[0]
+
+    def threads(self):
+        """Return an iterator over all the threads found by the query."""
+        threads_pp = capi.ffi.new('notmuch_threads_t **')
+        ret = capi.lib.notmuch_query_search_threads(self._query_p, threads_pp)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        return thread.ThreadIter(self, threads_pp[0], db=self._db)
+
+    def count_threads(self):
+        """Return the number of threads matching this query."""
+        count_p = capi.ffi.new('unsigned int *')
+        ret = capi.lib.notmuch_query_count_threads(self._query_p, count_p)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+        return count_p[0]
diff --git a/bindings/python-cffi/notmuch2/_tags.py b/bindings/python-cffi/notmuch2/_tags.py
new file mode 100644 (file)
index 0000000..ee5d2a3
--- /dev/null
@@ -0,0 +1,359 @@
+import collections.abc
+
+import notmuch2._base as base
+import notmuch2._capi as capi
+import notmuch2._errors as errors
+
+
+__all__ = ['ImmutableTagSet', 'MutableTagSet', 'TagsIter']
+
+
+class ImmutableTagSet(base.NotmuchObject, collections.abc.Set):
+    """The tags associated with a message thread or whole database.
+
+    Both a thread as well as the database expose the union of all tags
+    in messages associated with them.  This exposes these as a
+    :class:`collections.abc.Set` object.
+
+    Note that due to the underlying notmuch API the performance of the
+    implementation is not the same as you would expect from normal
+    sets.  E.g. the :meth:`__contains__` and :meth:`__len__` are O(n)
+    rather then O(1).
+
+    Tags are internally stored as bytestrings but normally exposed as
+    unicode strings using the UTF-8 encoding and the *ignore* decoder
+    error handler.  However the :meth:`iter` method can be used to
+    return tags as bytestrings or using a different error handler.
+
+    Note that when doing arithmetic operations on tags, this class
+    will return a plain normal set as it is no longer associated with
+    the message.
+
+    :param parent: the parent object
+    :param ptr_name: the name of the attribute on the parent which will
+       return the memory pointer.  This allows this object to
+       access the pointer via the parent's descriptor and thus
+       trigger :class:`MemoryPointer`'s memory safety.
+    :param cffi_fn: the callable CFFI wrapper to retrieve the tags
+       iter.  This can be one of notmuch_database_get_all_tags,
+       notmuch_thread_get_tags or notmuch_message_get_tags.
+    """
+
+    def __init__(self, parent, ptr_name, cffi_fn):
+        self._parent = parent
+        self._ptr = lambda: getattr(parent, ptr_name)
+        self._cffi_fn = cffi_fn
+
+    def __del__(self):
+        self._destroy()
+
+    @property
+    def alive(self):
+        return self._parent.alive
+
+    def _destroy(self):
+        pass
+
+    @classmethod
+    def _from_iterable(cls, it):
+        return set(it)
+
+    def __iter__(self):
+        """Return an iterator over the tags.
+
+        Tags are yielded as unicode strings, decoded using the
+        "ignore" error handler.
+
+        :raises NullPointerError: If the iterator can not be created.
+        """
+        return self.iter(encoding='utf-8', errors='ignore')
+
+    def iter(self, *, encoding=None, errors='strict'):
+        """Aternate iterator constructor controlling string decoding.
+
+        Tags are stored as bytes in the notmuch database, in Python
+        it's easier to work with unicode strings and thus is what the
+        normal iterator returns.  However this method allows you to
+        specify how you would like to get the tags, defaulting to the
+        bytestring representation instead of unicode strings.
+
+        :param encoding: Which codec to use.  The default *None* does not
+           decode at all and will return the unmodified bytes.
+           Otherwise this is passed on to :func:`str.decode`.
+        :param errors: If using a codec, this is the error handler.
+           See :func:`str.decode` to which this is passed on.
+
+        :raises NullPointerError: When things do not go as planned.
+        """
+        # self._cffi_fn should point either to
+        # notmuch_database_get_all_tags, notmuch_thread_get_tags or
+        # notmuch_message_get_tags.  nothmuch.h suggests these never
+        # fail, let's handle NULL anyway.
+        tags_p = self._cffi_fn(self._ptr())
+        if tags_p == capi.ffi.NULL:
+            raise errors.NullPointerError()
+        tags = TagsIter(self, tags_p, encoding=encoding, errors=errors)
+        return tags
+
+    def __len__(self):
+        return sum(1 for t in self)
+
+    def __contains__(self, tag):
+        if isinstance(tag, str):
+            tag = tag.encode()
+        for msg_tag in self.iter():
+            if tag == msg_tag:
+                return True
+        else:
+            return False
+
+    def __eq__(self, other):
+        return tuple(sorted(self.iter())) == tuple(sorted(other.iter()))
+
+    def issubset(self, other):
+        return self <= other
+
+    def issuperset(self, other):
+        return self >= other
+
+    def union(self, other):
+        return self | other
+
+    def intersection(self, other):
+        return self & other
+
+    def difference(self, other):
+        return self - other
+
+    def symmetric_difference(self, other):
+        return self ^ other
+
+    def copy(self):
+        return set(self)
+
+    def __hash__(self):
+        return hash(tuple(self.iter()))
+
+    def __repr__(self):
+        return '<{name} object at 0x{addr:x} tags={{{tags}}}>'.format(
+            name=self.__class__.__name__,
+            addr=id(self),
+            tags=', '.join(repr(t) for t in self))
+
+
+class MutableTagSet(ImmutableTagSet, collections.abc.MutableSet):
+    """The tags associated with a message.
+
+    This is a :class:`collections.abc.MutableSet` object which can be
+    used to manipulate the tags of a message.
+
+    Note that due to the underlying notmuch API the performance of the
+    implementation is not the same as you would expect from normal
+    sets.  E.g. the ``in`` operator and variants are O(n) rather then
+    O(1).
+
+    Tags are bytestrings and calling ``iter()`` will return an
+    iterator yielding bytestrings.  However the :meth:`iter` method
+    can be used to return tags as unicode strings, while all other
+    operations accept either byestrings or unicode strings.  In case
+    unicode strings are used they will be encoded using utf-8 before
+    being passed to notmuch.
+    """
+
+    # Since we subclass ImmutableTagSet we inherit a __hash__.  But we
+    # are mutable, setting it to None will make the Python machinery
+    # recognise us as unhashable.
+    __hash__ = None
+
+    def add(self, tag):
+        """Add a tag to the message.
+
+        :param tag: The tag to add.
+        :type tag: str or bytes.  A str will be encoded using UTF-8.
+
+        :param sync_flags: Whether to sync the maildir flags with the
+           new set of tags.  Leaving this as *None* respects the
+           configuration set in the database, while *True* will always
+           sync and *False* will never sync.
+        :param sync_flags: NoneType or bool
+
+        :raises TypeError: If the tag is not a valid type.
+        :raises TagTooLongError: If the added tag exceeds the maximum
+           length, see ``notmuch_cffi.NOTMUCH_TAG_MAX``.
+        :raises ReadOnlyDatabaseError: If the database is opened in
+           read-only mode.
+        """
+        if isinstance(tag, str):
+            tag = tag.encode()
+        if not isinstance(tag, bytes):
+            raise TypeError('Not a valid type for a tag: {}'.format(type(tag)))
+        ret = capi.lib.notmuch_message_add_tag(self._ptr(), tag)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def discard(self, tag):
+        """Remove a tag from the message.
+
+        :param tag: The tag to remove.
+        :type tag: str of bytes.  A str will be encoded using UTF-8.
+        :param sync_flags: Whether to sync the maildir flags with the
+           new set of tags.  Leaving this as *None* respects the
+           configuration set in the database, while *True* will always
+           sync and *False* will never sync.
+        :param sync_flags: NoneType or bool
+
+        :raises TypeError: If the tag is not a valid type.
+        :raises TagTooLongError: If the tag exceeds the maximum
+           length, see ``notmuch_cffi.NOTMUCH_TAG_MAX``.
+        :raises ReadOnlyDatabaseError: If the database is opened in
+           read-only mode.
+        """
+        if isinstance(tag, str):
+            tag = tag.encode()
+        if not isinstance(tag, bytes):
+            raise TypeError('Not a valid type for a tag: {}'.format(type(tag)))
+        ret = capi.lib.notmuch_message_remove_tag(self._ptr(), tag)
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def clear(self):
+        """Remove all tags from the message.
+
+        :raises ReadOnlyDatabaseError: If the database is opened in
+           read-only mode.
+        """
+        ret = capi.lib.notmuch_message_remove_all_tags(self._ptr())
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def from_maildir_flags(self):
+        """Update the tags based on the state in the message's maildir flags.
+
+        This function examines the filenames of 'message' for maildir
+        flags, and adds or removes tags on 'message' as follows when
+        these flags are present:
+
+        Flag    Action if present
+        ----    -----------------
+        'D'     Adds the "draft" tag to the message
+        'F'     Adds the "flagged" tag to the message
+        'P'     Adds the "passed" tag to the message
+        'R'     Adds the "replied" tag to the message
+        'S'     Removes the "unread" tag from the message
+
+        For each flag that is not present, the opposite action
+        (add/remove) is performed for the corresponding tags.
+
+        Flags are identified as trailing components of the filename
+        after a sequence of ":2,".
+
+        If there are multiple filenames associated with this message,
+        the flag is considered present if it appears in one or more
+        filenames. (That is, the flags from the multiple filenames are
+        combined with the logical OR operator.)
+        """
+        ret = capi.lib.notmuch_message_maildir_flags_to_tags(self._ptr())
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+    def to_maildir_flags(self):
+        """Update the message's maildir flags based on the notmuch tags.
+
+        If the message's filename is in a maildir directory, that is a
+        directory named ``new`` or ``cur``, and has a valid maildir
+        filename then the flags will be added as such:
+
+        'D' if the message has the "draft" tag
+        'F' if the message has the "flagged" tag
+        'P' if the message has the "passed" tag
+        'R' if the message has the "replied" tag
+        'S' if the message does not have the "unread" tag
+
+        Any existing flags unmentioned in the list above will be
+        preserved in the renaming.
+
+        Also, if this filename is in a directory named "new", rename it to
+        be within the neighboring directory named "cur".
+
+        In case there are multiple files associated with the message
+        all filenames will get the same logic applied.
+        """
+        ret = capi.lib.notmuch_message_tags_to_maildir_flags(self._ptr())
+        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+            raise errors.NotmuchError(ret)
+
+
+class TagsIter(base.NotmuchObject, collections.abc.Iterator):
+    """Iterator over tags.
+
+    This is only an iterator, not a container so calling
+    :meth:`__iter__` does not return a new, replenished iterator but
+    only itself.
+
+    :param parent: The parent object to keep alive.
+    :param tags_p: The CFFI pointer to the C-level tags iterator.
+    :param encoding: Which codec to use.  The default *None* does not
+       decode at all and will return the unmodified bytes.
+       Otherwise this is passed on to :func:`str.decode`.
+    :param errors: If using a codec, this is the error handler.
+       See :func:`str.decode` to which this is passed on.
+
+    :raises ObjectDestroyedError: if used after destroyed.
+    """
+    _tags_p = base.MemoryPointer()
+
+    def __init__(self, parent, tags_p, *, encoding=None, errors='strict'):
+        self._parent = parent
+        self._tags_p = tags_p
+        self._encoding = encoding
+        self._errors = errors
+
+    def __del__(self):
+        self._destroy()
+
+    @property
+    def alive(self):
+        if not self._parent.alive:
+            return False
+        try:
+            self._tags_p
+        except errors.ObjectDestroyedError:
+            return False
+        else:
+            return True
+
+    def _destroy(self):
+        if self.alive:
+            try:
+                capi.lib.notmuch_tags_destroy(self._tags_p)
+            except errors.ObjectDestroyedError:
+                pass
+        self._tags_p = None
+
+    def __iter__(self):
+        """Return the iterator itself.
+
+        Note that as this is an iterator and not a container this will
+        not return a new iterator.  Thus any elements already consumed
+        will not be yielded by the :meth:`__next__` method anymore.
+        """
+        return self
+
+    def __next__(self):
+        if not capi.lib.notmuch_tags_valid(self._tags_p):
+            self._destroy()
+            raise StopIteration()
+        tag_p = capi.lib.notmuch_tags_get(self._tags_p)
+        tag = capi.ffi.string(tag_p)
+        if self._encoding:
+            tag = tag.decode(encoding=self._encoding, errors=self._errors)
+        capi.lib.notmuch_tags_move_to_next(self._tags_p)
+        return tag
+
+    def __repr__(self):
+        try:
+            self._tags_p
+        except errors.ObjectDestroyedError:
+            return '<TagsIter (exhausted)>'
+        else:
+            return '<TagsIter>'
diff --git a/bindings/python-cffi/notmuch2/_thread.py b/bindings/python-cffi/notmuch2/_thread.py
new file mode 100644 (file)
index 0000000..e883f30
--- /dev/null
@@ -0,0 +1,194 @@
+import collections.abc
+import weakref
+
+from notmuch2 import _base as base
+from notmuch2 import _capi as capi
+from notmuch2 import _errors as errors
+from notmuch2 import _message as message
+from notmuch2 import _tags as tags
+
+
+__all__ = ['Thread']
+
+
+class Thread(base.NotmuchObject, collections.abc.Iterable):
+    _thread_p = base.MemoryPointer()
+
+    def __init__(self, parent, thread_p, *, db):
+        self._parent = parent
+        self._thread_p = thread_p
+        self._db = db
+
+    @property
+    def alive(self):
+        if not self._parent.alive:
+            return False
+        try:
+            self._thread_p
+        except errors.ObjectDestroyedError:
+            return False
+        else:
+            return True
+
+    def __del__(self):
+        self._destroy()
+
+    def _destroy(self):
+        if self.alive:
+            capi.lib.notmuch_thread_destroy(self._thread_p)
+        self._thread_p = None
+
+    @property
+    def threadid(self):
+        """The thread ID as a :class:`BinString`.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_thread_get_thread_id(self._thread_p)
+        return base.BinString.from_cffi(ret)
+
+    def __len__(self):
+        """Return the number of messages in the thread.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        return capi.lib.notmuch_thread_get_total_messages(self._thread_p)
+
+    def toplevel(self):
+        """Return an iterator of the toplevel messages.
+
+        :returns: An iterator yielding :class:`Message` instances.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        msgs_p = capi.lib.notmuch_thread_get_toplevel_messages(self._thread_p)
+        return message.MessageIter(self, msgs_p,
+                                   db=self._db,
+                                   msg_cls=message.OwnedMessage)
+
+    def __iter__(self):
+        """Return an iterator over all the messages in the thread.
+
+        :returns: An iterator yielding :class:`Message` instances.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        msgs_p = capi.lib.notmuch_thread_get_messages(self._thread_p)
+        return message.MessageIter(self, msgs_p,
+                                   db=self._db,
+                                   msg_cls=message.OwnedMessage)
+
+    @property
+    def matched(self):
+        """The number of messages in this thread which matched the query.
+
+        Of the messages in the thread this gives the count of messages
+        which did directly match the search query which this thread
+        originates from.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        return capi.lib.notmuch_thread_get_matched_messages(self._thread_p)
+
+    @property
+    def authors(self):
+        """A comma-separated string of all authors in the thread.
+
+        Authors of messages which matched the query the thread was
+        retrieved from will be at the head of the string, ordered by
+        date of their messages.  Following this will be the authors of
+        the other messages in the thread, also ordered by date of
+        their messages.  Both groups of authors are separated by the
+        ``|`` character.
+
+        :returns: The stringified list of authors.
+        :rtype: BinString
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_thread_get_authors(self._thread_p)
+        return base.BinString.from_cffi(ret)
+
+    @property
+    def subject(self):
+        """The subject of the thread, taken from the first message.
+
+        The thread's subject is taken to be the subject of the first
+        message according to query sort order.
+
+        :returns: The thread's subject.
+        :rtype: BinString
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        ret = capi.lib.notmuch_thread_get_subject(self._thread_p)
+        return base.BinString.from_cffi(ret)
+
+    @property
+    def first(self):
+        """Return the date of the oldest message in the thread.
+
+        The time the first message was sent as an integer number of
+        seconds since the *epoch*, 1 Jan 1970.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        return capi.lib.notmuch_thread_get_oldest_date(self._thread_p)
+
+    @property
+    def last(self):
+        """Return the date of the newest message in the thread.
+
+        The time the last message was sent as an integer number of
+        seconds since the *epoch*, 1 Jan 1970.
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        return capi.lib.notmuch_thread_get_newest_date(self._thread_p)
+
+    @property
+    def tags(self):
+        """Return an immutable set with all tags used in this thread.
+
+        This returns an immutable set-like object implementing the
+        collections.abc.Set Abstract Base Class.  Due to the
+        underlying libnotmuch implementation some operations have
+        different performance characteristics then plain set objects.
+        Mainly any lookup operation is O(n) rather then O(1).
+
+        Normal usage treats tags as UTF-8 encoded unicode strings so
+        they are exposed to Python as normal unicode string objects.
+        If you need to handle tags stored in libnotmuch which are not
+        valid unicode do check the :class:`ImmutableTagSet` docs for
+        how to handle this.
+
+        :rtype: ImmutableTagSet
+
+        :raises ObjectDestroyedError: if used after destroyed.
+        """
+        try:
+            ref = self._cached_tagset
+        except AttributeError:
+            tagset = None
+        else:
+            tagset = ref()
+        if tagset is None:
+            tagset = tags.ImmutableTagSet(
+                self, '_thread_p', capi.lib.notmuch_thread_get_tags)
+            self._cached_tagset = weakref.ref(tagset)
+        return tagset
+
+
+class ThreadIter(base.NotmuchIter):
+
+    def __init__(self, parent, threads_p, *, db):
+        self._db = db
+        super().__init__(parent, threads_p,
+                         fn_destroy=capi.lib.notmuch_threads_destroy,
+                         fn_valid=capi.lib.notmuch_threads_valid,
+                         fn_get=capi.lib.notmuch_threads_get,
+                         fn_next=capi.lib.notmuch_threads_move_to_next)
+
+    def __next__(self):
+        thread_p = super().__next__()
+        return Thread(self, thread_p, db=self._db)
diff --git a/bindings/python-cffi/setup.py b/bindings/python-cffi/setup.py
new file mode 100644 (file)
index 0000000..cda5233
--- /dev/null
@@ -0,0 +1,24 @@
+import setuptools
+
+with open('version.txt') as fp:
+    VERSION = fp.read().strip()
+
+setuptools.setup(
+    name='notmuch2',
+    version=VERSION,
+    description='Pythonic bindings for the notmuch mail database using CFFI',
+    author='Floris Bruynooghe',
+    author_email='flub@devork.be',
+    setup_requires=['cffi>=1.0.0'],
+    install_requires=['cffi>=1.0.0'],
+    packages=setuptools.find_packages(exclude=['tests']),
+    cffi_modules=['notmuch2/_build.py:ffibuilder'],
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: GNU General Public License (GPL)',
+        'Programming Language :: Python :: 3',
+        'Topic :: Communications :: Email',
+        'Topic :: Software Development :: Libraries',
+    ],
+)
diff --git a/bindings/python-cffi/tests/conftest.py b/bindings/python-cffi/tests/conftest.py
new file mode 100644 (file)
index 0000000..6835fd3
--- /dev/null
@@ -0,0 +1,149 @@
+import email.message
+import mailbox
+import pathlib
+import shutil
+import socket
+import subprocess
+import textwrap
+import time
+import os
+
+import pytest
+
+
+def pytest_report_header():
+    which = shutil.which('notmuch')
+    vers = subprocess.run(['notmuch', '--version'], stdout=subprocess.PIPE)
+    return ['{} ({})'.format(vers.stdout.decode(errors='replace').strip(),which)]
+
+
+@pytest.fixture(scope='function')
+def tmppath(tmpdir):
+    """The tmpdir fixture wrapped in pathlib.Path."""
+    return pathlib.Path(str(tmpdir))
+
+
+@pytest.fixture
+def notmuch(maildir):
+    """Return a function which runs notmuch commands on our test maildir.
+
+    This uses the notmuch-config file created by the ``maildir``
+    fixture.
+    """
+    def run(*args):
+        """Run a notmuch command.
+
+        This function runs with a timeout error as many notmuch
+        commands may block if multiple processes are trying to open
+        the database in write-mode.  It is all too easy to
+        accidentally do this in the unittests.
+        """
+        cfg_fname = maildir.path / 'notmuch-config'
+        cmd = ['notmuch'] + list(args)
+        env = os.environ.copy()
+        env['NOTMUCH_CONFIG'] = str(cfg_fname)
+        proc = subprocess.run(cmd,
+                              timeout=5,
+                              env=env)
+        proc.check_returncode()
+    return run
+
+
+@pytest.fixture
+def maildir(tmppath):
+    """A basic test interface to a valid maildir directory.
+
+    This creates a valid maildir and provides a simple mechanism to
+    deliver test emails to it.  It also writes a notmuch-config file
+    in the top of the maildir.
+    """
+    cur = tmppath / 'cur'
+    cur.mkdir()
+    new = tmppath / 'new'
+    new.mkdir()
+    tmp = tmppath / 'tmp'
+    tmp.mkdir()
+    cfg_fname = tmppath/'notmuch-config'
+    with cfg_fname.open('w') as fp:
+        fp.write(textwrap.dedent("""\
+            [database]
+            path={tmppath!s}
+            [user]
+            name=Some Hacker
+            primary_email=dst@example.com
+            [new]
+            tags=unread;inbox;
+            ignore=
+            [search]
+            exclude_tags=deleted;spam;
+            [maildir]
+            synchronize_flags=true
+            """.format(tmppath=tmppath)))
+    return MailDir(tmppath)
+
+
+class MailDir:
+    """An interface around a correct maildir."""
+
+    def __init__(self, path):
+        self._path = pathlib.Path(path)
+        self.mailbox = mailbox.Maildir(str(path))
+        self._idcount = 0
+
+    @property
+    def path(self):
+        """The pathname of the maildir."""
+        return self._path
+
+    def _next_msgid(self):
+        """Return a new unique message ID."""
+        msgid = '{}@{}'.format(self._idcount, socket.getfqdn())
+        self._idcount += 1
+        return msgid
+
+    def deliver(self,
+                subject='Test mail',
+                body='This is a test mail',
+                to='dst@example.com',
+                frm='src@example.com',
+                headers=None,
+                new=False,      # Move to new dir or cur dir?
+                keywords=None,  # List of keywords or labels
+                seen=False,     # Seen flag (cur dir only)
+                replied=False,  # Replied flag (cur dir only)
+                flagged=False):  # Flagged flag (cur dir only)
+        """Deliver a new mail message in the mbox.
+
+        This does only adds the message to maildir, does not insert it
+        into the notmuch database.
+
+        :returns: A tuple of (msgid, pathname).
+        """
+        msgid = self._next_msgid()
+        when = time.time()
+        msg = email.message.EmailMessage()
+        msg.add_header('Received', 'by MailDir; {}'.format(time.ctime(when)))
+        msg.add_header('Message-ID', '<{}>'.format(msgid))
+        msg.add_header('Date', time.ctime(when))
+        msg.add_header('From', frm)
+        msg.add_header('To', to)
+        msg.add_header('Subject', subject)
+        if headers:
+            for h, v in headers:
+                msg.add_header(h, v)
+        msg.set_content(body)
+        mdmsg = mailbox.MaildirMessage(msg)
+        if not new:
+            mdmsg.set_subdir('cur')
+        if flagged:
+            mdmsg.add_flag('F')
+        if replied:
+            mdmsg.add_flag('R')
+        if seen:
+            mdmsg.add_flag('S')
+        boxid = self.mailbox.add(mdmsg)
+        basename = boxid
+        if mdmsg.get_info():
+            basename += mailbox.Maildir.colon + mdmsg.get_info()
+        msgpath = self.path / mdmsg.get_subdir() / basename
+        return (msgid, msgpath)
diff --git a/bindings/python-cffi/tests/test_base.py b/bindings/python-cffi/tests/test_base.py
new file mode 100644 (file)
index 0000000..d3280a6
--- /dev/null
@@ -0,0 +1,116 @@
+import pytest
+
+from notmuch2 import _base as base
+from notmuch2 import _errors as errors
+
+
+class TestNotmuchObject:
+
+    def test_no_impl_methods(self):
+        class Object(base.NotmuchObject):
+            pass
+        with pytest.raises(TypeError):
+            Object()
+
+    def test_impl_methods(self):
+
+        class Object(base.NotmuchObject):
+
+            def __init__(self):
+                pass
+
+            @property
+            def alive(self):
+                pass
+
+            def _destroy(self, parent=False):
+                pass
+
+        Object()
+
+    def test_del(self):
+        destroyed = False
+
+        class Object(base.NotmuchObject):
+
+            def __init__(self):
+                pass
+
+            @property
+            def alive(self):
+                pass
+
+            def _destroy(self, parent=False):
+                nonlocal destroyed
+                destroyed = True
+
+        o = Object()
+        o.__del__()
+        assert destroyed
+
+
+class TestMemoryPointer:
+
+    @pytest.fixture
+    def obj(self):
+        class Cls:
+            ptr = base.MemoryPointer()
+        return Cls()
+
+    def test_unset(self, obj):
+        with pytest.raises(errors.ObjectDestroyedError):
+            obj.ptr
+
+    def test_set(self, obj):
+        obj.ptr = 'some'
+        assert obj.ptr == 'some'
+
+    def test_cleared(self, obj):
+        obj.ptr = 'some'
+        obj.ptr
+        obj.ptr = None
+        with pytest.raises(errors.ObjectDestroyedError):
+            obj.ptr
+
+    def test_two_instances(self, obj):
+        obj2 = obj.__class__()
+        obj.ptr = 'foo'
+        obj2.ptr = 'bar'
+        assert obj.ptr != obj2.ptr
+
+
+class TestBinString:
+
+    def test_type(self):
+        s = base.BinString(b'foo')
+        assert isinstance(s, str)
+
+    def test_init_bytes(self):
+        s = base.BinString(b'foo')
+        assert s == 'foo'
+
+    def test_init_str(self):
+        s = base.BinString('foo')
+        assert s == 'foo'
+
+    def test_bytes(self):
+        s = base.BinString(b'foo')
+        assert bytes(s) == b'foo'
+
+    def test_invalid_utf8(self):
+        s = base.BinString(b'\x80foo')
+        assert s == 'foo'
+        assert bytes(s) == b'\x80foo'
+
+    def test_errors(self):
+        s = base.BinString(b'\x80foo', errors='replace')
+        assert s == '�foo'
+        assert bytes(s) == b'\x80foo'
+
+    def test_encoding(self):
+        # pound sign: '£' == '\u00a3' latin-1: b'\xa3', utf-8: b'\xc2\xa3'
+        with pytest.raises(UnicodeDecodeError):
+            base.BinString(b'\xa3', errors='strict')
+        s = base.BinString(b'\xa3', encoding='latin-1', errors='strict')
+        assert s == '£'
+        assert bytes(s) == b'\xa3'
diff --git a/bindings/python-cffi/tests/test_config.py b/bindings/python-cffi/tests/test_config.py
new file mode 100644 (file)
index 0000000..1b2695f
--- /dev/null
@@ -0,0 +1,56 @@
+import collections.abc
+
+import pytest
+
+import notmuch2._database as dbmod
+
+import notmuch2._config as config
+
+
+class TestIter:
+
+    @pytest.fixture
+    def db(self, maildir):
+        with dbmod.Database.create(maildir.path) as db:
+            yield db
+
+    def test_type(self, db):
+        assert isinstance(db.config, collections.abc.MutableMapping)
+        assert isinstance(db.config, config.ConfigMapping)
+
+    def test_alive(self, db):
+        assert db.config.alive
+
+    def test_set_get(self, maildir):
+        # Ensure get-set works from different db objects
+        with dbmod.Database.create(maildir.path) as db0:
+            db0.config['spam'] = 'ham'
+        with dbmod.Database(maildir.path) as db1:
+            assert db1.config['spam'] == 'ham'
+
+    def test_get_keyerror(self, db):
+        with pytest.raises(KeyError):
+            val = db.config['not-a-key']
+            print(repr(val))
+
+    def test_iter(self, db):
+        assert list(db.config) == []
+        db.config['spam'] = 'ham'
+        db.config['eggs'] = 'bacon'
+        assert set(db.config) == {'spam', 'eggs'}
+        assert set(db.config.keys()) == {'spam', 'eggs'}
+        assert set(db.config.values()) == {'ham', 'bacon'}
+        assert set(db.config.items()) == {('spam', 'ham'), ('eggs', 'bacon')}
+
+    def test_len(self, db):
+        assert len(db.config) == 0
+        db.config['spam'] = 'ham'
+        assert len(db.config) == 1
+        db.config['eggs'] = 'bacon'
+        assert len(db.config) == 2
+
+    def test_del(self, db):
+        db.config['spam'] = 'ham'
+        assert db.config.get('spam') == 'ham'
+        del db.config['spam']
+        assert db.config.get('spam') is None
diff --git a/bindings/python-cffi/tests/test_database.py b/bindings/python-cffi/tests/test_database.py
new file mode 100644 (file)
index 0000000..a2c69de
--- /dev/null
@@ -0,0 +1,342 @@
+import collections
+import configparser
+import os
+import pathlib
+
+import pytest
+
+import notmuch2
+import notmuch2._errors as errors
+import notmuch2._database as dbmod
+import notmuch2._message as message
+
+
+@pytest.fixture
+def db(maildir):
+    with dbmod.Database.create(maildir.path) as db:
+        yield db
+
+
+class TestDefaultDb:
+    """Tests for reading the default database.
+
+    The error cases are fairly undefined, some relevant Python error
+    will come out if you give it a bad filename or if the file does
+    not parse correctly.  So we're not testing this too deeply.
+    """
+
+    def test_config_pathname_default(self, monkeypatch):
+        monkeypatch.delenv('NOTMUCH_CONFIG', raising=False)
+        user = pathlib.Path('~/.notmuch-config').expanduser()
+        assert dbmod._config_pathname() == user
+
+    def test_config_pathname_env(self, monkeypatch):
+        monkeypatch.setenv('NOTMUCH_CONFIG', '/some/random/path')
+        assert dbmod._config_pathname() == pathlib.Path('/some/random/path')
+
+    def test_default_path_nocfg(self, monkeypatch, tmppath):
+        monkeypatch.setenv('NOTMUCH_CONFIG', str(tmppath/'foo'))
+        with pytest.raises(FileNotFoundError):
+            dbmod.Database.default_path()
+
+    def test_default_path_cfg_is_dir(self, monkeypatch, tmppath):
+        monkeypatch.setenv('NOTMUCH_CONFIG', str(tmppath))
+        with pytest.raises(IsADirectoryError):
+            dbmod.Database.default_path()
+
+    def test_default_path_parseerr(self, monkeypatch, tmppath):
+        cfg = tmppath / 'notmuch-config'
+        with cfg.open('w') as fp:
+            fp.write('invalid')
+        monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg))
+        with pytest.raises(configparser.Error):
+            dbmod.Database.default_path()
+
+    def test_default_path_parse(self, monkeypatch, tmppath):
+        cfg = tmppath / 'notmuch-config'
+        with cfg.open('w') as fp:
+            fp.write('[database]\n')
+            fp.write('path={!s}'.format(tmppath))
+        monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg))
+        assert dbmod.Database.default_path() == tmppath
+
+    def test_default_path_param(self, monkeypatch, tmppath):
+        cfg_dummy = tmppath / 'dummy'
+        monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg_dummy))
+        cfg_real = tmppath / 'notmuch_config'
+        with cfg_real.open('w') as fp:
+            fp.write('[database]\n')
+            fp.write('path={!s}'.format(cfg_real/'mail'))
+        assert dbmod.Database.default_path(cfg_real) == cfg_real/'mail'
+
+
+class TestCreate:
+
+    def test_create(self, tmppath, db):
+        assert tmppath.joinpath('.notmuch/xapian/').exists()
+
+    def test_create_already_open(self, tmppath, db):
+        with pytest.raises(errors.NotmuchError):
+            db.create(tmppath)
+
+    def test_create_existing(self, tmppath, db):
+        with pytest.raises(errors.FileError):
+            dbmod.Database.create(path=tmppath)
+
+    def test_close(self, db):
+        db.close()
+
+    def test_del_noclose(self, db):
+        del db
+
+    def test_close_del(self, db):
+        db.close()
+        del db
+
+    def test_closed_attr(self, db):
+        assert not db.closed
+        db.close()
+        assert db.closed
+
+    def test_ctx(self, db):
+        with db as ctx:
+            assert ctx is db
+            assert not db.closed
+        assert db.closed
+
+    def test_path(self, db, tmppath):
+        assert db.path == tmppath
+
+    def test_version(self, db):
+        assert db.version > 0
+
+    def test_needs_upgrade(self, db):
+        assert db.needs_upgrade in (True, False)
+
+
+class TestAtomic:
+
+    def test_exit_early(self, db):
+        with pytest.raises(errors.UnbalancedAtomicError):
+            with db.atomic() as ctx:
+                ctx.force_end()
+
+    def test_exit_late(self, db):
+        with db.atomic() as ctx:
+            pass
+        with pytest.raises(errors.UnbalancedAtomicError):
+            ctx.force_end()
+
+    def test_abort(self, db):
+        with db.atomic() as txn:
+            txn.abort()
+        assert db.closed
+
+
+class TestRevision:
+
+    def test_single_rev(self, db):
+        r = db.revision()
+        assert isinstance(r, dbmod.DbRevision)
+        assert isinstance(r.rev, int)
+        assert isinstance(r.uuid, bytes)
+        assert r is r
+        assert r == r
+        assert r <= r
+        assert r >= r
+        assert not r < r
+        assert not r > r
+
+    def test_diff_db(self, tmppath):
+        dbpath0 = tmppath.joinpath('db0')
+        dbpath0.mkdir()
+        dbpath1 = tmppath.joinpath('db1')
+        dbpath1.mkdir()
+        db0 = dbmod.Database.create(path=dbpath0)
+        db1 = dbmod.Database.create(path=dbpath1)
+        r_db0 = db0.revision()
+        r_db1 = db1.revision()
+        assert r_db0 != r_db1
+        assert r_db0.uuid != r_db1.uuid
+
+    def test_cmp(self, db, maildir):
+        rev0 = db.revision()
+        _, pathname = maildir.deliver()
+        db.add(pathname, sync_flags=False)
+        rev1 = db.revision()
+        assert rev0 < rev1
+        assert rev0 <= rev1
+        assert not rev0 > rev1
+        assert not rev0 >= rev1
+        assert not rev0 == rev1
+        assert rev0 != rev1
+
+    # XXX add tests for revisions comparisons
+
+class TestMessages:
+
+    def test_add_message(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        msg, dup = db.add(pathname, sync_flags=False)
+        assert isinstance(msg, message.Message)
+        assert msg.path == pathname
+        assert msg.messageid == msgid
+
+    def test_add_message_str(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        msg, dup = db.add(str(pathname), sync_flags=False)
+
+    def test_add_message_bytes(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        msg, dup = db.add(os.fsencode(bytes(pathname)), sync_flags=False)
+
+    def test_remove_message(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        msg, dup = db.add(pathname, sync_flags=False)
+        assert db.find(msgid)
+        dup = db.remove(pathname)
+        with pytest.raises(LookupError):
+            db.find(msgid)
+
+    def test_remove_message_str(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        msg, dup = db.add(pathname, sync_flags=False)
+        assert db.find(msgid)
+        dup = db.remove(str(pathname))
+        with pytest.raises(LookupError):
+            db.find(msgid)
+
+    def test_remove_message_bytes(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        msg, dup = db.add(pathname, sync_flags=False)
+        assert db.find(msgid)
+        dup = db.remove(os.fsencode(bytes(pathname)))
+        with pytest.raises(LookupError):
+            db.find(msgid)
+
+    def test_find_message(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        msg0, dup = db.add(pathname, sync_flags=False)
+        msg1 = db.find(msgid)
+        assert isinstance(msg1, message.Message)
+        assert msg1.messageid == msgid == msg0.messageid
+        assert msg1.path == pathname == msg0.path
+
+    def test_find_message_notfound(self, db):
+        with pytest.raises(LookupError):
+            db.find('foo')
+
+    def test_get_message(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        msg0, _ = db.add(pathname, sync_flags=False)
+        msg1 = db.get(pathname)
+        assert isinstance(msg1, message.Message)
+        assert msg1.messageid == msgid == msg0.messageid
+        assert msg1.path == pathname == msg0.path
+
+    def test_get_message_str(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        db.add(pathname, sync_flags=False)
+        msg = db.get(str(pathname))
+        assert msg.messageid == msgid
+
+    def test_get_message_bytes(self, db, maildir):
+        msgid, pathname = maildir.deliver()
+        db.add(pathname, sync_flags=False)
+        msg = db.get(os.fsencode(bytes(pathname)))
+        assert msg.messageid == msgid
+
+
+class TestTags:
+    # We just want to test this behaves like a set at a hight level.
+    # The set semantics are tested in detail in the test_tags module.
+
+    def test_type(self, db):
+        assert isinstance(db.tags, collections.abc.Set)
+
+    def test_none(self, db):
+        itags = iter(db.tags)
+        with pytest.raises(StopIteration):
+            next(itags)
+        assert len(db.tags) == 0
+        assert not db.tags
+
+    def test_some(self, db, maildir):
+        _, pathname = maildir.deliver()
+        msg, _ = db.add(pathname, sync_flags=False)
+        msg.tags.add('hello')
+        itags = iter(db.tags)
+        assert next(itags) == 'hello'
+        with pytest.raises(StopIteration):
+            next(itags)
+        assert 'hello' in msg.tags
+
+    def test_cache(self, db):
+        assert db.tags is db.tags
+
+    def test_iters(self, db):
+        i1 = iter(db.tags)
+        i2 = iter(db.tags)
+        assert i1 is not i2
+
+
+class TestQuery:
+
+    @pytest.fixture
+    def db(self, maildir, notmuch):
+        """Return a read-only notmuch2.Database.
+
+        The database will have 3 messages, 2 threads.
+        """
+        msgid, _ = maildir.deliver(body='foo')
+        maildir.deliver(body='bar')
+        maildir.deliver(body='baz',
+                        headers=[('In-Reply-To', '<{}>'.format(msgid))])
+        notmuch('new')
+        with dbmod.Database(maildir.path, 'rw') as db:
+            yield db
+
+    def test_count_messages(self, db):
+        assert db.count_messages('*') == 3
+
+    def test_messages_type(self, db):
+        msgs = db.messages('*')
+        assert isinstance(msgs, collections.abc.Iterator)
+
+    def test_message_no_results(self, db):
+        msgs = db.messages('not_a_matching_query')
+        with pytest.raises(StopIteration):
+            next(msgs)
+
+    def test_message_match(self, db):
+        msgs = db.messages('*')
+        msg = next(msgs)
+        assert isinstance(msg, notmuch2.Message)
+
+    def test_count_threads(self, db):
+        assert db.count_threads('*') == 2
+
+    def test_threads_type(self, db):
+        threads = db.threads('*')
+        assert isinstance(threads, collections.abc.Iterator)
+
+    def test_threads_no_match(self, db):
+        threads = db.threads('not_a_matching_query')
+        with pytest.raises(StopIteration):
+            next(threads)
+
+    def test_threads_match(self, db):
+        threads = db.threads('*')
+        thread = next(threads)
+        assert isinstance(thread, notmuch2.Thread)
+
+    def test_use_threaded_message_twice(self, db):
+        thread = next(db.threads('*'))
+        for msg in thread.toplevel():
+            assert isinstance(msg, notmuch2.Message)
+            assert msg.alive
+            del msg
+        for msg in thread:
+            assert isinstance(msg, notmuch2.Message)
+            assert msg.alive
+            del msg
diff --git a/bindings/python-cffi/tests/test_message.py b/bindings/python-cffi/tests/test_message.py
new file mode 100644 (file)
index 0000000..532bf92
--- /dev/null
@@ -0,0 +1,226 @@
+import collections.abc
+import time
+import pathlib
+
+import pytest
+
+import notmuch2
+
+
+class TestMessage:
+    MaildirMsg = collections.namedtuple('MaildirMsg', ['msgid', 'path'])
+
+    @pytest.fixture
+    def maildir_msg(self, maildir):
+        msgid, path = maildir.deliver()
+        return self.MaildirMsg(msgid, path)
+
+    @pytest.fixture
+    def db(self, maildir):
+        with notmuch2.Database.create(maildir.path) as db:
+            yield db
+
+    @pytest.fixture
+    def msg(self, db, maildir_msg):
+        msg, dup = db.add(maildir_msg.path, sync_flags=False)
+        yield msg
+
+    def test_type(self, msg):
+        assert isinstance(msg, notmuch2.NotmuchObject)
+        assert isinstance(msg, notmuch2.Message)
+
+    def test_alive(self, msg):
+        assert msg.alive
+
+    def test_hash(self, msg):
+        assert hash(msg)
+
+    def test_eq(self, db, msg):
+        copy = db.get(msg.path)
+        assert msg == copy
+
+    def test_messageid_type(self, msg):
+        assert isinstance(msg.messageid, str)
+        assert isinstance(msg.messageid, notmuch2.BinString)
+        assert isinstance(bytes(msg.messageid), bytes)
+
+    def test_messageid(self, msg, maildir_msg):
+        assert msg.messageid == maildir_msg.msgid
+
+    def test_messageid_find(self, db, msg):
+        copy = db.find(msg.messageid)
+        assert msg.messageid == copy.messageid
+
+    def test_threadid_type(self, msg):
+        assert isinstance(msg.threadid, str)
+        assert isinstance(msg.threadid, notmuch2.BinString)
+        assert isinstance(bytes(msg.threadid), bytes)
+
+    def test_path_type(self, msg):
+        assert isinstance(msg.path, pathlib.Path)
+
+    def test_path(self, msg, maildir_msg):
+        assert msg.path == maildir_msg.path
+
+    def test_pathb_type(self, msg):
+        assert isinstance(msg.pathb, bytes)
+
+    def test_pathb(self, msg, maildir_msg):
+        assert msg.path == maildir_msg.path
+
+    def test_filenames_type(self, msg):
+        ifn = msg.filenames()
+        assert isinstance(ifn, collections.abc.Iterator)
+
+    def test_filenames(self, msg):
+        ifn = msg.filenames()
+        fn = next(ifn)
+        assert fn == msg.path
+        assert isinstance(fn, pathlib.Path)
+        with pytest.raises(StopIteration):
+            next(ifn)
+        assert list(msg.filenames()) == [msg.path]
+
+    def test_filenamesb_type(self, msg):
+        ifn = msg.filenamesb()
+        assert isinstance(ifn, collections.abc.Iterator)
+
+    def test_filenamesb(self, msg):
+        ifn = msg.filenamesb()
+        fn = next(ifn)
+        assert fn == msg.pathb
+        assert isinstance(fn, bytes)
+        with pytest.raises(StopIteration):
+            next(ifn)
+        assert list(msg.filenamesb()) == [msg.pathb]
+
+    def test_ghost_no(self, msg):
+        assert not msg.ghost
+
+    def test_date(self, msg):
+        # XXX Someone seems to treat things as local time instead of
+        #     UTC or the other way around.
+        now = int(time.time())
+        assert abs(now - msg.date) < 3600*24
+
+    def test_header(self, msg):
+        assert msg.header('from') == 'src@example.com'
+
+    def test_header_not_present(self, msg):
+        with pytest.raises(LookupError):
+            msg.header('foo')
+
+    def test_freeze(self, msg):
+        with msg.frozen():
+            msg.tags.add('foo')
+            msg.tags.add('bar')
+            msg.tags.discard('foo')
+        assert 'foo' not in msg.tags
+        assert 'bar' in msg.tags
+
+    def test_freeze_err(self, msg):
+        msg.tags.add('foo')
+        try:
+            with msg.frozen():
+                msg.tags.clear()
+                raise Exception('oops')
+        except Exception:
+            assert 'foo' in msg.tags
+        else:
+            pytest.fail('Context manager did not raise')
+
+    def test_replies_type(self, msg):
+        assert isinstance(msg.replies(), collections.abc.Iterator)
+
+    def test_replies(self, msg):
+        with pytest.raises(StopIteration):
+            next(msg.replies())
+
+
+class TestProperties:
+
+    @pytest.fixture
+    def props(self, maildir):
+        msgid, path = maildir.deliver()
+        with notmuch2.Database.create(maildir.path) as db:
+            msg, dup = db.add(path, sync_flags=False)
+            yield msg.properties
+
+    def test_type(self, props):
+        assert isinstance(props, collections.abc.MutableMapping)
+
+    def test_add_single(self, props):
+        props['foo'] = 'bar'
+        assert props['foo'] == 'bar'
+        props.add('bar', 'baz')
+        assert props['bar'] == 'baz'
+
+    def test_add_dup(self, props):
+        props.add('foo', 'bar')
+        props.add('foo', 'baz')
+        assert props['foo'] == 'bar'
+        assert (set(props.getall('foo', exact=True))
+                == {('foo', 'bar'), ('foo', 'baz')})
+
+    def test_len(self, props):
+        props.add('foo', 'a')
+        props.add('foo', 'b')
+        props.add('bar', 'a')
+        assert len(props) == 3
+        assert len(props.keys()) == 2
+        assert len(props.values()) == 2
+        assert len(props.items()) == 3
+
+    def test_del(self, props):
+        props.add('foo', 'a')
+        props.add('foo', 'b')
+        del props['foo']
+        with pytest.raises(KeyError):
+            props['foo']
+
+    def test_remove(self, props):
+        props.add('foo', 'a')
+        props.add('foo', 'b')
+        props.remove('foo', 'a')
+        assert props['foo'] == 'b'
+
+    def test_view_abcs(self, props):
+        assert isinstance(props.keys(), collections.abc.KeysView)
+        assert isinstance(props.values(), collections.abc.ValuesView)
+        assert isinstance(props.items(), collections.abc.ItemsView)
+
+    def test_pop(self, props):
+        props.add('foo', 'a')
+        props.add('foo', 'b')
+        val = props.pop('foo')
+        assert val == 'a'
+
+    def test_pop_default(self, props):
+        with pytest.raises(KeyError):
+            props.pop('foo')
+        assert props.pop('foo', 'default') == 'default'
+
+    def test_popitem(self, props):
+        props.add('foo', 'a')
+        assert props.popitem() == ('foo', 'a')
+        with pytest.raises(KeyError):
+            props.popitem()
+
+    def test_clear(self, props):
+        props.add('foo', 'a')
+        props.clear()
+        assert len(props) == 0
+
+    def test_getall(self, props):
+        props.add('foo', 'a')
+        assert set(props.getall('foo')) == {('foo', 'a')}
+
+    def test_getall_prefix(self, props):
+        props.add('foo', 'a')
+        props.add('foobar', 'b')
+        assert set(props.getall('foo')) == {('foo', 'a'), ('foobar', 'b')}
+
+    def test_getall_exact(self, props):
+        props.add('foo', 'a')
+        props.add('foobar', 'b')
+        assert set(props.getall('foo', exact=True)) == {('foo', 'a')}
diff --git a/bindings/python-cffi/tests/test_tags.py b/bindings/python-cffi/tests/test_tags.py
new file mode 100644 (file)
index 0000000..faf3947
--- /dev/null
@@ -0,0 +1,239 @@
+"""Tests for the behaviour of immutable and mutable tagsets.
+
+This module tests the Pythonic behaviour of the sets.
+"""
+
+import collections
+import subprocess
+import textwrap
+
+import pytest
+
+from notmuch2 import _database as database
+from notmuch2 import _tags as tags
+
+
+class TestImmutable:
+
+    @pytest.fixture
+    def tagset(self, maildir, notmuch):
+        """An non-empty immutable tagset.
+
+        This will have the default new mail tags: inbox, unread.
+        """
+        maildir.deliver()
+        notmuch('new')
+        with database.Database(maildir.path) as db:
+            yield db.tags
+
+    def test_type(self, tagset):
+        assert isinstance(tagset, tags.ImmutableTagSet)
+        assert isinstance(tagset, collections.abc.Set)
+
+    def test_hash(self, tagset, maildir, notmuch):
+        h0 = hash(tagset)
+        notmuch('tag', '+foo', '*')
+        with database.Database(maildir.path) as db:
+            h1 = hash(db.tags)
+        assert h0 != h1
+
+    def test_eq(self, tagset):
+        assert tagset == tagset
+
+    def test_neq(self, tagset, maildir, notmuch):
+        notmuch('tag', '+foo', '*')
+        with database.Database(maildir.path) as db:
+            assert tagset != db.tags
+
+    def test_contains(self, tagset):
+        print(tuple(tagset))
+        assert 'unread' in tagset
+        assert 'foo' not in tagset
+
+    def test_isdisjoint(self, tagset):
+        assert tagset.isdisjoint(set(['spam', 'ham']))
+        assert not tagset.isdisjoint(set(['inbox']))
+
+    def test_issubset(self, tagset):
+        assert {'inbox'} <= tagset
+        assert {'inbox'}.issubset(tagset)
+        assert tagset <= {'inbox', 'unread', 'spam'}
+        assert tagset.issubset({'inbox', 'unread', 'spam'})
+
+    def test_issuperset(self, tagset):
+        assert {'inbox', 'unread', 'spam'} >= tagset
+        assert {'inbox', 'unread', 'spam'}.issuperset(tagset)
+        assert tagset >= {'inbox'}
+        assert tagset.issuperset({'inbox'})
+
+    def test_iter(self, tagset):
+        expected = sorted(['unread', 'inbox'])
+        found = []
+        for tag in tagset:
+            assert isinstance(tag, str)
+            found.append(tag)
+        assert expected == sorted(found)
+
+    def test_special_iter(self, tagset):
+        expected = sorted([b'unread', b'inbox'])
+        found = []
+        for tag in tagset.iter():
+            assert isinstance(tag, bytes)
+            found.append(tag)
+        assert expected == sorted(found)
+
+    def test_special_iter_codec(self, tagset):
+        for tag in tagset.iter(encoding='ascii', errors='surrogateescape'):
+            assert isinstance(tag, str)
+
+    def test_len(self, tagset):
+        assert len(tagset) == 2
+
+    def test_and(self, tagset):
+        common = tagset & {'unread'}
+        assert isinstance(common, set)
+        assert isinstance(common, collections.abc.Set)
+        assert common == {'unread'}
+        common = tagset.intersection({'unread'})
+        assert isinstance(common, set)
+        assert isinstance(common, collections.abc.Set)
+        assert common == {'unread'}
+
+    def test_or(self, tagset):
+        res = tagset | {'foo'}
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'unread', 'inbox', 'foo'}
+        res = tagset.union({'foo'})
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'unread', 'inbox', 'foo'}
+
+    def test_sub(self, tagset):
+        res = tagset - {'unread'}
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'inbox'}
+        res = tagset.difference({'unread'})
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'inbox'}
+
+    def test_rsub(self, tagset):
+        res = {'foo', 'unread'} - tagset
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'foo'}
+
+    def test_xor(self, tagset):
+        res = tagset ^ {'unread', 'foo'}
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'inbox', 'foo'}
+        res = tagset.symmetric_difference({'unread', 'foo'})
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'inbox', 'foo'}
+
+    def test_rxor(self, tagset):
+        res = {'unread', 'foo'} ^ tagset
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'inbox', 'foo'}
+
+    def test_copy(self, tagset):
+        res = tagset.copy()
+        assert isinstance(res, set)
+        assert isinstance(res, collections.abc.Set)
+        assert res == {'inbox', 'unread'}
+
+
+class TestMutableTagset:
+
+    @pytest.fixture
+    def tagset(self, maildir, notmuch):
+        """An non-empty mutable tagset.
+
+        This will have the default new mail tags: inbox, unread.
+        """
+        _, pathname = maildir.deliver()
+        notmuch('new')
+        with database.Database(maildir.path,
+                               mode=database.Mode.READ_WRITE) as db:
+            msg = db.get(pathname)
+            yield msg.tags
+
+    def test_type(self, tagset):
+        assert isinstance(tagset, collections.abc.MutableSet)
+        assert isinstance(tagset, tags.MutableTagSet)
+
+    def test_hash(self, tagset):
+        assert not isinstance(tagset, collections.abc.Hashable)
+        with pytest.raises(TypeError):
+            hash(tagset)
+
+    def test_add(self, tagset):
+        assert 'foo' not in tagset
+        tagset.add('foo')
+        assert 'foo' in tagset
+
+    def test_discard(self, tagset):
+        assert 'inbox' in tagset
+        tagset.discard('inbox')
+        assert 'inbox' not in tagset
+
+    def test_discard_not_present(self, tagset):
+        assert 'foo' not in tagset
+        tagset.discard('foo')
+
+    def test_clear(self, tagset):
+        assert len(tagset) > 0
+        tagset.clear()
+        assert len(tagset) == 0
+
+    def test_from_maildir_flags(self, maildir, notmuch):
+        _, pathname = maildir.deliver(flagged=True)
+        notmuch('new')
+        with database.Database(maildir.path,
+                               mode=database.Mode.READ_WRITE) as db:
+            msg = db.get(pathname)
+            msg.tags.discard('flagged')
+            msg.tags.from_maildir_flags()
+            assert 'flagged' in msg.tags
+
+    def test_to_maildir_flags(self, maildir, notmuch):
+        _, pathname = maildir.deliver(flagged=True)
+        notmuch('new')
+        with database.Database(maildir.path,
+                               mode=database.Mode.READ_WRITE) as db:
+            msg = db.get(pathname)
+            flags = msg.path.name.split(',')[-1]
+            assert 'F' in flags
+            msg.tags.discard('flagged')
+            msg.tags.to_maildir_flags()
+            flags = msg.path.name.split(',')[-1]
+            assert 'F' not in flags
+
+    def test_isdisjoint(self, tagset):
+        assert tagset.isdisjoint(set(['spam', 'ham']))
+        assert not tagset.isdisjoint(set(['inbox']))
+
+    def test_issubset(self, tagset):
+        assert {'inbox'} <= tagset
+        assert {'inbox'}.issubset(tagset)
+        assert not {'spam'} <= tagset
+        assert not {'spam'}.issubset(tagset)
+        assert tagset <= {'inbox', 'unread', 'spam'}
+        assert tagset.issubset({'inbox', 'unread', 'spam'})
+        assert not {'inbox', 'unread', 'spam'} <= tagset
+        assert not {'inbox', 'unread', 'spam'}.issubset(tagset)
+
+    def test_issuperset(self, tagset):
+        assert {'inbox', 'unread', 'spam'} >= tagset
+        assert {'inbox', 'unread', 'spam'}.issuperset(tagset)
+        assert tagset >= {'inbox'}
+        assert tagset.issuperset({'inbox'})
+
+    def test_union(self, tagset):
+        assert {'spam'}.union(tagset) == {'inbox', 'unread', 'spam'}
+        assert tagset.union({'spam'}) == {'inbox', 'unread', 'spam'}
diff --git a/bindings/python-cffi/tests/test_thread.py b/bindings/python-cffi/tests/test_thread.py
new file mode 100644 (file)
index 0000000..1f44b35
--- /dev/null
@@ -0,0 +1,102 @@
+import collections.abc
+import time
+
+import pytest
+
+import notmuch2
+
+
+@pytest.fixture
+def thread(maildir, notmuch):
+    """Return a single thread with one matched message."""
+    msgid, _ = maildir.deliver(body='foo')
+    maildir.deliver(body='bar',
+                    headers=[('In-Reply-To', '<{}>'.format(msgid))])
+    notmuch('new')
+    with notmuch2.Database(maildir.path) as db:
+        yield next(db.threads('foo'))
+
+
+def test_type(thread):
+    assert isinstance(thread, notmuch2.Thread)
+    assert isinstance(thread, collections.abc.Iterable)
+
+
+def test_threadid(thread):
+    assert isinstance(thread.threadid, notmuch2.BinString)
+    assert thread.threadid
+
+
+def test_len(thread):
+    assert len(thread) == 2
+
+
+def test_toplevel_type(thread):
+    assert isinstance(thread.toplevel(), collections.abc.Iterator)
+
+
+def test_toplevel(thread):
+    msgs = thread.toplevel()
+    assert isinstance(next(msgs), notmuch2.Message)
+    with pytest.raises(StopIteration):
+        next(msgs)
+
+
+def test_toplevel_reply(thread):
+    msg = next(thread.toplevel())
+    assert isinstance(next(msg.replies()), notmuch2.Message)
+
+
+def test_iter(thread):
+    msgs = list(iter(thread))
+    assert len(msgs) == len(thread)
+    for msg in msgs:
+        assert isinstance(msg, notmuch2.Message)
+
+
+def test_matched(thread):
+    assert thread.matched == 1
+
+
+def test_authors_type(thread):
+    assert isinstance(thread.authors, notmuch2.BinString)
+
+
+def test_authors(thread):
+    assert thread.authors == 'src@example.com'
+
+
+def test_subject(thread):
+    assert thread.subject == 'Test mail'
+
+
+def test_first(thread):
+    # XXX Someone seems to treat things as local time instead of
+    #     UTC or the other way around.
+    now = int(time.time())
+    assert abs(now - thread.first) < 3600*24
+
+
+def test_last(thread):
+    # XXX Someone seems to treat things as local time instead of
+    #     UTC or the other way around.
+    now = int(time.time())
+    assert abs(now - thread.last) < 3600*24
+
+
+def test_first_last(thread):
+    # Sadly we only have second resolution so these will always be the
+    # same time in our tests.
+    assert thread.first <= thread.last
+
+
+def test_tags_type(thread):
+    assert isinstance(thread.tags, notmuch2.ImmutableTagSet)
+
+
+def test_tags_cache(thread):
+    assert thread.tags is thread.tags
+
+
+def test_tags(thread):
+    assert 'inbox' in thread.tags
diff --git a/bindings/python-cffi/tox.ini b/bindings/python-cffi/tox.ini
new file mode 100644 (file)
index 0000000..7cf93be
--- /dev/null
@@ -0,0 +1,19 @@
+[pytest]
+minversion = 3.0
+addopts = -ra --cov=notmuch2 --cov=tests
+
+[tox]
+envlist = py35,py36,py37,py38,pypy35,pypy36
+
+[testenv]
+deps =
+     cffi
+     pytest
+     pytest-cov
+commands = pytest --cov={envsitepackagesdir}/notmuch2 {posargs}
+
+[testenv:pypy35]
+basepython = pypy3.5
+
+[testenv:pypy36]
+basepython = pypy3.6
diff --git a/bindings/python-cffi/version.txt b/bindings/python-cffi/version.txt
new file mode 100644 (file)
index 0000000..c415e1c
--- /dev/null
@@ -0,0 +1 @@
+0.31.2
index 88ca836e2d1aa4c78471ede28f7541fac630aafa..8fb507fafc1bfad1b246775470129061e2a1ab0c 100644 (file)
@@ -65,7 +65,7 @@ class Database(object):
     .. note::
 
         Any function in this class can and will throw an
-        :exc:`NotInitializedError` if the database was not intitialized
+        :exc:`NotInitializedError` if the database was not initialized
         properly.
     """
     _std_db_path = None
@@ -273,9 +273,9 @@ class Database(object):
         return Database._get_version(self._db)
 
     def get_revision (self):
-        """Returns the committed database revison and UUID
+        """Returns the committed database revision and UUID
 
-        :returns: (revison, uuid) The database revision as a positive integer
+        :returns: (revision, uuid) The database revision as a positive integer
         and the UUID of the database.
         """
         self._assert_db_is_initialized()
@@ -574,7 +574,7 @@ class Database(object):
                   in the meantime. In this case, you should close and
                   reopen the database and retry.
             :exc:`NotInitializedError` if
-                    the database was not intitialized.
+                    the database was not initialized.
         """
         self._assert_db_is_initialized()
         msg_p = NotmuchMessageP()
@@ -600,7 +600,7 @@ class Database(object):
                  case, you should close and reopen the database and
                  retry.
         :raises: :exc:`NotInitializedError` if the database was not
-                 intitialized.
+                 initialized.
 
         *Added in notmuch 0.9*"""
         self._assert_db_is_initialized()
@@ -616,7 +616,7 @@ class Database(object):
         """Returns :class:`Tags` with a list of all tags found in the database
 
         :returns: :class:`Tags`
-        :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
+        :exception: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
                     on error
         """
         self._assert_db_is_initialized()
index 6e32b5f7ada2d825d4cd6c23403f6b6b1271691f..e71dbe3e9933011d334d3ed18990d6fa2844693d 100644 (file)
@@ -46,7 +46,7 @@ import sys
 
 
 class Message(Python3StringMixIn):
-    """Represents a single Email message
+    r"""Represents a single Email message
 
     Technically, this wraps the underlying *notmuch_message_t*
     structure. A user will usually not create these objects themselves
index cae5da508f353f12cca585cb056c0b9ed92e29b3..3801c6664c2d0eaafaf9eaf8cddfea95f60addce 100644 (file)
@@ -32,7 +32,7 @@ from .tag import Tags
 from .message import Message
 
 class Messages(object):
-    """Represents a list of notmuch messages
+    r"""Represents a list of notmuch messages
 
     This object provides an iterator over a list of notmuch messages
     (Technically, it provides a wrapper for the underlying
index cc70e2aa3acc937fcc43d6a577a86337305f8b90..ffb86df1ed3c5ec444f5aef9f4a772cdc301c9eb 100644 (file)
@@ -95,7 +95,7 @@ class Query(object):
             :exc:`NullPointerError` if the query creation failed
                 (e.g. too little memory).
             :exc:`NotInitializedError` if the underlying db was not
-                intitialized.
+                initialized.
         """
         db._assert_db_is_initialized()
         # create reference to parent db to keep it alive
@@ -140,7 +140,7 @@ class Query(object):
     _search_threads.restype = c_uint
 
     def search_threads(self):
-        """Execute a query for threads
+        r"""Execute a query for threads
 
         Execute a query for threads, returning a :class:`Threads` iterator.
         The returned threads are owned by the query and as such, will only be
index e688b56520f7a4cef5ac998820a005a0417a6b7f..0d8fdd0472fd54ba397fe0ea5fe1f887eb563c4e 100644 (file)
@@ -1,3 +1,3 @@
 # this file should be kept in sync with ../../../version
-__VERSION__ = '0.29.3'
+__VERSION__ = '0.31.2'
 SOVERSION = '5'
index c55cf6e20d11b8faf3e10d979ad43a79952499db..6ea82afa20c5071086883b437054d80613e7a45d 100644 (file)
@@ -137,13 +137,18 @@ VALUE
 notmuch_rb_message_get_flag (VALUE self, VALUE flagv)
 {
     notmuch_message_t *message;
+    notmuch_bool_t is_set;
+    notmuch_status_t status;
 
     Data_Get_Notmuch_Message (self, message);
 
     if (!FIXNUM_P (flagv))
        rb_raise (rb_eTypeError, "Flag not a Fixnum");
 
-    return notmuch_message_get_flag (message, FIX2INT (flagv)) ? Qtrue : Qfalse;
+    status = notmuch_message_get_flag_st (message, FIX2INT (flagv), &is_set);
+    notmuch_rb_status_raise (status);
+
+    return is_set ? Qtrue : Qfalse;
 }
 
 /*
index d64aa85bbb3f2a390b072f6eeccbce3177630ccb..169b12a36b7be4edd5058626abc86fe6c98e6611 100644 (file)
@@ -5,16 +5,16 @@
 #include "command-line-arguments.h"
 
 typedef enum {
-    OPT_FAILED, /* false */
-    OPT_OK, /* good */
-    OPT_GIVEBACK, /* pop one of the arguments you thought you were getting off the stack */
+    OPT_FAILED,         /* false */
+    OPT_OK,             /* good */
+    OPT_GIVEBACK,       /* pop one of the arguments you thought you were getting off the stack */
 } opt_handled;
 
 /*
-  Search the array of keywords for a given argument, assigning the
-  output variable to the corresponding value.  Return false if nothing
-  matches.
-*/
* Search the array of keywords for a given argument, assigning the
* output variable to the corresponding value.  Return false if nothing
* matches.
+ */
 
 static opt_handled
 _process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next,
@@ -78,15 +78,17 @@ _process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next,
        return OPT_FAILED;
     }
 
-    *arg_desc->opt_bool = negate ? !value : value;
+    *arg_desc->opt_bool = negate ? (! value) : value;
 
     return OPT_OK;
 }
 
 static opt_handled
-_process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
+_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 OPT_FAILED;
@@ -102,7 +104,8 @@ _process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg
 }
 
 static opt_handled
-_process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
+_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);
@@ -117,20 +120,22 @@ _process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *
 }
 
 /* Return number of non-NULL opt_* fields in opt_desc. */
-static int _opt_set_count (const notmuch_opt_desc_t *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;
+       (bool) opt_desc->opt_inherit +
+       (bool) opt_desc->opt_bool +
+       (bool) opt_desc->opt_int +
+       (bool) opt_desc->opt_keyword +
+       (bool) opt_desc->opt_flags +
+       (bool) opt_desc->opt_string +
+       (bool) opt_desc->opt_position;
 }
 
 /* Return true if opt_desc is valid. */
-static bool _opt_valid (const notmuch_opt_desc_t *opt_desc)
+static bool
+_opt_valid (const notmuch_opt_desc_t *opt_desc)
 {
     int n = _opt_set_count (opt_desc);
 
@@ -142,15 +147,17 @@ static bool _opt_valid (const notmuch_opt_desc_t *opt_desc)
 }
 
 /*
  Search for the {pos_arg_index}th position argument, return false if
  that does not exist.
-*/
* Search for the {pos_arg_index}th position argument, return false if
* that does not exist.
+ */
 
 bool
 parse_position_arg (const char *arg_str, int pos_arg_index,
-                   const notmuch_opt_desc_t *arg_desc) {
+                   const notmuch_opt_desc_t *arg_desc)
+{
 
     int pos_arg_counter = 0;
+
     while (_opt_valid (arg_desc)) {
        if (arg_desc->opt_position) {
            if (pos_arg_counter == pos_arg_index) {
@@ -176,12 +183,12 @@ parse_position_arg (const char *arg_str, int pos_arg_index,
 int
 parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index)
 {
-    assert(argv);
+    assert (argv);
 
     const char *_arg = argv[opt_index];
 
-    assert(_arg);
-    assert(options);
+    assert (_arg);
+    assert (options);
 
     const char *arg = _arg + 2; /* _arg starts with -- */
     const char *negative_arg = NULL;
@@ -239,7 +246,7 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_
        if (lookahead) {
            next = ' ';
            value = next_arg;
-           opt_index ++;
+           opt_index++;
        }
 
        opt_handled opt_status = OPT_FAILED;
@@ -258,12 +265,12 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_
            return -1;
 
        if (lookahead && opt_status == OPT_GIVEBACK)
-           opt_index --;
+           opt_index--;
 
        if (try->present)
            *try->present = true;
 
-       return opt_index+1;
+       return opt_index + 1;
     }
     return -1;
 }
@@ -271,13 +278,14 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_
 /* See command-line-arguments.h for description */
 int
 parse_arguments (int argc, char **argv,
-                const notmuch_opt_desc_t *options, int opt_index) {
+                const notmuch_opt_desc_t *options, int opt_index)
+{
 
     int pos_arg_index = 0;
     bool more_args = true;
 
     while (more_args && opt_index < argc) {
-       if (strncmp (argv[opt_index],"--",2) != 0) {
+       if (strncmp (argv[opt_index], "--", 2) != 0) {
 
            more_args = parse_position_arg (argv[opt_index], pos_arg_index, options);
 
@@ -290,7 +298,7 @@ parse_arguments (int argc, char **argv,
            int prev_opt_index = opt_index;
 
            if (strlen (argv[opt_index]) == 2)
-               return opt_index+1;
+               return opt_index + 1;
 
            opt_index = parse_option (argc, argv, options, opt_index);
            if (opt_index < 0) {
index f722f97dde6e57c691437914b91cf42128384fa9..606e5cd005f8e60326a48a7e9501263c343aaaae 100644 (file)
@@ -45,20 +45,20 @@ typedef struct notmuch_opt_desc {
 
 
 /*
-  This is the main entry point for command line argument parsing.
-
-  Parse command line arguments according to structure options,
-  starting at position opt_index.
-
-  All output of parsed values is via pointers in options.
-
-  Parsing stops at -- (consumed) or at the (k+1)st argument
-  not starting with -- (a "positional argument") if options contains
-  k positional argument descriptors.
-
-  Returns the index of first non-parsed argument, or -1 in case of error.
-
-*/
* This is the main entry point for command line argument parsing.
+ *
* Parse command line arguments according to structure options,
* starting at position opt_index.
+ *
* All output of parsed values is via pointers in options.
+ *
* Parsing stops at -- (consumed) or at the (k+1)st argument
* not starting with -- (a "positional argument") if options contains
* k positional argument descriptors.
+ *
* Returns the index of first non-parsed argument, or -1 in case of error.
+ *
+ */
 int
 parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index);
 
@@ -71,12 +71,12 @@ 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_toptions, int opt_index);
+parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index);
 
 bool
 parse_position_arg (const char *arg,
                    int position_arg_index,
-                   const notmuch_opt_desc_toptions);
+                   const notmuch_opt_desc_t *options);
 
 
 #endif
index bcb9f0ecdceb8c064357a2c54f281b97ba9e726f..2ee1b3996f23f17ca980affdf1bf8cbae763029e 100644 (file)
@@ -1,4 +1,4 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := compat
 extra_cflags += -I$(srcdir)/$(dir)
index e92c0f6253636b435087680a562092feb352ed5e..000f9e781573e92b50bef8708962bffa0e08f2d6 100644 (file)
@@ -4,10 +4,10 @@
 #include <stdlib.h>
 
 char *
-canonicalize_file_name (const char * path)
+canonicalize_file_name (const char *path)
 {
 #ifdef PATH_MAX
-    char *resolved_path =  malloc (PATH_MAX+1);
+    char *resolved_path =  malloc (PATH_MAX + 1);
     if (resolved_path == NULL)
        return NULL;
 
index b0e56f0c1a2b10e1bb46564d91967cb829101bc8..62ad69d66dc9665ba8b59e1646ad30458a8ac107 100644 (file)
@@ -1,7 +1,8 @@
 #include <time.h>
 #include <stdio.h>
 
-int main()
+int
+main ()
 {
     struct tm tm;
 
index c435eb89df035c48c6b33474fe1180d87d2b1710..babeb742cabc6baa5d6367de48ef372da1fcc9bd 100644 (file)
@@ -1,7 +1,8 @@
 #include <stdio.h>
 #include <pwd.h>
 
-int main()
+int
+main ()
 {
     struct passwd passwd, *ignored;
 
index 88bc4df40cf1b40c37fa81b73e9e62dd4f09c2fb..8f15e585fda82221eaeed22bfc0834b4ccd9fa67 100644 (file)
 extern "C" {
 #endif
 
-#if !STD_GETPWUID
+#if ! STD_GETPWUID
 #define _POSIX_PTHREAD_SEMANTICS 1
 #endif
-#if !STD_ASCTIME
+#if ! STD_ASCTIME
 #define _POSIX_PTHREAD_SEMANTICS 1
 #endif
 
-#if !HAVE_CANONICALIZE_FILE_NAME
+#if ! HAVE_CANONICALIZE_FILE_NAME
 /* we only call this function from C, and this makes testing easier */
 #ifndef __cplusplus
 char *
@@ -45,7 +45,7 @@ canonicalize_file_name (const char *path);
 #endif
 #endif
 
-#if !HAVE_GETLINE
+#if ! HAVE_GETLINE
 #include <stdio.h>
 #include <unistd.h>
 
@@ -55,31 +55,31 @@ getline (char **lineptr, size_t *n, FILE *stream);
 ssize_t
 getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp);
 
-#endif /* !HAVE_GETLINE */
+#endif  /* !HAVE_GETLINE */
 
-#if !HAVE_STRCASESTR
-char* strcasestr(const char *haystack, const char *needle);
-#endif /* !HAVE_STRCASESTR */
+#if ! HAVE_STRCASESTR
+char *strcasestr (const char *haystack, const char *needle);
+#endif  /* !HAVE_STRCASESTR */
 
-#if !HAVE_STRSEP
-char *strsep(char **stringp, const char *delim);
-#endif /* !HAVE_STRSEP */
+#if ! HAVE_STRSEP
+char *strsep (char **stringp, const char *delim);
+#endif  /* !HAVE_STRSEP */
 
-#if !HAVE_TIMEGM
+#if ! HAVE_TIMEGM
 #include <time.h>
 time_t timegm (struct tm *tm);
-#endif /* !HAVE_TIMEGM */
+#endif  /* !HAVE_TIMEGM */
 
 /* Silence gcc warnings about unused results.  These warnings exist
  * for a reason; any use of this needs to be justified. */
 #ifdef __GNUC__
-#define IGNORE_RESULT(x) ({ __typeof__(x) __z = (x); (void)(__z = __z); })
+#define IGNORE_RESULT(x) ({ __typeof__(x) __z = (x); (void) (__z = __z); })
 #else /* !__GNUC__ */
 #define IGNORE_RESULT(x) x
-#endif /* __GNUC__ */
+#endif  /* __GNUC__ */
 
 #ifdef __cplusplus
 }
 #endif
 
-#endif /* NOTMUCH_COMPAT_H */
+#endif  /* NOTMUCH_COMPAT_H */
index 1945b5bf1bed7ea6f20c12398a7c82948b68fbdd..8f08bec45f20ff6ab3a6bfaaebfa5b5870ce1772 100644 (file)
@@ -35,9 +35,9 @@
  * provides support for testing for function attributes.
  */
 #ifndef NORETURN_ATTRIBUTE
-#if (__GNUC__ >= 3 ||                          \
-     (__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || \
-     __has_attribute (noreturn))
+#if (__GNUC__ >= 3 ||                           \
+     (__GNUC__ == 2 && __GNUC_MINOR__ >= 5) ||  \
+    __has_attribute (noreturn))
 #define NORETURN_ATTRIBUTE __attribute__ ((noreturn))
 #else
 #define NORETURN_ATTRIBUTE
index 198a727c647ad4ea984ee8abeeefce27e2591d13..7c0ee727589f93dccce99c384513b69a23c83b43 100644 (file)
@@ -2,17 +2,18 @@
 #include <zlib.h>
 
 static const char *template =
-       "prefix=/usr\n"
-       "exec_prefix=${prefix}\n"
-       "libdir=${exec_prefix}/lib\n"
-       "\n"
-       "Name: zlib\n"
-       "Description: zlib compression library\n"
-       "Version: %s\n"
-       "Libs: -lz\n";
+    "prefix=/usr\n"
+    "exec_prefix=${prefix}\n"
+    "libdir=${exec_prefix}/lib\n"
+    "\n"
+    "Name: zlib\n"
+    "Description: zlib compression library\n"
+    "Version: %s\n"
+    "Libs: -lz\n";
 
-int main(void)
+int
+main (void)
 {
-       printf(template, ZLIB_VERSION);
-       return 0;
+    printf (template, ZLIB_VERSION);
+    return 0;
 }
index 407f3d07c7443836a11b5dbc9236337e7e6294e7..e5c1f07c200078fc68c9a86e530ad00c540069b7 100644 (file)
@@ -1,21 +1,21 @@
 /* getdelim.c --- Implementation of replacement getdelim function.
  Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005, 2006, 2007,
  2008, 2009 Free Software Foundation, Inc.
-
  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, 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, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.  */
* Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005, 2006, 2007,
* 2008, 2009 Free Software Foundation, Inc.
+ *
* 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, 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.  */
 
 /* Ported from glibc by Simon Josefsson. */
 
 
 #if USE_UNLOCKED_IO
 # include "unlocked-io.h"
-# define getc_maybe_unlocked(fp)       getc(fp)
-#elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED
+# define getc_maybe_unlocked(fp)        getc (fp)
+#elif ! HAVE_FLOCKFILE || ! HAVE_FUNLOCKFILE || ! HAVE_DECL_GETC_UNLOCKED
 # undef flockfile
 # undef funlockfile
 # define flockfile(x) ((void) 0)
 # define funlockfile(x) ((void) 0)
-# define getc_maybe_unlocked(fp)       getc(fp)
+# define getc_maybe_unlocked(fp)        getc (fp)
 #else
-# define getc_maybe_unlocked(fp)       getc_unlocked(fp)
+# define getc_maybe_unlocked(fp)        getc_unlocked (fp)
 #endif
 
 /* Read up to (and including) a DELIMITER from FP into *LINEPTR (and
  NUL-terminate it).  *LINEPTR is a pointer returned from malloc (or
  NULL), pointing to *N characters of space.  It is realloc'ed as
  necessary.  Returns the number of characters read (not including
  the null terminator), or -1 on error or EOF.  */
* NUL-terminate it).  *LINEPTR is a pointer returned from malloc (or
* NULL), pointing to *N characters of space.  It is realloc'ed as
* necessary.  Returns the number of characters read (not including
* the null terminator), or -1 on error or EOF.  */
 
 ssize_t
 getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp)
 {
-  ssize_t result = -1;
-  size_t cur_len = 0;
+    ssize_t result = -1;
+    size_t cur_len = 0;
 
-  if (lineptr == NULL || n == NULL || fp == NULL)
-    {
-      errno = EINVAL;
-      return -1;
+    if (lineptr == NULL || n == NULL || fp == NULL) {
+       errno = EINVAL;
+       return -1;
     }
 
-  flockfile (fp);
-
-  if (*lineptr == NULL || *n == 0)
-    {
-      char *new_lineptr;
-      *n = 120;
-      new_lineptr = (char *) realloc (*lineptr, *n);
-      if (new_lineptr == NULL)
-       {
-         result = -1;
-         goto unlock_return;
+    flockfile (fp);
+
+    if (*lineptr == NULL || *n == 0) {
+       char *new_lineptr;
+       *n = 120;
+       new_lineptr = (char *) realloc (*lineptr, *n);
+       if (new_lineptr == NULL) {
+           result = -1;
+           goto unlock_return;
        }
-      *lineptr = new_lineptr;
+       *lineptr = new_lineptr;
     }
 
-  for (;;)
-    {
-      int i;
+    for (;;) {
+       int i;
 
-      i = getc_maybe_unlocked (fp);
-      if (i == EOF)
-       {
-         result = -1;
-         break;
+       i = getc_maybe_unlocked (fp);
+       if (i == EOF) {
+           result = -1;
+           break;
        }
 
-      /* Make enough space for len+1 (for final NUL) bytes.  */
-      if (cur_len + 1 >= *n)
-       {
-         size_t needed_max =
-           SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
-         size_t needed = 2 * *n + 1;   /* Be generous. */
-         char *new_lineptr;
-
-         if (needed_max < needed)
-           needed = needed_max;
-         if (cur_len + 1 >= needed)
-           {
-             result = -1;
-             errno = EOVERFLOW;
-             goto unlock_return;
+       /* Make enough space for len+1 (for final NUL) bytes.  */
+       if (cur_len + 1 >= *n) {
+           size_t needed_max =
+               SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
+           size_t needed = 2 * *n + 1; /* Be generous. */
+           char *new_lineptr;
+
+           if (needed_max < needed)
+               needed = needed_max;
+           if (cur_len + 1 >= needed) {
+               result = -1;
+               errno = EOVERFLOW;
+               goto unlock_return;
            }
 
-         new_lineptr = (char *) realloc (*lineptr, needed);
-         if (new_lineptr == NULL)
-           {
-             result = -1;
-             goto unlock_return;
+           new_lineptr = (char *) realloc (*lineptr, needed);
+           if (new_lineptr == NULL) {
+               result = -1;
+               goto unlock_return;
            }
 
-         *lineptr = new_lineptr;
-         *n = needed;
+           *lineptr = new_lineptr;
+           *n = needed;
        }
 
-      (*lineptr)[cur_len] = i;
-      cur_len++;
+       (*lineptr)[cur_len] = i;
+       cur_len++;
 
-      if (i == delimiter)
-       break;
+       if (i == delimiter)
+           break;
     }
-  (*lineptr)[cur_len] = '\0';
-  result = cur_len ? (ssize_t) cur_len : result;
+    (*lineptr)[cur_len] = '\0';
+    result = cur_len ? (ssize_t) cur_len : result;
 
- unlock_return:
-  funlockfile (fp); /* doesn't set errno */
 unlock_return:
+    funlockfile (fp); /* doesn't set errno */
 
-  return result;
+    return result;
 }
index 222e0f6cd70be54b52a12f3644872c62c5be52ab..2fcaba1439b7d20714dbc83beceef37f08fc8d56 100644 (file)
@@ -1,20 +1,20 @@
 /* getline.c --- Implementation of replacement getline function.
  Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
-
  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, 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, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.  */
* Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
+ *
* 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, 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.  */
 
 /* Written by Simon Josefsson. */
 
@@ -25,5 +25,5 @@
 ssize_t
 getline (char **lineptr, size_t *n, FILE *stream)
 {
-  return getdelim (lineptr, n, '\n', stream);
+    return getdelim (lineptr, n, '\n', stream);
 }
index 24c848ec3bf5d8e21b8d8ddd1dbf908779782d44..e56097930fc85cde5c9fce535c1f14df3a64dfa5 100644 (file)
@@ -1,7 +1,8 @@
 #define _GNU_SOURCE
 #include <stdlib.h>
 
-int main()
+int
+main ()
 {
     char *found;
     char *string;
index 9ca6c6e08ed6cad81e84bf012e224c3225fecce7..5338ee4dadac620abe51b21d69580c02ceb39fac 100644 (file)
@@ -1,6 +1,7 @@
 #include <dirent.h>
 
-int main()
+int
+main ()
 {
     struct dirent ent;
 
index a8bcd17e8dcc15f5f3d32f3c23b86ac0bb5c4305..6952a3b36724bfd771920677740d4c82a76b8efb 100644 (file)
@@ -2,12 +2,13 @@
 #include <stdio.h>
 #include <sys/types.h>
 
-int main()
+int
+main ()
 {
     ssize_t count = 0;
     size_t n = 0;
     char **lineptr = NULL;
     FILE *stream = NULL;
 
-    count = getline(lineptr, &n, stream);
+    count = getline (lineptr, &n, stream);
 }
index c0fb7629d8e410f3a372c5c365c722e2a17a4ee1..3cd1838d950bd30c6239053a09d2a8f401f8b537 100644 (file)
@@ -1,10 +1,11 @@
 #define _GNU_SOURCE
 #include <strings.h>
 
-int main()
+int
+main ()
 {
     char *found;
     const char *haystack, *needle;
 
-    found = strcasestr(haystack, needle);
+    found = strcasestr (haystack, needle);
 }
index 2abab819b01a483e8f1738995b5aa163f3eca7fb..dd4aae753447cee641bfd9d26a45d7b8cb73f2af 100644 (file)
@@ -1,11 +1,12 @@
 #define _GNU_SOURCE
 #include <string.h>
 
-int main()
+int
+main ()
 {
     char *found;
     char **stringp;
     const char *delim;
 
-    found = strsep(stringp, delim);
+    found = strsep (stringp, delim);
 }
index 483fc3b6685dd3df772382a888edfd7dde3401d5..8f7b380e767b6caf97d64963aa48f5be66d82a1b 100644 (file)
@@ -1,6 +1,7 @@
 #include <time.h>
 
-int main()
+int
+main ()
 {
-    return (int) timegm((struct tm *)0);
+    return (int) timegm ((struct tm *) 0);
 }
index 62a3a5450170be12a0f9e7eef61a2e97dc1e9ea9..d4480bf3ce073b1c5aa63211199ba0231aac8d7d 100644 (file)
@@ -3,20 +3,20 @@
  * don't include it in their library
  *
  * based on a GPL implementation in OpenTTD found under GPL v2
-
  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, version 2.
-
  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, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.  */
+ *
* 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, version 2.
+ *
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.  */
 
 /* Imported into notmuch by Dirk Hohndel - original author unknown. */
 
 
 #include "compat.h"
 
-char *strcasestr(const char *haystack, const char *needle)
+char *
+strcasestr (const char *haystack, const char *needle)
 {
-       size_t hay_len = strlen(haystack);
-       size_t needle_len = strlen(needle);
-       while (hay_len >= needle_len) {
-               if (strncasecmp(haystack, needle, needle_len) == 0)
-                   return (char *) haystack;
+    size_t hay_len = strlen (haystack);
+    size_t needle_len = strlen (needle);
+
+    while (hay_len >= needle_len) {
+       if (strncasecmp (haystack, needle, needle_len) == 0)
+           return (char *) haystack;
 
-               haystack++;
-               hay_len--;
-       }
+       haystack++;
+       hay_len--;
+    }
 
-       return NULL;
+    return NULL;
 }
index 78ab9e714a0f8325de0c22123b2875001aae18c7..4b6926d9fdb491711ce9597b3db9fd02a96346f9 100644 (file)
@@ -1,65 +1,61 @@
 /* Copyright (C) 1992, 93, 96, 97, 98, 99, 2004 Free Software Foundation, Inc.
  This file is part of the GNU C Library.
-
  The GNU C Library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
-
  The GNU C Library 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
  Lesser General Public License for more details.
-
  You should have received a copy of the GNU Lesser General Public
  License along with the GNU C Library; if not, write to the Free
  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  02111-1307 USA.  */
* This file is part of the GNU C Library.
+ *
* The GNU C Library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
+ *
* The GNU C Library 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
* Lesser General Public License for more details.
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with the GNU C Library; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA.  */
 
 #include <string.h>
 
 /* Taken from glibc 2.6.1 */
 
-char *strsep (char **stringp, const char *delim)
+char *
+strsep (char **stringp, const char *delim)
 {
-  char *begin, *end;
+    char *begin, *end;
 
-  begin = *stringp;
-  if (begin == NULL)
-    return NULL;
+    begin = *stringp;
+    if (begin == NULL)
+       return NULL;
 
-  /* A frequent case is when the delimiter string contains only one
-     character.  Here we don't need to call the expensive `strpbrk'
-     function and instead work using `strchr'.  */
-  if (delim[0] == '\0' || delim[1] == '\0')
-    {
-      char ch = delim[0];
+    /* A frequent case is when the delimiter string contains only one
+     * character.  Here we don't need to call the expensive `strpbrk'
+     * function and instead work using `strchr'.  */
+    if (delim[0] == '\0' || delim[1] == '\0') {
+       char ch = delim[0];
 
-      if (ch == '\0')
-       end = NULL;
-      else
-       {
-         if (*begin == ch)
-           end = begin;
-         else if (*begin == '\0')
+       if (ch == '\0')
            end = NULL;
-         else
-           end = strchr (begin + 1, ch);
+       else {
+           if (*begin == ch)
+               end = begin;
+           else if (*begin == '\0')
+               end = NULL;
+           else
+               end = strchr (begin + 1, ch);
        }
-    }
-  else
-    /* Find the end of the token.  */
-    end = strpbrk (begin, delim);
-
-  if (end)
-    {
-      /* Terminate the token and set *STRINGP past NUL character.  */
-      *end++ = '\0';
-      *stringp = end;
-    }
-  else
-    /* No more delimiters; this is the last token.  */
-    *stringp = NULL;
-
-  return begin;
+    } else
+       /* Find the end of the token.  */
+       end = strpbrk (begin, delim);
+
+    if (end) {
+       /* Terminate the token and set *STRINGP past NUL character.  */
+       *end++ = '\0';
+       *stringp = end;
+    } else
+       /* No more delimiters; this is the last token.  */
+       *stringp = NULL;
+
+    return begin;
 }
index 3560c370f40c425ecf379fc3b9a9850355a515dc..005a42399d8e8b9afa63e33ef4278778709943d7 100644 (file)
@@ -1,19 +1,19 @@
 /* timegm.c --- Implementation of replacement timegm function.
-
  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, 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, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.  */
+ *
* 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, 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.  */
 
 /* Copyright 2013 Blake Jones. */
 
@@ -35,20 +35,20 @@ leapyear (int year)
 time_t
 timegm (struct tm *tm)
 {
-    int        monthlen[2][12] = {
+    int monthlen[2][12] = {
        { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
        { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
     };
-    int        year, month, days;
+    int year, month, days;
 
     days = 365 * (tm->tm_year - 70);
     for (year = 70; year < tm->tm_year; year++) {
-       if (leapyear(1900 + year)) {
+       if (leapyear (1900 + year)) {
            days++;
        }
     }
     for (month = 0; month < tm->tm_mon; month++) {
-       days += monthlen[leapyear(1900 + year)][month];
+       days += monthlen[leapyear (1900 + year)][month];
     }
     days += tm->tm_mday - 1;
 
index 8e86c9d231d958287b8b69ad58f16f81956a60d2..54df463c24fa48bdb2362396edff9ac263f74458 100644 (file)
@@ -1,4 +1,4 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := completion
 
index 6e73b22ef68d044f63074bbd7b9a1775a23e12af..cfa9c09bd3dc4c186c163c6b223b56598789ba3e 100755 (executable)
--- a/configure
+++ b/configure
@@ -26,8 +26,29 @@ readonly DEFAULT_IFS="$IFS"
 srcdir=$(dirname "$0")
 NOTMUCH_SRCDIR=$(cd "$srcdir" && pwd)
 
+case $NOTMUCH_SRCDIR in ( *\'* | *['\"`$']* )
+       echo "Definitely unsafe characters in source path '$NOTMUCH_SRCDIR'".
+       exit 1
+esac
+
+case $PWD in ( *\'* | *['\"`$']* )
+       echo "Definitely unsafe characters in current directory '$PWD'".
+       exit 1
+esac
+
+# In case of whitespace, builds may work, tests definitely will not.
+case $NOTMUCH_SRCDIR in ( *["$IFS"]* )
+       echo "Whitespace in source path '$NOTMUCH_SRCDIR' not supported".
+       exit 1
+esac
+
+case $PWD in ( *["$IFS"]* )
+       echo "Whitespace in current directory '$PWD' not supported".
+       exit 1
+esac
+
 subdirs="util compat lib parse-time-string completion doc emacs"
-subdirs="${subdirs} performance-test test test/test-databases"
+subdirs="${subdirs} performance-test test"
 subdirs="${subdirs} bindings"
 
 # For a non-srcdir configure invocation (such as ../configure), create
@@ -49,6 +70,14 @@ if [ "$srcdir" != "." ]; then
     mkdir bindings/ruby
     cp -a "$srcdir"/bindings/ruby/*.[ch] bindings/ruby
     cp -a "$srcdir"/bindings/ruby/extconf.rb bindings/ruby
+
+    # Use the same hack to replicate python-cffi source for
+    # out-of-tree builds (again, not ideal).
+    mkdir bindings/python-cffi
+    cp -a "$srcdir"/bindings/python-cffi/tests \
+       "$srcdir"/bindings/python-cffi/notmuch2 \
+       "$srcdir"/bindings/python-cffi/setup.py \
+       bindings/python-cffi/
 fi
 
 # Set several defaults (optionally specified by the user in
@@ -79,6 +108,7 @@ PREFIX=/usr/local
 LIBDIR=
 WITH_DOCS=1
 WITH_API_DOCS=1
+WITH_PYTHON_DOCS=1
 WITH_EMACS=1
 WITH_DESKTOP=1
 WITH_BASH=1
@@ -147,7 +177,7 @@ Fine tuning of some installation directories is available:
        --emacslispdir=DIR      Emacs code [PREFIX/share/emacs/site-lisp]
        --emacsetcdir=DIR       Emacs miscellaneous files [PREFIX/share/emacs/site-lisp]
        --bashcompletiondir=DIR Bash completions files [PREFIX/share/bash-completion/completions]
-       --zshcompletiondir=DIR  Zsh completions files [PREFIX/share/zsh/functions/Completion/Unix]
+       --zshcompletiondir=DIR  Zsh completions files [PREFIX/share/zsh/site-functions]
 
 Some features can be disabled (--with-feature=no is equivalent to
 --without-feature) :
@@ -401,15 +431,22 @@ else
     have_pkg_config=0
 fi
 
-printf "Checking for Xapian development files... "
+
+
+printf "Checking for Xapian development files (>= 1.4.0)... "
 have_xapian=0
-for xapian_config in ${XAPIAN_CONFIG} xapian-config xapian-config-1.3; do
+for xapian_config in ${XAPIAN_CONFIG} xapian-config; do
     if ${xapian_config} --version > /dev/null 2>&1; then
        xapian_version=$(${xapian_config} --version | sed -e 's/.* //')
-       printf "Yes (%s).\n" ${xapian_version}
-       have_xapian=1
-       xapian_cxxflags=$(${xapian_config} --cxxflags)
-       xapian_ldflags=$(${xapian_config} --libs)
+       case $xapian_version in
+               1.[4-9]* | 1.[1-9][0-9]* | [2-9]* | [1-9][0-9]*)
+                       printf "Yes (%s).\n" ${xapian_version}
+                       have_xapian=1
+                       xapian_cxxflags=$(${xapian_config} --cxxflags)
+                       xapian_ldflags=$(${xapian_config} --libs)
+                       ;;
+               *) printf "Xapian $xapian_version not supported... "
+       esac
        break
     fi
 done
@@ -418,81 +455,10 @@ if [ ${have_xapian} = "0" ]; then
     errors=$((errors + 1))
 fi
 
-have_xapian_compact=0
-have_xapian_field_processor=0
-if [ ${have_xapian} = "1" ]; then
-    printf "Checking for Xapian compaction support... "
-    cat>_compact.cc<<EOF
-#include <xapian.h>
-class TestCompactor : public Xapian::Compactor { };
-EOF
-    if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _compact.cc -o _compact.o > /dev/null 2>&1
-    then
-       have_xapian_compact=1
-       printf "Yes.\n"
-    else
-       printf "No.\n"
-       errors=$((errors + 1))
-    fi
-
-    rm -f _compact.o _compact.cc
-
-    printf "Checking for Xapian FieldProcessor API... "
-    cat>_field_processor.cc<<EOF
-#include <xapian.h>
-class TitleFieldProcessor : public Xapian::FieldProcessor { };
-EOF
-    if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _field_processor.cc -o _field_processor.o > /dev/null 2>&1
-    then
-       have_xapian_field_processor=1
-       printf "Yes.\n"
-    else
-       printf "No. (optional)\n"
-    fi
-
-    rm -f _field_processor.o _field_processor.cc
-
-    default_xapian_backend=""
-    # DB_RETRY_LOCK is only supported on Xapian > 1.3.2
-    have_xapian_db_retry_lock=0
-    if [ $WITH_RETRY_LOCK = "1" ]; then
-       printf "Checking for Xapian lock retry support... "
-       cat>_retry.cc<<EOF
-#include <xapian.h>
-int flag = Xapian::DB_RETRY_LOCK;
-EOF
-       if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _retry.cc -o _retry.o > /dev/null 2>&1
-       then
-           have_xapian_db_retry_lock=1
-           printf "Yes.\n"
-       else
-           printf "No. (optional)\n"
-       fi
-       rm -f _retry.o _retry.cc
-    fi
-
-    printf "Testing default Xapian backend... "
-    cat >_default_backend.cc <<EOF
-#include <xapian.h>
-int main(int argc, char** argv) {
-   Xapian::WritableDatabase db("test.db",Xapian::DB_CREATE_OR_OPEN);
-}
-EOF
-    ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} _default_backend.cc -o _default_backend ${xapian_ldflags}
-    ./_default_backend
-    if [ -f test.db/iamglass ]; then
-       default_xapian_backend=glass
-    else
-       default_xapian_backend=chert
-    fi
-    printf "%s\n" "${default_xapian_backend}";
-    rm -rf test.db _default_backend _default_backend.cc
-fi
-
 GMIME_MINVER=3.0.3
 
-printf "Checking for GMime development files... "
-if pkg-config --exists "gmime-3.0 > $GMIME_MINVER"; then
+printf "Checking for GMime development files (>= $GMIME_MINVER)... "
+if pkg-config --exists "gmime-3.0 >= $GMIME_MINVER"; then
     printf "Yes.\n"
     have_gmime=1
     gmime_cflags=$(pkg-config --cflags gmime-3.0)
@@ -513,11 +479,11 @@ int main () {
 
     g_mime_init ();
     parser = g_mime_parser_new ();
-    g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("test/corpora/crypto/basic-encrypted.eml", "r", &error));
+    g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("$srcdir/test/corpora/crypto/basic-encrypted.eml", "r", &error));
     if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/crypto/basic-encrypted.eml\n");
 
     body = GMIME_MULTIPART_ENCRYPTED(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL)));
-    if (body == NULL) return !!        fprintf (stderr, "did not find a multipart encrypted message\n");
+    if (body == NULL) return !! fprintf (stderr, "did not find a multipart encrypted message\n");
 
     output = g_mime_multipart_encrypted_decrypt (body, GMIME_DECRYPT_EXPORT_SESSION_KEY, NULL, &decrypt_result, &error);
     if (error || output == NULL) return !! fprintf (stderr, "decryption failed\n");
@@ -533,7 +499,7 @@ EOF
         printf 'No.\nCould not make tempdir for testing session-key support.\n'
         errors=$((errors + 1))
     elif ${CC} ${CFLAGS} ${gmime_cflags} _check_session_keys.c ${gmime_ldflags} -o _check_session_keys \
-           && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < test/gnupg-secret-key.asc \
+           && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
            && SESSION_KEY=$(GNUPGHOME=${TEMP_GPG} ./_check_session_keys) \
            && [ $SESSION_KEY = 9:0BACD64099D1468AB07C796F0C0AC4851948A658A15B34E803865E9FC635F2F5 ]
     then
@@ -559,6 +525,154 @@ EOF
     if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
         rm -rf "$TEMP_GPG"
     fi
+
+    # see https://github.com/jstedfast/gmime/pull/90
+    # should be fixed in GMime in 3.2.7, but some distros might patch
+    printf "Checking for GMime X.509 certificate validity... "
+
+    cat > _check_x509_validity.c <<EOF
+#include <stdio.h>
+#include <gmime/gmime.h>
+
+int main () {
+    GError *error = NULL;
+    GMimeParser *parser = NULL;
+    GMimeApplicationPkcs7Mime *body = NULL;
+    GMimeSignatureList *sig_list = NULL;
+    GMimeSignature *sig = NULL;
+    GMimeCertificate *cert = NULL;
+    GMimeObject *output = NULL;
+    GMimeValidity validity = GMIME_VALIDITY_UNKNOWN;
+    int len;
+
+    g_mime_init ();
+    parser = g_mime_parser_new ();
+    g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("$srcdir/test/corpora/pkcs7/smime-onepart-signed.eml", "r", &error));
+    if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/pkcs7/smime-onepart-signed.eml\n");
+
+    body = GMIME_APPLICATION_PKCS7_MIME(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL)));
+    if (body == NULL) return !!        fprintf (stderr, "did not find a application/pkcs7 message\n");
+
+    sig_list = g_mime_application_pkcs7_mime_verify (body, GMIME_VERIFY_NONE, &output, &error);
+    if (error || output == NULL) return !! fprintf (stderr, "verify failed\n");
+
+    if (sig_list == NULL) return !! fprintf (stderr, "no GMimeSignatureList found\n");
+    len = g_mime_signature_list_length (sig_list);
+    if (len != 1) return !! fprintf (stderr, "expected 1 signature, got %d\n", len);
+    sig = g_mime_signature_list_get_signature (sig_list, 0);
+    if (sig == NULL) return !! fprintf (stderr, "no GMimeSignature found at position 0\n");
+    cert = g_mime_signature_get_certificate (sig);
+    if (cert == NULL) return !! fprintf (stderr, "no GMimeCertificate found\n");
+    validity = g_mime_certificate_get_id_validity (cert);
+    if (validity != GMIME_VALIDITY_FULL) return !! fprintf (stderr, "Got validity %d, expected %d\n", validity, GMIME_VALIDITY_FULL);
+
+    return 0;
+}
+EOF
+    if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
+        printf 'No.\nCould not make tempdir for testing X.509 certificate validity support.\n'
+        errors=$((errors + 1))
+    elif ${CC} ${CFLAGS} ${gmime_cflags} _check_x509_validity.c ${gmime_ldflags} -o _check_x509_validity \
+            && echo disable-crl-checks > "$TEMP_GPG/gpgsm.conf" \
+            && echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$TEMP_GPG/trustlist.txt" \
+            && GNUPGHOME=${TEMP_GPG} gpgsm --batch --quiet --import < "$srcdir"/test/smime/ca.crt
+    then
+        if GNUPGHOME=${TEMP_GPG} ./_check_x509_validity; then
+            gmime_x509_cert_validity=1
+            printf "Yes.\n"
+        else
+            gmime_x509_cert_validity=0
+            printf "No.\n"
+            if pkg-config --exists "gmime-3.0 >= 3.2.7"; then
+                cat <<EOF
+*** Error: GMime fails to calculate X.509 certificate validity, and
+is later than 3.2.7, which should have fixed this issue.
+
+Please follow up on https://github.com/jstedfast/gmime/pull/90 with
+more details.
+EOF
+                errors=$((errors + 1))
+            fi
+        fi
+    else
+        printf 'No.\nFailed to set up gpgsm for testing X.509 certificate validity support.\n'
+        errors=$((errors + 1))
+    fi
+    if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
+        rm -rf "$TEMP_GPG"
+    fi
+
+    # see https://dev.gnupg.org/T3464
+    # there are problems verifying signatures when decrypting with session keys with GPGME 1.13.0 and 1.13.1
+    printf "Checking signature verification when decrypting using session keys... "
+
+    cat > _verify_sig_with_session_key.c <<EOF
+#include <stdio.h>
+#include <gmime/gmime.h>
+
+int main () {
+    GError *error = NULL;
+    GMimeParser *parser = NULL;
+    GMimeMultipartEncrypted *body = NULL;
+    GMimeDecryptResult *result = NULL;
+    GMimeSignatureList *sig_list = NULL;
+    GMimeSignature *sig = NULL;
+    GMimeObject *output = NULL;
+    GMimeSignatureStatus status;
+    int len;
+
+    g_mime_init ();
+    parser = g_mime_parser_new ();
+    g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("$srcdir/test/corpora/crypto/encrypted-signed.eml", "r", &error));
+    if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/pkcs7/smime-onepart-signed.eml\n");
+
+    body = GMIME_MULTIPART_ENCRYPTED(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL)));
+    if (body == NULL) return !!        fprintf (stderr, "did not find a multipart/encrypted message\n");
+
+    output = g_mime_multipart_encrypted_decrypt (body, GMIME_DECRYPT_NONE, "9:13607E4217515A70EC8DF9DBC16C5327B94577561D98AD1246FA8756659C7899", &result, &error);
+    if (error || output == NULL) return !! fprintf (stderr, "decrypt failed\n");
+
+    sig_list = g_mime_decrypt_result_get_signatures (result);
+    if (sig_list == NULL) return !! fprintf (stderr, "sig_list is NULL\n");
+
+    if (sig_list == NULL) return !! fprintf (stderr, "no GMimeSignatureList found\n");
+    len = g_mime_signature_list_length (sig_list);
+    if (len != 1) return !! fprintf (stderr, "expected 1 signature, got %d\n", len);
+    sig = g_mime_signature_list_get_signature (sig_list, 0);
+    if (sig == NULL) return !! fprintf (stderr, "no GMimeSignature found at position 0\n");
+    status = g_mime_signature_get_status (sig);
+    if (status & GMIME_SIGNATURE_STATUS_KEY_MISSING) return !! fprintf (stderr, "signature status contains KEY_MISSING (see https://dev.gnupg.org/T3464)\n");
+
+    return 0;
+}
+EOF
+    if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
+        printf 'No.\nCould not make tempdir for testing signature verification when decrypting with session keys.\n'
+        errors=$((errors + 1))
+    elif ${CC} ${CFLAGS} ${gmime_cflags} _verify_sig_with_session_key.c ${gmime_ldflags} -o _verify_sig_with_session_key \
+            && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
+            && rm -f ${TEMP_GPG}/private-keys-v1.d/*.key
+    then
+        if GNUPGHOME=${TEMP_GPG} ./_verify_sig_with_session_key; then
+            gmime_verify_with_session_key=1
+            printf "Yes.\n"
+        else
+            gmime_verify_with_session_key=0
+            printf "No.\n"
+            cat <<EOF
+*** Error: GMime fails to verify signatures when decrypting with a session key.
+
+This is most likely due to a buggy version of GPGME, which should be fixed in 1.13.2 or later.
+See https://dev.gnupg.org/T3464 for more details.
+EOF
+        fi
+    else
+        printf 'No.\nFailed to set up gpg for testing signature verification while decrypting with a session key.\n'
+        errors=$((errors + 1))
+    fi
+    if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
+        rm -rf "$TEMP_GPG"
+    fi
 else
     have_gmime=0
     printf "No.\n"
@@ -583,7 +697,7 @@ fi
 if ! pkg-config --exists zlib; then
   ${CC} -o compat/gen_zlib_pc "$srcdir"/compat/gen_zlib_pc.c >/dev/null 2>&1 &&
   compat/gen_zlib_pc > compat/zlib.pc &&
-  PKG_CONFIG_PATH="$PKG_CONFIG_PATH":compat &&
+  PKG_CONFIG_PATH=${PKG_CONFIG_PATH:+$PKG_CONFIG_PATH:}compat &&
   export PKG_CONFIG_PATH
   rm -f compat/gen_zlib_pc
 fi
@@ -650,6 +764,43 @@ if [ $have_python -eq 0 ]; then
     errors=$((errors + 1))
 fi
 
+have_python3=0
+if [ $have_python -eq 1 ]; then
+    printf "Checking for python3 (>= 3.5)..."
+    if "$python" -c 'import sys, sysconfig; assert sys.version_info >= (3,5)'; >/dev/null 2>&1; then
+        printf "Yes.\n"
+        have_python3=1
+    else
+        printf "No (will not install CFFI-based python bindings).\n"
+    fi
+fi
+
+have_python3_cffi=0
+have_python3_pytest=0
+if [ $have_python3 -eq 1 ]; then
+    printf "Checking for python3 cffi and setuptools... "
+    if "$python" -c 'import cffi,setuptools; cffi.FFI().verify()' >/dev/null 2>&1; then
+        printf "Yes.\n"
+        have_python3_cffi=1
+        WITH_PYTHON_DOCS=1
+    else
+        WITH_PYTHON_DOCS=0
+        printf "No (will not install CFFI-based python bindings).\n"
+    fi
+    rm -rf __pycache__  # cffi.FFI().verify() uses this space
+
+    printf "Checking for python3 pytest (>= 3.0)... "
+    conf=$(mktemp)
+    printf "[pytest]\nminversion=3.0\n" > $conf
+    if "$python" -m pytest -c $conf --version >/dev/null 2>&1; then
+        printf "Yes.\n"
+        have_python3_pytest=1
+    else
+        printf "No (will not test CFFI-based python bindings).\n"
+    fi
+    rm -f $conf
+fi
+
 printf "Checking for valgrind development files... "
 if pkg-config --exists valgrind; then
     printf "Yes.\n"
@@ -677,13 +828,14 @@ if [ -z "${EMACSETCDIR-}" ]; then
     EMACSETCDIR="\$(prefix)/share/emacs/site-lisp"
 fi
 
-printf "Checking if emacs (>= 24) is available... "
-if emacs --quick --batch --eval '(if (< emacs-major-version 24) (kill-emacs 1))' > /dev/null 2>&1; then
-    printf "Yes.\n"
-    have_emacs=1
-else
-    printf "No (so will not byte-compile emacs code)\n"
-    have_emacs=0
+if [ $WITH_EMACS = "1" ]; then
+    printf "Checking if emacs (>= 25) is available... "
+    if emacs --quick --batch --eval '(if (< emacs-major-version 25) (kill-emacs 1))' > /dev/null 2>&1; then
+        printf "Yes.\n"
+    else
+        printf "No (disabling emacs related parts of build)\n"
+        WITH_EMACS=0
+    fi
 fi
 
 have_doxygen=0
@@ -823,8 +975,8 @@ EOF
     if [ $have_python -eq 0 ]; then
        echo "  python interpreter"
     fi
-    if [ $have_xapian -eq 0 -o $have_xapian_compact -eq 0 ]; then
-       echo "  Xapian library (>= version 1.2.6, including development files such as headers)"
+    if [ $have_xapian -eq 0 ]; then
+       echo "  Xapian library (>= version 1.4.0, including development files such as headers)"
        echo "  https://xapian.org/"
     fi
     if [ $have_zlib -eq 0 ]; then
@@ -859,7 +1011,7 @@ On Debian and similar systems:
 
 Or on Fedora and similar systems:
 
-       sudo yum install xapian-core-devel gmime-devel libtalloc-devel zlib-devel
+       sudo dnf install xapian-core-devel gmime30-devel libtalloc-devel zlib-devel
 
 On other systems, similar commands can be used, but the details of the
 package names may be different.
@@ -874,7 +1026,7 @@ to install pkg-config with a command such as:
 
        sudo apt-get install pkg-config
 Or:
-       sudo yum install pkgconfig
+       sudo dnf install pkgconfig
 
 But if pkg-config is not available for your system, then you will need
 to modify the configure script to manually set the cflags and ldflags
@@ -948,6 +1100,22 @@ else
 fi
 rm -f compat/have_timegm
 
+cat <<EOF > _time_t.c
+#include <time.h>
+#include <assert.h>
+static_assert(sizeof(time_t) >= 8, "sizeof(time_t) < 8");
+EOF
+
+printf "Checking for 64 bit time_t... "
+if ${CC} -c _time_t.c -o /dev/null
+then
+    printf "Yes.\n"
+    have_64bit_time_t=1
+else
+    printf "No.\n"
+    have_64bit_time_t=0
+fi
+
 printf "Checking for dirent.d_type... "
 if ${CC} -o compat/have_d_type "$srcdir"/compat/have_d_type.c > /dev/null 2>&1
 then
@@ -1031,7 +1199,8 @@ for flag in -Wmissing-declarations; do
 done
 printf "\n\t%s\n" "${WARN_CFLAGS}"
 
-rm -f minimal minimal.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys
+rm -f minimal minimal.c _time_t.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys _check_x509_validity.c _check_x509_validity \
+   _verify_sig_with_session_key.c _verify_sig_with_session_key
 
 # construct the Makefile.config
 cat > Makefile.config <<EOF
@@ -1165,9 +1334,6 @@ BASH_ABSOLUTE = ${bash_absolute}
 HAVE_PERL = ${have_perl}
 PERL_ABSOLUTE = ${perl_absolute}
 
-# Whether there's an emacs binary available for byte-compiling
-HAVE_EMACS = ${have_emacs}
-
 # Whether there's a sphinx-build binary available for building documentation
 HAVE_SPHINX=${have_sphinx}
 
@@ -1187,7 +1353,7 @@ desktop_dir = \$(prefix)/share/applications
 bash_completion_dir = ${BASHCOMPLETIONDIR:=\$(prefix)/share/bash-completion/completions}
 
 # The directory to which zsh completions files should be installed
-zsh_completion_dir = ${ZSHCOMLETIONDIR:=\$(prefix)/share/zsh/functions/Completion/Unix}
+zsh_completion_dir = ${ZSHCOMLETIONDIR:=\$(prefix)/share/zsh/site-functions}
 
 # Whether the canonicalize_file_name function is available (if not, then notmuch will
 # build its own version)
@@ -1204,6 +1370,12 @@ HAVE_GETLINE = ${have_getline}
 # building/testing ruby bindings.
 HAVE_RUBY_DEV = ${have_ruby_dev}
 
+# Is the python cffi package available?
+HAVE_PYTHON3_CFFI = ${have_python3_cffi}
+
+# Is the python pytest package available?
+HAVE_PYTHON3_PYTEST = ${have_python3_pytest}
+
 # Whether the strcasestr function is available (if not, then notmuch will
 # build its own version)
 HAVE_STRCASESTR = ${have_strcasestr}
@@ -1219,14 +1391,8 @@ HAVE_TIMEGM = ${have_timegm}
 # Whether struct dirent has d_type (if not, then notmuch will use stat)
 HAVE_D_TYPE = ${have_d_type}
 
-# Whether the Xapian version in use supports compaction
-HAVE_XAPIAN_COMPACT = ${have_xapian_compact}
-
-# Whether the Xapian version in use supports field processors
-HAVE_XAPIAN_FIELD_PROCESSOR = ${have_xapian_field_processor}
-
-# Whether the Xapian version in use supports DB_RETRY_LOCK
-HAVE_XAPIAN_DB_RETRY_LOCK = ${have_xapian_db_retry_lock}
+# Whether to have Xapian retry lock
+HAVE_XAPIAN_DB_RETRY_LOCK = ${WITH_RETRY_LOCK}
 
 # Whether the getpwuid_r function is standards-compliant
 # (if not, then notmuch will #define _POSIX_PTHREAD_SEMANTICS
@@ -1250,9 +1416,6 @@ LINKER_RESOLVES_LIBRARY_DEPENDENCIES = ${linker_resolves_library_dependencies}
 XAPIAN_CXXFLAGS = ${xapian_cxxflags}
 XAPIAN_LDFLAGS = ${xapian_ldflags}
 
-# Which backend will Xapian use by default?
-DEFAULT_XAPIAN_BACKEND = ${default_xapian_backend}
-
 # Flags needed to compile and link against GMime
 GMIME_CFLAGS = ${gmime_cflags}
 GMIME_LDFLAGS = ${gmime_ldflags}
@@ -1305,16 +1468,14 @@ COMMON_CONFIGURE_CFLAGS = \\
        -DHAVE_D_TYPE=\$(HAVE_D_TYPE)                           \\
        -DSTD_GETPWUID=\$(STD_GETPWUID)                         \\
        -DSTD_ASCTIME=\$(STD_ASCTIME)                           \\
-       -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT)           \\
        -DSILENCE_XAPIAN_DEPRECATION_WARNINGS                   \\
-       -DHAVE_XAPIAN_FIELD_PROCESSOR=\$(HAVE_XAPIAN_FIELD_PROCESSOR) \\
        -DHAVE_XAPIAN_DB_RETRY_LOCK=\$(HAVE_XAPIAN_DB_RETRY_LOCK)
 
 CONFIGURE_CFLAGS = \$(COMMON_CONFIGURE_CFLAGS)
 
 CONFIGURE_CXXFLAGS = \$(COMMON_CONFIGURE_CFLAGS) \$(XAPIAN_CXXFLAGS)
 
-CONFIGURE_LDFLAGS =  \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS)
+CONFIGURE_LDFLAGS = \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS)
 EOF
 
 # construct the sh.config
@@ -1324,17 +1485,14 @@ cat > sh.config <<EOF
 
 NOTMUCH_SRCDIR='${NOTMUCH_SRCDIR}'
 
-# Whether the Xapian version in use supports compaction
-NOTMUCH_HAVE_XAPIAN_COMPACT=${have_xapian_compact}
+# Whether to have Xapian retry lock
+NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${WITH_RETRY_LOCK}
 
-# Whether the Xapian version in use supports field processors
-NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR=${have_xapian_field_processor}
+# Whether GMime can verify X.509 certificate validity
+NOTMUCH_GMIME_X509_CERT_VALIDITY=${gmime_x509_cert_validity}
 
-# Whether the Xapian version in use supports lock retry
-NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${have_xapian_db_retry_lock}
-
-# Which backend will Xapian use by default?
-NOTMUCH_DEFAULT_XAPIAN_BACKEND=${default_xapian_backend}
+# Whether GMime can verify signatures when decrypting with a session key:
+NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY=${gmime_verify_with_session_key}
 
 # do we have man pages?
 NOTMUCH_HAVE_MAN=$((have_sphinx))
@@ -1343,6 +1501,9 @@ NOTMUCH_HAVE_MAN=$((have_sphinx))
 NOTMUCH_HAVE_BASH=${have_bash}
 NOTMUCH_BASH_ABSOLUTE=${bash_absolute}
 
+# Whether time_t is 64 bits (or more)
+NOTMUCH_HAVE_64BIT_TIME_T=${have_64bit_time_t}
+
 # Whether perl exists, and if so where
 NOTMUCH_HAVE_PERL=${have_perl}
 NOTMUCH_PERL_ABSOLUTE=${perl_absolute}
@@ -1357,10 +1518,27 @@ NOTMUCH_RUBY=${RUBY}
 # building/testing ruby bindings.
 NOTMUCH_HAVE_RUBY_DEV=${have_ruby_dev}
 
+# Is the python cffi package available?
+NOTMUCH_HAVE_PYTHON3_CFFI=${have_python3_cffi}
+
+# Is the python pytest package available?
+NOTMUCH_HAVE_PYTHON3_PYTEST=${have_python3_pytest}
+
 # Platform we are run on
 PLATFORM=${platform}
 EOF
 
+{
+    echo "# Generated by configure, run from doc/conf.py"
+    if [ $WITH_EMACS = "1" ]; then
+        echo "tags.add('WITH_EMACS')"
+    fi
+    if [ $WITH_PYTHON_DOCS = "1" ]; then
+        echo "tags.add('WITH_PYTHON')"
+    fi
+    printf "rsti_dir = '%s'\n" "$(cd emacs && pwd -P)"
+} > sphinx.config
+
 # Finally, after everything configured, inform the user how to continue.
 cat <<EOF
 
index 855438be35779a8497a9a7b4981892fe66af05e3..de933eaa72d26117eb5672fb3497a9d4768ead99 100644 (file)
@@ -15,11 +15,11 @@ README.html: README
        markdown $< > $@
 
 install: all
-       mkdir -p $(DESTDIR)$(prefix)/bin
+       mkdir -p $(DESTDIR)$(prefix)/bin $(DESTDIR)$(mandir)/man1 $(DESTDIR)$(sysconfdir)/Muttrc.d
        sed "1s|^#!.*|#! $(PERL_ABSOLUTE)|" < $(NAME) > $(DESTDIR)$(prefix)/bin/$(NAME)
        chmod 755 $(DESTDIR)$(prefix)/bin/$(NAME)
-       install -D -m 644 $(NAME).1 $(DESTDIR)$(mandir)/man1/$(NAME).1
-       install -D -m 644 $(NAME).rc $(DESTDIR)$(sysconfdir)/Muttrc.d/$(NAME).rc
+       install -m 644 $(NAME).1 $(DESTDIR)$(mandir)/man1/
+       install -m 644 $(NAME).rc $(DESTDIR)$(sysconfdir)/Muttrc.d/
 
 clean:
        rm -f notmuch-mutt.1 README.html
index 0e46a8c1b95e76163eed68694aa5a1a973c8b371..d1e2c084b4208f093f97a47e81169da28a65d9ad 100755 (executable)
@@ -12,6 +12,7 @@ use strict;
 use warnings;
 
 use File::Path;
+use File::Basename;
 use Getopt::Long qw(:config no_getopt_compat);
 use Mail::Header;
 use Mail::Box::Maildir;
@@ -41,16 +42,17 @@ sub search($$$) {
     my ($maildir, $remove_dups, $query) = @_;
     my $dup_option = "";
 
-    $query = shell_quote($query);
-
-    if ($remove_dups) {
-      $dup_option = "--duplicate=1";
-    }
+    my @args = qw/notmuch search --output=files/;
+    push @args, "--duplicate=1" if $remove_dups;
+    push @args, $query;
 
     empty_maildir($maildir);
-    system("notmuch search --output=files $dup_option $query"
-          . " | sed -e 's: :\\\\ :g'"
-          . " | xargs -r -I searchoutput ln -s searchoutput $maildir/cur/");
+    open my $pipe, '-|', @args or die "Running @args failed: $!\n";
+    while (<$pipe>) {
+       chomp;
+       my $ln = "$maildir/cur/" . basename $_;
+       symlink $_, "$ln" or warn "Failed to symlink '$_', '$ln': $!\n";
+    }
 }
 
 sub prompt($$) {
index 9273bbb42d31fca0e9ed98297224ee383724f334..63aaaa8a492b9720492acb1220b82375fe4295a3 100644 (file)
@@ -1,3 +1,100 @@
+notmuch (0.31.2-3) unstable; urgency=medium
+
+  * Switch to debhelper compat level 13
+
+ -- David Bremner <bremner@debian.org>  Mon, 09 Nov 2020 13:59:47 -0400
+
+notmuch (0.31.2-2) unstable; urgency=medium
+
+  * Run tests in verbose mode
+
+ -- David Bremner <bremner@debian.org>  Mon, 09 Nov 2020 08:45:38 -0400
+
+notmuch (0.31.2-1) unstable; urgency=medium
+
+  * Delete stray "version" file in upstream source
+
+ -- David Bremner <bremner@debian.org>  Sun, 08 Nov 2020 11:32:45 -0400
+
+notmuch (0.31.1-1) unstable; urgency=medium
+
+  * New upstream bugfix release.
+    - Portability / C++20 fixes
+    - Fix initialization bug in library config handling.
+
+ -- David Bremner <bremner@debian.org>  Sun, 08 Nov 2020 07:48:22 -0400
+
+notmuch (0.31-1) unstable; urgency=medium
+
+  * New upstream release
+  * Compatibility fixes for Emacs 27.1
+
+ -- David Bremner <bremner@debian.org>  Sat, 05 Sep 2020 21:47:42 -0300
+
+notmuch (0.31~rc2-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * Bug fix: "suggest elpa-mailscripts", thanks to Sean Whitton (Closes:
+    #944269).
+  * Bug fix: "suggest mailscripts", thanks to Sean Whitton (Closes:
+    #944270).
+  * Bug fix: "please drop transitional package notmuch-emacs from
+    src:notmuch", thanks to Holger Levsen (Closes: #940738).
+
+ -- David Bremner <bremner@debian.org>  Tue, 25 Aug 2020 07:51:33 -0300
+
+notmuch (0.31~rc1-1) experimental; urgency=medium
+
+  * Fix buggy test in T562-lib-database
+  * Clean up generated file in source package.
+
+ -- David Bremner <bremner@debian.org>  Mon, 17 Aug 2020 21:05:46 -0300
+
+notmuch (0.31~rc0-1) experimental; urgency=medium
+
+  * New upstream release candidate.
+  * Update notmuch-emacs for compatibility with GNU Emacs 27.1.
+
+ -- David Bremner <bremner@debian.org>  Sun, 16 Aug 2020 11:08:14 -0300
+
+notmuch (0.30-1) unstable; urgency=medium
+
+  * New upstream release
+  * Improvements to S/MIME handling
+  * Repairs to some mangled MIME messages
+  * New python bindings (notmuch2) compatible with current python 3
+
+ -- David Bremner <bremner@debian.org>  Fri, 10 Jul 2020 22:24:14 -0300
+
+notmuch (0.30~rc3-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * Mark two tests broken on legacy (32 bit time_t) architectures.
+  * Drop -std=c99
+
+ -- David Bremner <bremner@debian.org>  Fri, 03 Jul 2020 06:48:51 -0300
+
+notmuch (0.30~rc2-1) experimental; urgency=medium
+
+  * New upstream release candidate.
+  * Upstream fixes for new python bindings (python3-notmuch2).
+  * Update debian/copyright (one new author).
+
+ -- David Bremner <bremner@debian.org>  Tue, 16 Jun 2020 08:32:16 -0300
+
+notmuch (0.30~rc1-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * Update debian/changelog (new copyright holders)
+
+ -- David Bremner <bremner@debian.org>  Sat, 06 Jun 2020 08:06:56 -0300
+
+notmuch (0.30~rc0-2) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Mon, 01 Jun 2020 21:01:27 -0300
+
 notmuch (0.29.3-1~bpo10+1) buster-backports; urgency=medium
 
   * Rebuild for buster-backports.
diff --git a/debian/compat b/debian/compat
deleted file mode 100644 (file)
index b4de394..0000000
+++ /dev/null
@@ -1 +0,0 @@
-11
index 84be657dd7e266bbab551e730d115eae2ed63dab..585ff77889672720d75d40d87d6e49ff40da6e78 100644 (file)
@@ -4,39 +4,57 @@ Priority: optional
 Maintainer: Carl Worth <cworth@debian.org>
 Uploaders:
  Jameson Graef Rollins <jrollins@finestructure.net>,
- David Bremner <bremner@debian.org>
-Build-Conflicts: ruby1.8, gdb-minimal, gdb [ia64 mips mips64el]
+ David Bremner <bremner@debian.org>,
+Build-Conflicts:
+ gdb [ia64 mips mips64el],
+ gdb-minimal,
+ ruby1.8,
 Build-Depends:
+ bash-completion (>=1.9.0~),
+ debhelper-compat (= 13),
+ dh-elpa (>= 1.3),
+ dh-python,
+ desktop-file-utils,
+ doxygen,
  dpkg-dev (>= 1.17.14),
- debhelper (>= 11~),
- pkg-config,
- libxapian-dev,
+ dtach (>= 0.8) <!nocheck>,
+ emacs-nox | emacs-gtk | emacs-lucid | emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) | emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~),
+ gdb [!ia64 !mips !mips64el !kfreebsd-any !alpha] <!nocheck>,
+ gnupg <!nocheck>,
+ gpgsm <!nocheck>,
  libgmime-3.0-dev (>= 3.0.3~),
+ libpython3-all-dev,
  libtalloc-dev,
+ libxapian-dev,
  libz-dev,
+ pkg-config,
  python3-all (>= 3.1.2-7~),
- dh-python,
- dh-elpa (>= 1.3),
+ python3-cffi,
+ python3-pytest,
+ python3-pytest-cov,
+ python3-setuptools,
  python3-sphinx,
- ruby, ruby-dev (>>1:1.9.3~),
- emacs-nox | emacs-gtk | emacs-lucid |
- emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) |
- emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~),
- gdb [!ia64 !mips !mips64el !kfreebsd-any !alpha] <!nocheck>,
- dtach (>= 0.8) <!nocheck>,
- gpgsm <!nocheck>,
- gnupg <!nocheck>,
- bash-completion (>=1.9.0~),
- texinfo
-Standards-Version: 4.1.3
+ ruby,
+ ruby-dev (>>1:1.9.3~),
+ texinfo,
+Standards-Version: 4.4.1
 Homepage: https://notmuchmail.org/
 Vcs-Git: https://git.notmuchmail.org/git/notmuch -b release
 Vcs-Browser: https://git.notmuchmail.org/git/notmuch
+Rules-Requires-Root: no
 
 Package: notmuch
 Architecture: any
-Depends: libnotmuch5 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
-Recommends: elpa-notmuch | notmuch-vim | notmuch-mutt | alot,  gnupg-agent, gpgsm
+Depends:
+ libnotmuch5 (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Recommends:
+ elpa-notmuch | notmuch-vim | notmuch-mutt | alot,
+ gnupg-agent,
+ gpgsm,
+Suggests:
+ mailscripts
 Description: thread-based email index, search and tagging
  Notmuch is a system for indexing, searching, reading, and tagging
  large collections of email messages in maildir or mh format. It uses
@@ -48,8 +66,11 @@ Description: thread-based email index, search and tagging
 Package: libnotmuch5
 Section: libs
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}
-Pre-Depends: ${misc:Pre-Depends}
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
+Pre-Depends:
+ ${misc:Pre-Depends},
 Description: thread-based email index, search and tagging (runtime)
  Notmuch is a system for indexing, searching, reading, and tagging
  large collections of email messages in maildir or mh format. It uses
@@ -62,7 +83,9 @@ Description: thread-based email index, search and tagging (runtime)
 Package: libnotmuch-dev
 Section: libdevel
 Architecture: any
-Depends: ${misc:Depends}, libnotmuch5 (= ${binary:Version})
+Depends:
+ libnotmuch5 (= ${binary:Version}),
+ ${misc:Depends},
 Description: thread-based email index, search and tagging (development)
  Notmuch is a system for indexing, searching, reading, and tagging
  large collections of email messages in maildir or mh format. It uses
@@ -75,7 +98,29 @@ Description: thread-based email index, search and tagging (development)
 Package: python3-notmuch
 Architecture: all
 Section: python
-Depends: ${misc:Depends}, ${python3:Depends}, libnotmuch5 (>= ${source:Version})
+Depends:
+ libnotmuch5 (>= ${source:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+Description: Python 3 legacy interface to the notmuch mail search and index library
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package provides a legacy Python 3 interface to the notmuch
+ functionality, directly interfacing with a shared notmuch library.
+ .
+ New projects are encouraged to use python3-notmuch2 instead.
+
+Package: python3-notmuch2
+Architecture: any
+Section: python
+Depends:
+ libnotmuch5 (>= ${source:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
 Description: Python 3 interface to the notmuch mail search and index library
  Notmuch is a system for indexing, searching, reading, and tagging
  large collections of email messages in maildir or mh format. It uses
@@ -83,12 +128,17 @@ Description: Python 3 interface to the notmuch mail search and index library
  convenient search syntax.
  .
  This package provides a Python 3 interface to the notmuch
- functionality, directly interfacing with a shared notmuch library.
+ functionality using CFFI bindings, which interface with a shared
+ notmuch library.
+ .
+ This is the preferred way to use notmuch via Python.
 
 Package: ruby-notmuch
 Architecture: any
 Section: ruby
-Depends: ${shlibs:Depends}, ${misc:Depends}
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
 Description: Ruby interface to the notmuch mail search and index library
  Notmuch is a system for indexing, searching, reading, and tagging
  large collections of email messages in maildir or mh format. It uses
@@ -98,16 +148,12 @@ Description: Ruby interface to the notmuch mail search and index library
  This package provides a Ruby interface to the notmuch
  functionality, directly interfacing with a shared notmuch library.
 
-Package: notmuch-emacs
-Section: oldlibs
-Architecture: all
-Depends: elpa-notmuch, ${misc:Depends}
-Description: thread-based email index, search and tagging (transitional package)
- This dummy package help ease transition to the new package elpa-notmuch
-
 Package: elpa-notmuch
 Architecture: all
-Depends: ${misc:Depends}, ${elpa:Depends}
+Depends:
+ ${elpa:Depends},
+ ${misc:Depends},
+Suggests: elpa-mailscripts
 Description: thread-based email index, search and tagging (emacs interface)
  Notmuch is a system for indexing, searching, reading, and tagging
  large collections of email messages in maildir or mh format. It uses
@@ -119,10 +165,18 @@ Description: thread-based email index, search and tagging (emacs interface)
 
 Package: notmuch-vim
 Architecture: all
-Breaks: notmuch (<<0.6~254~)
-Replaces: notmuch (<<0.6~254~)
-Depends: ${misc:Depends}, notmuch, vim-addon-manager, vim-ruby, ruby-notmuch
-Recommends: ruby-mail
+Breaks:
+ notmuch (<<0.6~254~),
+Replaces:
+ notmuch (<<0.6~254~),
+Depends:
+ notmuch,
+ ruby-notmuch,
+ vim-addon-manager,
+ vim-ruby,
+ ${misc:Depends},
+Recommends:
+ ruby-mail,
 Description: thread-based email index, search and tagging (vim interface)
  Notmuch is a system for indexing, searching, reading, and tagging
  large collections of email messages in maildir or mh format. It uses
@@ -135,12 +189,18 @@ Description: thread-based email index, search and tagging (vim interface)
 Package: notmuch-mutt
 Architecture: all
 Depends:
+ libmail-box-perl,
+ libmailtools-perl,
+ libstring-shellquote-perl,
+ libterm-readline-gnu-perl,
  notmuch (>= 0.4),
- libmail-box-perl, libmailtools-perl,
- libstring-shellquote-perl, libterm-readline-gnu-perl,
- ${misc:Depends}
-Recommends: mutt
-Enhances: notmuch, mutt
+ ${misc:Depends},
+ ${perl:Depends},
+Recommends:
+ mutt,
+Enhances:
+ mutt,
+ notmuch,
 Description: thread-based email index, search and tagging (Mutt interface)
  notmuch-mutt provides integration among the Mutt mail user agent and
  the Notmuch mail indexer.
index 0931d9b97446dda6692f359d5dd603eff1983789..ba221e6bd26c9728fb40d1be6217fca27c84f283 100644 (file)
 Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 Upstream-Name: notmuch
-Source: git://notmuchmail.org/git/notmuch
+Source: https://git.notmuchmail.org/git/notmuch
 Upstream-Contact: Notmuch Mailing List <notmuch@notmuchmail.org>
 
 Files: *
-Copyright:  Copyright 2009 Carl Worth <cworth@cworth.org>
- Bart Trojanowski <bart@jukie.net>
- Keith Packard <keithp@keithp.com>
- Alexander Botero-Lowry <alex.boterolowry@gmail.com>
- Ingmar Vanhassel <ingmar@exherbo.org>
- Jed Brown <jed@59A2.org>
- Jan Janak <jan@ryngle.com>
- Chris Wilson <chris@chris-wilson.co.uk>
- Keith Amidon <keith@nicira.com>
- Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
- Mikhail Gusarov <dottedmag@dottedmag.net>
- Jeffrey C. Ollie <jeff@ocjtech.us>
- Jameson Graef Rollins <jrollins@finestructure.net>
- Stewart Smith <stewart@flamingspork.com>
- Adrian Perez <aperez@igalia.com>
- Kan-Ru Chen <kanru@kanru.info>
- James Rowe <jnrowe@gmail.com>
- Eric Anholt <eric@anholt.net>
- Alec Berryman <alec@thened.net>
- Tassilo Horn <tassilo@member.fsf.org>
- Stefan Schmidt <stefan@datenfreihafen.org>
- Rolland Santimano <rollandsantimano@yahoo.com>
- Peter Wang <novalazy@gmail.com>
- Lars Kellogg-Stedman <lars@seas.harvard.edu>
- Holger Freyther <zecke@selfish.org>
- David Bremner <bremner@unb.ca>
- Alexander Botero-Lowry <alexbl@fortitudo.(none)>
+Copyright:  Copyright 2009-2020
+ David Bremner
+ Carl Worth
+ Jani Nikula
+ Austin Clements
+ Daniel Kahn Gillmor
+ Mark Walters
+ Floris Bruynooghe
+ David Edmondson
+ Tomi Ollila
+ Sebastian Spaeth
+ Ali Polatel
+ Michal Sojka
+ Justus Winter
+ Sebastien Binet
+ W. Trevor King
+ Jameson Graef Rollins
+ Felipe Contreras
+ Pieter Praet
+ Peter Feigl
+ Dmitry Kurochkin
+ Peter Wang
+ Daniel Schoepe
+ Gregor Zattler
+ Keith Packard
+ Adam Wolfe Gordon
+ Stefano Zacchiroli
+ Vincent Breitmoser
+ laochailan
+ Ben Gamari
+ Aaron Ecay
+ Jesse Rosenthal
+ l-m-h@web.de
+ Thomas Jost
+ Dirk Hohndel
+ Blake Jones
+ Jonas Bernoulli
+ Damien Cassou
+ Vladimir Panteleev
+ Anton Khirnov
+ Matt Armstrong
+ Örjan Ekeberg
+ Jan Janak
+ Patrick Totzke
+ Chunyang Xu
+ rhn
+ Ruben Pollan
+ Ioan-Adrian Ratiu
+ Ethan Glasser-Camp
+ Todd
+ Chris Wilson
+ William Casarin
+ Yuri Volchkov
+ Cédric Cabessa
+ Mark Anderson
+ Jed Brown
+ Maxime Coste
+ Ludovic LANGE
+ Sebastian Poeplau
+ Mikhail
+ Gaute Hope
+ Keith Amidon
+ martin f. krafft
+ Jeffrey C. Ollie
+ Bart Trojanowski
+ Jameson Rollins
+ Scott Henson
+ Vladimir Marek
+ Servilio Afre Puentes
+ Kevin McCarthy
+ Tomas Carnecky
+ Kevin J. McCarthy
+ Scott Robinson
+ Wael M. Nasreddine
+ Charles Celerier
+ Olly Betts
+ Istvan Marko
+ Florian Klink
+ Thibaut Horel
+ Joel Borggrén-Franck
+ Ingmar Vanhassel
+ Olivier Taïbi
+ Ian Main
+ Alexander Botero-Lowry
+ Luis Ressel
+ Sergei Shilovsky
+ Trevor Jim
+ Jinwoo Lee
+ Uli Scholler
+ Matthew Lear
+ Amadeusz Żołnowski
 License: GPL-3+
 
 Files: debian/*
index 19e3ba5194fd184ac45da88ffb49b88efcaf1d62..4712b73ff76e8ad31fc54c51999ef862d863dd9e 100644 (file)
@@ -1,3 +1,3 @@
-emacs/*.el
-emacs/notmuch-logo.png
-debian/tmp/usr/share/info/*
+debian/tmp/usr/share/emacs/site-lisp/*.el
+debian/tmp/usr/share/emacs/site-lisp/notmuch-logo.png
+emacs/notmuch-pkg.el
diff --git a/debian/elpa-notmuch.info b/debian/elpa-notmuch.info
new file mode 100644 (file)
index 0000000..0ac0fbf
--- /dev/null
@@ -0,0 +1 @@
+usr/share/info/*.info
diff --git a/debian/elpa-notmuch.lintian-overrides b/debian/elpa-notmuch.lintian-overrides
new file mode 100644 (file)
index 0000000..aa275ed
--- /dev/null
@@ -0,0 +1,4 @@
+# elpa-notmuch is an elisp plugin for dealing with e-mail.  We can
+# already tell from the package name that it is an elisp package, so
+# it belongs in Section: mail, and lintian is being too strict here.
+elpa-notmuch: wrong-section-according-to-package-name elpa-notmuch => lisp
diff --git a/debian/libnotmuch-dev.manpages b/debian/libnotmuch-dev.manpages
new file mode 100644 (file)
index 0000000..9c4bd5d
--- /dev/null
@@ -0,0 +1 @@
+usr/share/man/man3/notmuch.3.gz
index 308567b83892a0e05d5b7595de817749308c5585..ec01593bf6592bba7a512d198b444017f5bd5bd6 100644 (file)
@@ -1,4 +1,5 @@
 libnotmuch.so.5 libnotmuch5 #MINVER#
+* Build-Depends-Package: libnotmuch-dev
  notmuch_built_with@Base 0.23~rc0
  notmuch_config_list_destroy@Base 0.23~rc0
  notmuch_config_list_key@Base 0.23~rc0
@@ -55,6 +56,7 @@ libnotmuch.so.5 libnotmuch5 #MINVER#
  notmuch_message_get_filename@Base 0.3
  notmuch_message_get_filenames@Base 0.5
  notmuch_message_get_flag@Base 0.3
+ notmuch_message_get_flag_st@Base 0.31~rc0
  notmuch_message_get_header@Base 0.3
  notmuch_message_get_message_id@Base 0.3
  notmuch_message_get_properties@Base 0.23~rc0
@@ -63,6 +65,7 @@ libnotmuch.so.5 libnotmuch5 #MINVER#
  notmuch_message_get_tags@Base 0.3
  notmuch_message_get_thread_id@Base 0.3
  notmuch_message_has_maildir_flag@Base 0.26~rc0
+ notmuch_message_has_maildir_flag_st@Base 0.31~rc0
  notmuch_message_maildir_flags_to_tags@Base 0.5
  notmuch_message_properties_destroy@Base 0.23~rc0
  notmuch_message_properties_key@Base 0.23~rc0
@@ -128,8 +131,6 @@ libnotmuch.so.5 libnotmuch5 #MINVER#
  (c++)"typeinfo for Xapian::DatabaseError@Base" 0.24~rc0
  (c++)"typeinfo for Xapian::DatabaseModifiedError@Base" 0.24~rc0
  (c++|optional=present with Xapian 1.4)"typeinfo for Xapian::QueryParserError@Base" 0.23~rc0
- (c++)"typeinfo for Xapian::QueryParser::add_valuerangeprocessor(Xapian::ValueRangeProcessor*)::ShimRangeProcessor@Base" 0.25~rc0
- (c++)"typeinfo name for Xapian::QueryParser::add_valuerangeprocessor(Xapian::ValueRangeProcessor*)::ShimRangeProcessor@Base" 0.25~rc0
  (c++)"typeinfo name for Xapian::LogicError@Base" 0.6.1
  (c++)"typeinfo name for Xapian::RuntimeError@Base" 0.6.1
  (c++)"typeinfo name for Xapian::DocNotFoundError@Base" 0.6.1
diff --git a/debian/not-installed b/debian/not-installed
new file mode 100644 (file)
index 0000000..fd92945
--- /dev/null
@@ -0,0 +1,3 @@
+usr/share/applications/mimeinfo.cache
+usr/share/info/dir
+usr/share/emacs/site-lisp/*.elc
index 9b468bdb0fecec74097a034dbb26242e263ff435..8314f8832394e9ca7016f66642743faf31c67fae 100644 (file)
@@ -1,2 +1,2 @@
-usr/bin/notmuch-mutt
 etc/Muttrc.d/notmuch-mutt.rc
+usr/bin/notmuch-mutt
index c6373e421f50975e1e44567c1389434c8740dfab..2b531314e6995880308d8a18f4e57396b9489508 100644 (file)
@@ -1,4 +1,4 @@
-usr/share/vim/registry
-usr/share/vim/addons/plugin
 usr/share/vim/addons/doc
+usr/share/vim/addons/plugin
 usr/share/vim/addons/syntax
+usr/share/vim/registry
index a1af708d78b2a1b051c016177920f7424952ae3f..cf8987385a097665fdb84df8aa2f1eb8e2d724f4 100644 (file)
@@ -1,4 +1,4 @@
-vim/notmuch.vim usr/share/vim/addons/plugin
 vim/notmuch.txt usr/share/vim/addons/doc
-vim/syntax/notmuch-*.vim usr/share/vim/addons/syntax
+vim/notmuch.vim usr/share/vim/addons/plugin
 vim/notmuch.yaml usr/share/vim/registry
+vim/syntax/notmuch-*.vim usr/share/vim/addons/syntax
index 0cce21bd60793667f552315add1b243f887a5a89..60f09712472c314af941c1db721eac4ff01650bc 100644 (file)
@@ -1,5 +1,5 @@
 usr/bin/notmuch
 usr/bin/notmuch-emacs-mua
+usr/share/applications/notmuch-emacs-mua.desktop
 usr/share/bash-completion
 usr/share/zsh/vendor-completions
-emacs/notmuch-emacs-mua.desktop usr/share/applications
index f9fcb54ac134fc9a0870da450154008527ff445f..3ac2ff5b45d51cf6801de20f5158406c153829bf 100644 (file)
@@ -1,18 +1,19 @@
-usr/share/man/man5/notmuch-hooks.5.gz
-usr/share/man/man1/notmuch-dump.1.gz
-usr/share/man/man1/notmuch-count.1.gz
+usr/share/man/man1/notmuch-address.1.gz
 usr/share/man/man1/notmuch-compact.1.gz
+usr/share/man/man1/notmuch-config.1.gz
+usr/share/man/man1/notmuch-count.1.gz
+usr/share/man/man1/notmuch-dump.1.gz
 usr/share/man/man1/notmuch-emacs-mua.1.gz
+usr/share/man/man1/notmuch-insert.1.gz
 usr/share/man/man1/notmuch-new.1.gz
-usr/share/man/man1/notmuch.1.gz
 usr/share/man/man1/notmuch-reindex.1.gz
-usr/share/man/man1/notmuch-address.1.gz
-usr/share/man/man1/notmuch-tag.1.gz
 usr/share/man/man1/notmuch-reply.1.gz
-usr/share/man/man1/notmuch-search.1.gz
 usr/share/man/man1/notmuch-restore.1.gz
-usr/share/man/man1/notmuch-insert.1.gz
+usr/share/man/man1/notmuch-search.1.gz
+usr/share/man/man1/notmuch-setup.1.gz
 usr/share/man/man1/notmuch-show.1.gz
-usr/share/man/man1/notmuch-config.1.gz
+usr/share/man/man1/notmuch-tag.1.gz
+usr/share/man/man1/notmuch.1.gz
+usr/share/man/man5/notmuch-hooks.5.gz
 usr/share/man/man7/notmuch-properties.7.gz
 usr/share/man/man7/notmuch-search-terms.7.gz
diff --git a/debian/python-notmuch.install b/debian/python-notmuch.install
deleted file mode 100644 (file)
index b2cc136..0000000
+++ /dev/null
@@ -1 +0,0 @@
-usr/lib/python2*
index 5a378f6e6fc4285c9a08139b9f2b8beb383fda9f..fa0551a951906e686860d08a78c615f24af6e3b0 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/make -f
 
-export PYBUILD_NAME=notmuch
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
 
 %:
        dh $@ --with python3,elpa
@@ -15,19 +15,25 @@ override_dh_auto_configure:
                --zshcompletiondir=/usr/share/zsh/vendor-completions \
                --localstatedir=/var
 
+override_dh_auto_test:
+       dh_auto_test -- V=1
+
 override_dh_auto_build:
        dh_auto_build -- V=1
-       dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python
+       PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python
+       PYBUILD_NAME=notmuch2 dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python-cffi
        $(MAKE) -C contrib/notmuch-mutt
 
 override_dh_auto_clean:
        dh_auto_clean
-       dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python
+       PYBUILD_NAME=notmuch dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python
+       PYBUILD_NAME=notmuch2 dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python-cffi
        dh_auto_clean --sourcedirectory bindings/ruby
        $(MAKE) -C contrib/notmuch-mutt clean
 
 override_dh_auto_install:
        dh_auto_install
-       dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python
+       PYBUILD_NAME=notmuch dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python
+       PYBUILD_NAME=notmuch2 dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python-cffi
        $(MAKE) -C contrib/notmuch-mutt DESTDIR=$(CURDIR)/debian/tmp install
        dh_auto_install --sourcedirectory bindings/ruby
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
new file mode 100644 (file)
index 0000000..8f266aa
--- /dev/null
@@ -0,0 +1,6 @@
+Bug-Database: https://nmbug.notmuchmail.org/status/
+Bug-Submit: mailto:notmuch@notmuchmail.org
+FAQ: https://notmuchmail.org/faq/
+Repository: https://git.notmuchmail.org/git/notmuch
+Repository-Browse: https://git.notmuchmail.org/git/notmuch
+Screenshots: https://notmuchmail.org/screenshots/
index 0febf170d729167b6697f18fffaae467d8c84259..5f47a1d77d9f45f8adf4e16c9872b88b692c7e75 100644 (file)
@@ -39,8 +39,7 @@ debugger_is_active (void)
 
     sprintf (buf, "/proc/%d/exe", getppid ());
     if (readlink (buf, buf2, sizeof (buf2)) != -1 &&
-       strncmp (basename (buf2), "gdb", 3) == 0)
-    {
+       strncmp (basename (buf2), "gdb", 3) == 0) {
        return true;
     }
 
index da6531248feb7e827c6f5066faaf1049a61a2652..b18a9573c19abb6042bbe2bf29c367453759d0e5 100644 (file)
@@ -53,11 +53,19 @@ function (param_type param, param_type param)
   if/for/while test) and are preceded by a space. The opening brace of
   functions is the exception, and starts on a new line.
 
-* Comments are always C-style /* */ block comments.  They should start
-  with a capital letter and generally be written in complete
-  sentences.  Public library functions are documented immediately
-  before their prototype in lib/notmuch.h.  Internal functions are
-  typically documented immediately before their definition.
+* Opening parens also cuddle, even if the first argument does not fit
+  on the same line.
+
+* Ternary operators that span a line should be parenthesized like as
+  "a ? (\n b ) : c". This is mainly to keep the indentation tools
+  happy.
+
+* Comments are always C-style /* */ block comments, with a leading *
+  each line.  They should start with a capital letter and generally be
+  written in complete sentences.  Public library functions are
+  documented immediately before their prototype in lib/notmuch.h.
+  Internal functions are typically documented immediately before their
+  definition.
 
 * Code lines should be less than 80 columns and comments should be
   wrapped at 70 columns.
diff --git a/devel/author-scan.sh b/devel/author-scan.sh
new file mode 100644 (file)
index 0000000..2d9c4af
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+FILE_EXCLUDE='corpora'
+AUTHOR_EXCLUDE='uncrustify'
+# based on the FSF guideline, for want of a better idea.
+THRESHOLD=15
+
+git ls-files | grep -v -e "$FILE_EXCLUDE" | xargs -n 1 -d \\n \
+                                                  git blame -w --line-porcelain -- | \
+    sed -n "/$AUTHOR_EXCLUDE/d; s/^[aA][uU][tT][hH][Oo][rR] //p" | \
+    sort -fd | uniq -ic | awk "\$1 >= $THRESHOLD" | sort -nr
index 464b9467555655afe0171d268a4c835c85fe5fe7..65dfe0eb78215ec15f5057db1dd7a748cd8d16d6 100644 (file)
@@ -20,7 +20,7 @@
 | q         | notmuch-bury-or-kill-this-buffer       | notmuch-bury-or-kill-this-buffer                      | notmuch-bury-or-kill-this-buffer        |
 | r         | notmuch-search-reply-to-thread-sender  | notmuch-show-reply-sender                             | notmuch-show-reply-sender               |
 | s         | notmuch-search                         | notmuch-search                                        | notmuch-search                          |
-| t         | notmuch-search-filter-by-tag           | toggle-truncate-lines                                 |                                         |
+| t         | notmuch-search-filter-by-tag           | toggle-truncate-lines                                 | notmuch-search-by-tag                   |
 | u         |                                        |                                                       |                                         |
 | v         |                                        |                                                       | notmuch-show-view-all-mime-parts        |
 | w         |                                        | notmuch-show-save-attachments                         | notmuch-show-save-attachments           |
diff --git a/devel/gen-testdb.sh b/devel/gen-testdb.sh
deleted file mode 100755 (executable)
index 61ae48a..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/usr/bin/env bash
-#
-# NAME
-#      gen-testdb.sh - generate test databases
-#
-# SYNOPSIS
-#      gen-testdb.sh -v NOTMUCH-VERSION [-c CORPUS-PATH] [-s TAR-SUFFIX]
-#
-# DESCRIPTION
-#      Generate a tarball containing the specified test corpus and
-#      the corresponding notmuch database, indexed using a specific
-#      version of notmuch, resulting in a specific version of the
-#      database.
-#
-#      The specific version of notmuch will be built on the fly.
-#      Therefore the script must be run within a git repository to be
-#      able to build the old versions of notmuch.
-#
-#      This script reuses the test infrastructure, and the script
-#      must be run from within the test directory.
-#
-#      The output tarballs, named database-<TAR-SUFFIX>.tar.gz, are
-#      placed in the test/test-databases directory.
-#
-# OPTIONS
-#      -v NOTMUCH-VERSION
-#              Notmuch version in terms of a git tag or commit to use
-#              for generating the database. Required.
-#
-#      -c CORPUS-PATH
-#              Path to a corpus to use for generating the
-#              database. Due to CWD changes within the test
-#              infrastructure, use absolute paths. Defaults to the
-#              test corpus.
-#
-#      -s TAR-SUFFIX
-#              Suffix for the tarball basename. Empty by default.
-#
-# EXAMPLE
-#
-#      Generate a database indexed with notmuch 0.17. Use the default
-#      test corpus. Name the tarball database-v1.tar.gz to reflect
-#      the fact that notmuch 0.17 used database version 1.
-#
-#      $ cd test
-#      $ ../devel/gen-testdb.sh -v 0.17 -s v1
-#
-# CAVEATS
-#      Test infrastructure options won't work.
-#
-#      Any existing databases with the same name will be overwritten.
-#
-#      It may not be possible to build old versions of notmuch with
-#      the set of dependencies that satisfy building the current
-#      version of notmuch.
-#
-# AUTHOR
-#      Jani Nikula <jani@nikula.org>
-#
-# LICENSE
-#      Same as notmuch test infrastructure (GPLv2+).
-#
-
-test_description="database generation abusing test infrastructure"
-
-# immediate exit on subtest failure; see test_failure_ in test-lib.sh
-immediate=t
-
-VERSION=
-CORPUS=
-SUFFIX=
-
-while getopts v:c:s: opt; do
-    case "$opt" in
-       v) VERSION="$OPTARG";;
-       c) CORPUS="$OPTARG";;
-       s) SUFFIX="-$OPTARG";;
-    esac
-done
-shift `expr $OPTIND - 1`
-
-. ./test-lib.sh || exit 1
-
-SHORT_CORPUS=$(basename ${CORPUS:-database})
-DBNAME=${SHORT_CORPUS}${SUFFIX}
-TARBALLNAME=${DBNAME}.tar.xz
-
-CORPUS=${CORPUS:-${TEST_DIRECTORY}/corpus}
-
-test_expect_code 0 "notmuch version specified on the command line" \
-    "test -n ${VERSION}"
-
-test_expect_code 0 "the specified version ${VERSION} refers to a commit" \
-    "git show ${VERSION} >/dev/null 2>&1"
-
-BUILD_DIR="notmuch-${VERSION}"
-test_expect_code 0 "generate snapshot of notmuch version ${VERSION}" \
-    "git -C $TEST_DIRECTORY/.. archive --prefix=${BUILD_DIR}/ --format=tar ${VERSION} | tar x"
-
-# force version string
-git describe --match '[0-9.]*' ${VERSION} > ${BUILD_DIR}/version
-
-test_expect_code 0 "configure and build notmuch version ${VERSION}" \
-    "make -C ${BUILD_DIR}"
-
-# use the newly built notmuch
-export PATH=./${BUILD_DIR}:$PATH
-
-test_begin_subtest "verify the newly built notmuch version"
-test_expect_equal "`notmuch --version`" "notmuch `cat ${BUILD_DIR}/version`"
-
-# replace the existing mails, if any, with the specified corpus
-rm -rf ${MAIL_DIR}
-cp -a ${CORPUS} ${MAIL_DIR}
-
-test_expect_code 0 "index the corpus" \
-    "notmuch new"
-
-# wrap the resulting mail store and database in a tarball
-
-cp -a ${MAIL_DIR} ${TMP_DIRECTORY}/${DBNAME}
-tar Jcf ${TMP_DIRECTORY}/${TARBALLNAME} -C ${TMP_DIRECTORY} ${DBNAME}
-mkdir -p  ${TEST_DIRECTORY}/test-databases
-cp -a ${TMP_DIRECTORY}/${TARBALLNAME} ${TEST_DIRECTORY}/test-databases
-test_expect_code 0 "create the output tarball ${TARBALLNAME}" \
-    "test -f ${TEST_DIRECTORY}/test-databases/${TARBALLNAME}"
-
-# generate a checksum file
-test_expect_code 0 "compute checksum" \
-    "(cd ${TEST_DIRECTORY}/test-databases/ && sha256sum ${TARBALLNAME} > ${TARBALLNAME}.sha256)"
-test_done
index c35dd75d4243dd8f415e345ff1a4fc69c18ce0b7..043c186369c1f55fce114b230746f138d7bc2345 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (c) 2011-2014 David Bremner <david@tethera.net>
 #                         W. Trevor King <wking@tremily.us>
index eaceb2ce47123941aecaca2c888e5b855232d4b5..18a0bc70c6aaebe0abe97ad04a9692ee0d846b0d 100755 (executable)
@@ -1,10 +1,9 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (c) 2011-2012 David Bremner <david@tethera.net>
 #
 # dependencies
-#       - python 2.6 for json
-#       - argparse; either python 2.7, or install separately
+#       - python3 or python2.7
 #
 # 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
diff --git a/devel/printmimestructure b/devel/printmimestructure
deleted file mode 100755 (executable)
index 70e0a5c..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
-# License: GPLv3+
-
-# This script reads a MIME message from stdin and produces a treelike
-# representation on it stdout.
-
-# Example:
-#
-# 0 dkg@alice:~$ printmimestructure < 'Maildir/cur/1269025522.M338697P12023.monkey,S=6459,W=6963:2,Sa'
-# └┬╴multipart/signed 6546 bytes
-#  ├─╴text/plain inline 895 bytes
-#  └─╴application/pgp-signature inline [signature.asc] 836 bytes
-# 0 dkg@alice:~$
-
-
-# If you want to number the parts, i suggest piping the output through
-# something like "cat -n"
-
-from __future__ import print_function
-
-import email
-import sys
-
-def print_part(z, prefix):
-    fname = '' if z.get_filename() is None else ' [' + z.get_filename() + ']'
-    cset = '' if z.get_charset() is None else ' (' + z.get_charset() + ')'
-    disp = z.get_params(None, header='Content-Disposition')
-    if (disp is None):
-        disposition = ''
-    else:
-        disposition = ''
-        for d in disp:
-            if d[0] in [ 'attachment', 'inline' ]:
-                disposition = ' ' + d[0]
-    if z.is_multipart():
-        nbytes = len(z.as_string())
-    else:
-        nbytes = len(z.get_payload())
-
-    print('{}{}{}{}{} {:d} bytes'.format(
-        prefix,
-        z.get_content_type(),
-        cset,
-        disposition,
-        fname,
-        nbytes,
-    ))
-
-def test(z, prefix=''):
-    if (z.is_multipart()):
-        print_part(z, prefix+'┬╴')
-        if prefix.endswith('└'):
-            prefix = prefix.rpartition('└')[0] + ' '
-        if prefix.endswith('├'):
-            prefix = prefix.rpartition('├')[0] + '│'
-        parts = z.get_payload()
-        i = 0
-        while (i < parts.__len__()-1):
-            test(parts[i], prefix + '├')
-            i += 1
-        test(parts[i], prefix + '└')
-        # FIXME: show epilogue?
-    else:
-        print_part(z, prefix+'─╴')
-
-test(email.message_from_file(sys.stdin), '└')
index 7ba948224bc732d807b80f7c3886ceb925af2426..23c29eaafcdba5b37098cd4058fe53262be7b8da 100755 (executable)
@@ -29,7 +29,7 @@ append_emsg ()
        emsgs="${emsgs:+$emsgs\n}  $1"
 }
 
-for f in ./version debian/changelog NEWS "$PV_FILE"
+for f in ./version.txt debian/changelog NEWS "$PV_FILE"
 do
        if   [ ! -f "$f" ]; then append_emsg "File '$f' is missing"
        elif [ ! -r "$f" ]; then append_emsg "File '$f' is unreadable"
@@ -53,7 +53,7 @@ then
 else
        echo "Reading './version' file failed (surprisingly!)"
        exit 1
-fi < ./version
+fi < ./version.txt
 
 readonly VERSION
 
@@ -109,7 +109,7 @@ else
 fi
 
 echo -n "Checking that python bindings version is $VERSION... "
-py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"`
+py_version=`python3 -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"`
 if [ "$py_version" = "$VERSION" ]
 then
        echo Yes.
@@ -178,10 +178,7 @@ esac
 year=`exec date +%Y`
 echo -n "Checking that copyright in documentation contains 2009-$year... "
 # Read the value of variable `copyright' defined in 'doc/conf.py'.
-# As __file__ is not defined when python command is given from command line,
-# it is defined before contents of 'doc/conf.py' (which dereferences __file__)
-# is executed.
-copyrightline=`exec python -c "with open('doc/conf.py') as cf: __file__ = ''; exec(cf.read()); print(copyright)"`
+copyrightline=$(grep ^copyright doc/conf.py)
 case $copyrightline in
        *2009-$year*)
                echo Yes. ;;
index 041f6216fbbf293554b480e50819f057ad009842..585d6242deb198a83be227bab381a425c5fa4799 100755 (executable)
@@ -44,28 +44,10 @@ while looking at: " pdir "emacs\n\nexit emacs (y or n)? ")
        try-notmuch-emacs-directory (concat pdir "emacs/")
        load-path (cons try-notmuch-emacs-directory load-path)))
 
-;; they say advice doesn't work for primitives (functions from c source)
-;; well, these 'before' advice works for emacs 23.1 - 24.5 (at least)
-;; ...and for our purposes 24.3 is enough (there is no load-prefer-newer there)
-;; note also that the old, "obsolete" defadvice mechanism was used, but that
-;; is the only one available for emacs 23 and 24 up to 24.3.
-
-(if (boundp 'load-prefer-newer)
-    (defadvice require (before before-require activate)
-      (unless (featurep feature)
-       (message "require: %s" feature)))
-  ;; else: special require "short-circuit"; after load feature is provided...
-  ;; ... in notmuch sources we always use require and there are no loops
-  (defadvice require (before before-require activate)
-    (unless (featurep feature)
-      (message "require: %s" feature)
-      (let ((name (symbol-name feature)))
-       (if (and (string-match "^notmuch" name)
-                (file-newer-than-file-p
-                 (concat try-notmuch-emacs-directory name ".el")
-                 (concat try-notmuch-emacs-directory name ".elc")))
-           (load (concat try-notmuch-emacs-directory name ".el") nil nil t t)
-         )))))
+(define-advice require
+    (:before (feature &optional _filename _noerror) notmuch)
+  (unless (featurep feature)
+    (message "require: %s" feature)))
 
 (insert "Found notmuch emacs client in " try-notmuch-emacs-directory "\n")
 
index 6a8769c63902e76835d27c5d32a0fd7a4e32595c..c36c33d67052e8ee034d44478de7473e2f67cba4 100644 (file)
@@ -117,3 +117,5 @@ align_right_cmt_span        = 8             # align comments span this much in func
 cmt_star_cont          = true
 
 # indent_brace         = 0
+
+indent_class = true
index d733b51e5a0789d4d007b14ce2af76ccacb3daf9..60bd718469d43ae4393ca6ed8bfa5dab951fa0a4 100644 (file)
@@ -1,10 +1,10 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := doc
 
 # You can set these variables from the command line.
 SPHINXOPTS    := -q
-SPHINXBUILD   = HAVE_EMACS=${HAVE_EMACS} WITH_EMACS=${WITH_EMACS} sphinx-build
+SPHINXBUILD   = sphinx-build
 DOCBUILDDIR      := $(dir)/_build
 
 # Internal variables.
@@ -29,8 +29,8 @@ MAN1_TEXI := $(patsubst $(srcdir)/doc/man1/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$
 MAN5_TEXI := $(patsubst $(srcdir)/doc/man5/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$(MAN5_RST))
 MAN7_TEXI := $(patsubst $(srcdir)/doc/man7/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$(MAN7_RST))
 INFO_TEXI_FILES := $(MAN1_TEXI) $(MAN5_TEXI) $(MAN7_TEXI)
-ifeq ($(HAVE_EMACS)$(WITH_EMACS),11)
-       INFO_TEXI_FILES := $(INFO_TEXI_FILES) $(DOCBUILDDIR)/texinfo/notmuch-emacs.texi
+ifeq ($(WITH_EMACS),1)
+       INFO_TEXI_FILES += $(DOCBUILDDIR)/texinfo/notmuch-emacs.texi
 endif
 
 INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info)
@@ -40,7 +40,7 @@ INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info)
 .PHONY: install-man build-man apidocs install-apidocs
 
 %.gz: %
-       rm -f $@ && gzip --stdout $^ > $@
+       rm -f $@ && gzip --no-name --stdout $^ > $@
 
 ifeq ($(WITH_EMACS),1)
 $(DOCBUILDDIR)/.roff.stamp sphinx-html sphinx-texinfo: docstring.stamp
index 8afff929071078e4bbf7eeb8508bce6ae4a85a9a..11bed51d7596100ac2ca381792661e8378bbc5dd 100644 (file)
@@ -4,6 +4,8 @@
 import sys
 import os
 
+extensions = [ 'sphinx.ext.autodoc' ]
+
 # The suffix of source filenames.
 source_suffix = '.rst'
 
@@ -12,16 +14,26 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'notmuch'
-copyright = u'2009-2019, Carl Worth and many others'
+copyright = u'2009-2020, Carl Worth and many others'
 
 location = os.path.dirname(__file__)
 
 for pathdir in ['.', '..']:
-    version_file = os.path.join(location,pathdir,'version')
+    version_file = os.path.join(location,pathdir,'version.txt')
     if os.path.exists(version_file):
         with open(version_file,'r') as infile:
             version=infile.read().replace('\n','')
 
+# for autodoc
+sys.path.insert(0, os.path.join(location, '..', 'bindings', 'python-cffi', 'notmuch2'))
+
+# read generated config
+for pathdir in ['.', '..']:
+    conf_file = os.path.join(location,pathdir,'sphinx.config')
+    if os.path.exists(conf_file):
+        with open(conf_file,'r') as infile:
+            exec(''.join(infile.readlines()))
+
 # The full version, including alpha/beta/rc tags.
 release = version
 
@@ -29,12 +41,23 @@ release = version
 # directories to ignore when looking for source files.
 exclude_patterns = ['_build']
 
-# If we don't have emacs (or the user configured --without-emacs),
-# don't build the notmuch-emacs docs, as they need emacs to generate
-# the docstring include files
-if os.environ.get('HAVE_EMACS') != '1' or os.environ.get('WITH_EMACS') != '1':
+if tags.has('WITH_EMACS'):
+    # Hacky reimplementation of include to workaround limitations of
+    # sphinx-doc
+    lines = ['.. include:: /../emacs/rstdoc.rsti\n\n'] # in the source tree
+    for file in ('notmuch.rsti', 'notmuch-lib.rsti', 'notmuch-show.rsti', 'notmuch-tag.rsti'):
+        lines.extend(open(rsti_dir+'/'+file))
+    rst_epilog = ''.join(lines)
+    del lines
+else:
+    # If we don't have emacs (or the user configured --without-emacs),
+    # don't build the notmuch-emacs docs, as they need emacs to generate
+    # the docstring include files
     exclude_patterns.append('notmuch-emacs.rst')
 
+if not tags.has('WITH_PYTHON'):
+    exclude_patterns.append('python-bindings.rst')
+
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = 'sphinx'
 
index 2ca15d41e7a824acb876536b144f9b4b5ec15790..a2c4fd07400c9e0bdbfd885062c0531ee649dbd0 100644 (file)
@@ -264,12 +264,10 @@ GENERATE_TAGFILE       =
 ALLEXTERNALS           = NO
 EXTERNAL_GROUPS        = NO
 EXTERNAL_PAGES         = NO
-PERL_PATH              = /usr/bin/perl
 #---------------------------------------------------------------------------
 # Configuration options related to the dot tool
 #---------------------------------------------------------------------------
 CLASS_DIAGRAMS         = NO
-MSCGEN_PATH            =
 HIDE_UNDOC_RELATIONS   = YES
 HAVE_DOT               = NO
 DOT_NUM_THREADS        = 0
index 4440d93aad285ba1d3da14d692ba9f54c47af3c6..a3bf348084b8c2f8140515328a107436ffdb732f 100644 (file)
@@ -26,6 +26,7 @@ Contents:
    man7/notmuch-search-terms
    man1/notmuch-show
    man1/notmuch-tag
+   python-bindings
 
 Indices and tables
 ==================
index 28487079b43b75bc07714a63ee9d3d81402c16f7..769f336a33afac4dfeb54ec259940257777e7f71 100644 (file)
@@ -38,7 +38,7 @@ programmatically as described in the SYNOPSIS above.
     Every configuration item is printed to stdout, each on a separate
     line of the form::
 
-        *section*.\ *item*\ =\ *value*
+        section.item=value
 
     No additional whitespace surrounds the dot or equals sign
     characters. In a multiple-value item (a list), the values are
@@ -134,14 +134,6 @@ The available configuration items are described below.
 
     Default: ``true``.
 
-**crypto.gpg_path**
-    Name (or full path) of gpg binary to use in verification and
-    decryption of PGP/MIME messages.  NOTE: This configuration item is
-    deprecated, and will be ignored if notmuch is built against GMime
-    3.0 or later.
-
-    Default: ``gpg``.
-
 **index.decrypt** **[STORED IN DATABASE]**
     Policy for decrypting encrypted messages during indexing.  Must be
     one of: ``false``, ``auto``, ``nostash``, or ``true``.
@@ -206,8 +198,9 @@ The available configuration items are described below.
 
 **built_with.<name>**
     Compile time feature <name>. Current possibilities include
-    "compact" (see **notmuch-compact(1)**) and "field_processor" (see
-    **notmuch-search-terms(7)**).
+    "retry_lock" (configure option, included by default).
+    (since notmuch 0.30, "compact" and "field_processor" are
+    always included.)
 
 **query.<name>** **[STORED IN DATABASE]**
     Expansion for named query called <name>. See
index d2cd8da55cb99e0d20792fdbe326e415bd0109f7..fecfd08a360d0143586352387a5016a1fd25ee2f 100644 (file)
@@ -128,9 +128,9 @@ OPTION SYNTAX
 -------------
 
 All options accepting an argument can be used with '=' or ':' as a
-separator. For the cases where it's not ambiguous (in particular
-excluding boolean options), a space can also be used. The following
-are all equivalent:
+separator. Except for boolean options (which would be ambiguous), a
+space can also be used as a separator. The following are all
+equivalent:
 
 ::
 
index 802e6763341e55db0699121a391e07262368e3a2..a7d91d674a89f92e37989e6bb1ca3846839360c2 100644 (file)
@@ -109,6 +109,30 @@ of its normal activity.
     example, an AES-128 key might be stashed in a notmuch property as:
     ``session-key=7:14B16AF65536C28AF209828DFE34C9E0``.
 
+**index.repaired**
+
+    Some messages arrive in forms that are confusing to view; they can
+    be mangled by mail transport agents, or the sending mail user
+    agent may structure them in a way that is confusing.  If notmuch
+    knows how to both detect and repair such a problematic message, it
+    will do so during indexing.
+
+    If it applies a message repair during indexing, it will use the
+    ``index.repaired`` property to note the type of repair(s) it
+    performed.
+
+    ``index.repaired=skip-protected-headers-legacy-display`` indicates
+    that when indexing the cleartext of an encrypted message, notmuch
+    skipped over a "legacy-display" text/rfc822-headers part that it
+    found in that message, since it was able to index the built-in
+    protected headers directly.
+
+    ``index.repaired=mixedup`` indicates the repair of a "Mixed Up"
+    encrypted PGP/MIME message, a mangling typically produced by
+    Microsoft's Exchange MTA.  See
+    https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling
+    for more information.
+
 SEE ALSO
 ========
 
index 1dd2dc5813f5c40da3b9c95b15aa8eaae0491542..28fca7379b7bfb4e84a3e1abe0b79eab88e9fdea 100644 (file)
@@ -37,9 +37,8 @@ In addition to free text, the following prefixes can be used to force
 terms to match against specific portions of an email, (where <brackets>
 indicate user-supplied values).
 
-If notmuch is built with **Xapian Field Processors** (see below) some
-of the prefixes with <regex> forms can be also used to restrict the
-results to those whose value matches a regular expression (see
+Some of the prefixes with <regex> forms can be also used to restrict
+the results to those whose value matches a regular expression (see
 **regex(7)**) delimited with //, for example::
 
    notmuch search 'from:"/bob@.*[.]example[.]com/"'
@@ -87,8 +86,7 @@ thread:<thread-id>
     of output from **notmuch search**
 
 thread:{<notmuch query>}
-    If notmuch is built with **Xapian Field Processors** (see below),
-    threads may be searched for indirectly by providing an arbitrary
+    Threads may be searched for indirectly by providing an arbitrary
     notmuch query in **{}**. For example, the following returns
     threads containing a message from mallory and one (not necessarily
     the same message) with Subject containing the word "crypto".
@@ -158,9 +156,7 @@ lastmod:<initial-revision>..<final-revision>
 
 query:<name>
     The **query:** prefix allows queries to refer to previously saved
-    queries added with **notmuch-config(1)**. Named queries are only
-    available if notmuch is built with **Xapian Field Processors**
-    (see below).
+    queries added with **notmuch-config(1)**.
 
 property:<key>=<value>
     The **property:** prefix searches for messages with a particular
@@ -353,23 +349,21 @@ since 1970-01-01 00:00:00 UTC. For example:
 
     date:@<initial-timestamp>..@<final-timestamp>
 
-date:<expr>..! can be used as a shorthand for date:<expr>..<expr>. The
-expansion takes place before interpretation, and thus, for example,
-date:monday..! matches from the beginning of Monday until the end of
-Monday.
-With **Xapian Field Processor** support (see below), non-range
-date queries such as date:yesterday will work, but otherwise
-will give unexpected results; if in doubt use date:yesterday..!
-
-Currently, we do not support spaces in range expressions. You can
+Currently, spaces in range expressions are not supported. You can
 replace the spaces with '\_', or (in most cases) '-', or (in some cases)
 leave the spaces out altogether. Examples in this man page use spaces
 for clarity.
 
-Open-ended ranges are supported (since Xapian 1.2.1), i.e. it's possible
-to specify date:..<until> or date:<since>.. to not limit the start or
-end time, respectively. Pre-1.2.1 Xapian does not report an error on
-open ended ranges, but it does not work as expected either.
+Open-ended ranges are supported. I.e. it's possible to specify
+date:..<until> or date:<since>.. to not limit the start or
+end time, respectively.
+
+Single expression
+-----------------
+
+date:<expr> works as a shorthand for date:<expr>..<expr>.
+For example, date:monday matches from the beginning of Monday until
+the end of Monday.
 
 Relative date and time
 ----------------------
@@ -446,24 +440,6 @@ Time zones
 
 Some time zone codes, e.g. UTC, EET.
 
-XAPIAN FIELD PROCESSORS
-=======================
-
-Certain optional features of the notmuch query processor rely on the
-presence of the Xapian field processor API. You can determine if your
-notmuch was built against a sufficiently recent version of Xapian by running
-
-::
-
-  % notmuch config get built_with.field_processor
-
-Currently the following features require field processor support:
-
-- non-range date queries, e.g. "date:today"
-- named queries e.g. "query:my_special_query"
-- regular expression searches, e.g. "subject:/^\\[SPAM\\]/"
-- thread subqueries, e.g. "thread:{from:bob}"
-
 SEE ALSO
 ========
 
index 1655e2f0a7ad5b160130580f0e9463706b803e72..de47b72688e3a07a96fd4c925e2a833e1e41311f 100644 (file)
@@ -377,13 +377,3 @@ suffix exist it will be read instead (just one of these, chosen in this
 order). Most often users create ``~/.emacs.d/notmuch-config.el`` and just
 work with it. If Emacs was invoked with the ``-q`` or ``--no-init-file``
 options, ``notmuch-init-file`` is not read.
-
-.. include:: ../emacs/rstdoc.rsti
-
-.. include:: ../emacs/notmuch.rsti
-
-.. include:: ../emacs/notmuch-lib.rsti
-
-.. include:: ../emacs/notmuch-show.rsti
-
-.. include:: ../emacs/notmuch-tag.rsti
diff --git a/doc/python-bindings.rst b/doc/python-bindings.rst
new file mode 100644 (file)
index 0000000..e1ad26a
--- /dev/null
@@ -0,0 +1,5 @@
+Python Bindings
+===============
+
+.. automodule:: notmuch2
+   :members:
index 40b7c14fff280e84bcaae09fd706aab4afa6ba6d..d1b320c33d28a7303c4977ad692daf5da03ef5ae 100644 (file)
@@ -1,4 +1,4 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := emacs
 emacs_sources := \
@@ -47,7 +47,7 @@ emacs_images := \
 emacs_bytecode = $(emacs_sources:.el=.elc)
 emacs_docstrings = $(emacs_sources:.el=.rsti)
 
-ifneq ($(HAVE_SPHINX)$(HAVE_EMACS),11)
+ifneq ($(HAVE_SPHINX)$(WITH_EMACS),11)
 docstring.stamp:
        @echo "Missing prerequisites, not collecting docstrings"
 else
@@ -60,7 +60,7 @@ endif
 # the byte compiler may load an old .elc file when processing a
 # "require" or we may fail to rebuild a .elc that depended on a macro
 # from an updated file.
-ifeq ($(HAVE_EMACS),1)
+ifeq ($(WITH_EMACS),1)
 $(dir)/.eldeps: $(dir)/Makefile.local $(dir)/make-deps.el $(emacs_sources)
        $(call quiet,EMACS) --directory emacs -batch -l make-deps.el \
                -f batch-make-deps $(emacs_sources) > $@.tmp && \
@@ -82,7 +82,7 @@ $(dir)/notmuch-lib.elc: $(dir)/notmuch-version.elc
 endif
 CLEAN+=$(dir)/.eldeps $(dir)/.eldeps.tmp $(dir)/.eldeps.x
 
-ifeq ($(HAVE_EMACS),1)
+ifeq ($(WITH_EMACS),1)
 %.elc: %.el $(global_deps)
        $(call quiet,EMACS) --directory emacs -batch -f batch-byte-compile $<
 %.rsti: %.el
@@ -103,10 +103,8 @@ endif
        rm -r .elpa-build
 
 ifeq ($(WITH_EMACS),1)
-ifeq ($(HAVE_EMACS),1)
 all: $(emacs_bytecode) $(emacs_docstrings)
 install-emacs: $(emacs_bytecode)
-endif
 
 install: install-emacs
 endif
@@ -115,7 +113,7 @@ endif
 install-emacs: $(emacs_sources) $(emacs_images)
        mkdir -p "$(DESTDIR)$(emacslispdir)"
        install -m0644 $(emacs_sources) "$(DESTDIR)$(emacslispdir)"
-ifeq ($(HAVE_EMACS),1)
+ifeq ($(WITH_EMACS),1)
        install -m0644 $(emacs_bytecode) "$(DESTDIR)$(emacslispdir)"
 endif
        mkdir -p "$(DESTDIR)$(emacsetcdir)"
index 350d537fb264273d6f4277eb3e0941d13c6d8d29..39a8de2bcd2dfb18bcb3426f39e361a9e0eba8be 100644 (file)
@@ -1,6 +1,6 @@
 ;;; coolj.el --- automatically wrap long lines  -*- coding:utf-8 -*-
 
-;; Copyright (C) 2000, 2001, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+;; Copyright (C) 2000, 2001, 2004-2009 Free Software Foundation, Inc.
 
 ;; Authors:    Kai Grossjohann <Kai.Grossjohann@CS.Uni-Dortmund.DE>
 ;;             Alex Schroeder <alex@gnu.org>
@@ -107,12 +107,12 @@ not need to be wrapped, move point to the next line and return t."
 If the line should not be broken, return nil; point remains on the
 line."
   (move-to-column fill-column)
-  (if (and (re-search-forward "[^ ]" (line-end-position) 1)
-           (> (current-column) fill-column))
-      ;; This line is too long.  Can we break it?
-      (or (coolj-find-break-backward prefix)
-          (progn (move-to-column fill-column)
-                 (coolj-find-break-forward)))))
+  (and (re-search-forward "[^ ]" (line-end-position) 1)
+       (> (current-column) fill-column)
+       ;; This line is too long.  Can we break it?
+       (or (coolj-find-break-backward prefix)
+          (progn (move-to-column fill-column)
+                 (coolj-find-break-forward)))))
 
 (defun coolj-find-break-backward (prefix)
   "Move point backward to the first available breakpoint and return t.
@@ -135,12 +135,12 @@ If no breakpoint is found, return nil."
 If no break point is found, return nil."
   (and (search-forward " " (line-end-position) 1)
        (progn (skip-chars-forward " " (line-end-position))
-              (null (eolp)))
+             (null (eolp)))
        (if (and fill-nobreak-predicate
-                (run-hook-with-args-until-success
-                 'fill-nobreak-predicate))
-           (coolj-find-break-forward)
-         t)))
+               (run-hook-with-args-until-success
+                'fill-nobreak-predicate))
+          (coolj-find-break-forward)
+        t)))
 
 (provide 'coolj)
 
index 5b6db6986dc0953d41a3564ede46a6aba7ffc11c..a7699fb1dd05cff9c167217d6671cb0b891dd628 100644 (file)
@@ -1,4 +1,4 @@
-;; make-deps.el --- compute make dependencies for Elisp sources
+;;; make-deps.el --- compute make dependencies for Elisp sources
 ;;
 ;; Copyright © Austin Clements
 ;;
@@ -23,7 +23,6 @@
 
 (defun batch-make-deps ()
   "Invoke `make-deps' for each file on the command line."
-
   (setq debug-on-error t)
   (dolist (file command-line-args-left)
     (let ((default-directory command-line-default-directory))
@@ -37,8 +36,8 @@
 This prints make dependencies to `standard-output' based on the
 top-level `require' expressions in the current buffer.  Paths in
 rules will be given relative to DIR, or `default-directory'."
-
-  (setq dir (or dir default-directory))
+  (unless dir
+    (setq dir default-directory))
   (save-excursion
     (goto-char (point-min))
     (condition-case nil
index 64887a43fe652b04332e0e3ed9b15577656cc9f8..8a6d299cb3061bc7234c932a59a73f7e6da87e21 100644 (file)
 (declare-function company-manual-begin "company")
 
 (defvar notmuch-address-last-harvest 0
-  "Time of last address harvest")
+  "Time of last address harvest.")
 
 (defvar notmuch-address-completions (make-hash-table :test 'equal)
   "Hash of email addresses for completion during email composition.
-  This variable is set by calling `notmuch-address-harvest'.")
+This variable is set by calling `notmuch-address-harvest'.")
 
 (defvar notmuch-address-full-harvest-finished nil
-  "t indicates that full completion address harvesting has been
-finished. Use notmuch-address--harvest-ready to access as that
-will load a saved hash if necessary (and available).")
+  "t indicates that full completion address harvesting has been finished.
+Use notmuch-address--harvest-ready to access as that will load a
+saved hash if necessary (and available).")
 
 (defun notmuch-address--harvest-ready ()
   "Return t if there is a full address hash available.
@@ -54,9 +54,9 @@ If it is a string then that string should be an external program
 which must take a single argument (searched string) and output a
 list of completion candidates, one per line.
 
-Alternatively, it can be the symbol 'internal, in which case
+Alternatively, it can be the symbol `internal', in which case
 internal completion is used; the variable
-`notmuch-address-internal-completion` can be used to customize
+`notmuch-address-internal-completion' can be used to customize
 this case.
 
 Finally, if this variable is nil then address completion is
@@ -72,12 +72,12 @@ disabled."
 (defcustom notmuch-address-internal-completion '(sent nil)
   "Determines how internal address completion generates candidates.
 
-This should be a list of the form '(DIRECTION FILTER), where
- DIRECTION is either sent or received and specifies whether the
- candidates are searched in messages sent by the user or received
- by the user (note received by is much faster), and FILTER is
- either nil or a filter-string, such as \"date:1y..\" to append
- to the query."
+This should be a list of the form (DIRECTION FILTER), where
+DIRECTION is either sent or received and specifies whether the
+candidates are searched in messages sent by the user or received
+by the user (note received by is much faster), and FILTER is
+either nil or a filter-string, such as \"date:1y..\" to append to
+the query."
   :type '(list :tag "Use internal address completion"
               (radio
                :tag "Base completion on messages you have"
@@ -101,8 +101,8 @@ This should be a list of the form '(DIRECTION FILTER), where
   "Filename to save the cached completion addresses.
 
 All the addresses notmuch uses for address completion will be
-cached in this file. This has obvious privacy implications so you
-should make sure it is not somewhere publicly readable."
+cached in this file.  This has obvious privacy implications so
+you should make sure it is not somewhere publicly readable."
   :type '(choice (const :tag "Off" nil)
                 (file :tag "Filename"))
   :group 'notmuch-send
@@ -110,12 +110,14 @@ should make sure it is not somewhere publicly readable."
   :group 'notmuch-external)
 
 (defcustom notmuch-address-selection-function 'notmuch-address-selection-function
-  "The function to select address from given list. The function is
-called with PROMPT, COLLECTION, and INITIAL-INPUT as arguments
-(subset of what `completing-read' can be called with).
-While executed the value of `completion-ignore-case' is t.
-See documentation of function `notmuch-address-selection-function'
-to know how address selection is made by default."
+  "The function to select address from given list.
+
+The function is called with PROMPT, COLLECTION, and INITIAL-INPUT
+as arguments (subset of what `completing-read' can be called
+with).  While executed the value of `completion-ignore-case'
+is t.  See documentation of function
+`notmuch-address-selection-function' to know how address
+selection is made by default."
   :type 'function
   :group 'notmuch-send
   :group 'notmuch-address
@@ -126,8 +128,7 @@ to know how address selection is made by default."
 
 The completed address is passed as an argument to each function.
 Note that this hook will be invoked for completion in headers
-matching `notmuch-address-completion-headers-regexp'.
-"
+matching `notmuch-address-completion-headers-regexp'."
   :type 'hook
   :group 'notmuch-address
   :group 'notmuch-hooks)
@@ -147,21 +148,21 @@ matching `notmuch-address-completion-headers-regexp'.
   (message "calling notmuch-address-message-insinuate is no longer needed"))
 
 (defcustom notmuch-address-use-company t
-  "If available, use company mode for address completion"
+  "If available, use company mode for address completion."
   :type 'boolean
   :group 'notmuch-send
   :group 'notmuch-address)
 
 (defun notmuch-address-setup ()
   (let* ((setup-company (and notmuch-address-use-company
-                          (require 'company nil t)))
+                            (require 'company nil t)))
         (pair (cons notmuch-address-completion-headers-regexp
-                      #'notmuch-address-expand-name)))
-      (when setup-company
-       (notmuch-company-setup))
-      (unless (member pair message-completion-alist)
-       (setq message-completion-alist
-             (push pair message-completion-alist)))))
+                    #'notmuch-address-expand-name)))
+    (when setup-company
+      (notmuch-company-setup))
+    (unless (member pair message-completion-alist)
+      (setq message-completion-alist
+           (push pair message-completion-alist)))))
 
 (defun notmuch-address-toggle-internal-completion ()
   "Toggle use of internal completion for current buffer.
@@ -171,11 +172,11 @@ toggles the setting in this buffer."
   (interactive)
   (if (local-variable-p 'notmuch-address-command)
       (kill-local-variable 'notmuch-address-command)
-    (notmuch-setq-local notmuch-address-command 'internal))
-  (if (boundp 'company-idle-delay)
-      (if (local-variable-p 'company-idle-delay)
-         (kill-local-variable 'company-idle-delay)
-       (notmuch-setq-local company-idle-delay nil))))
+    (setq-local notmuch-address-command 'internal))
+  (when (boundp 'company-idle-delay)
+    (if (local-variable-p 'company-idle-delay)
+       (kill-local-variable 'company-idle-delay)
+      (setq-local company-idle-delay nil))))
 
 (defun notmuch-address-matching (substring)
   "Returns a list of completion candidates matching SUBSTRING.
@@ -189,17 +190,18 @@ The candidates are taken from `notmuch-address-completions'."
     candidates))
 
 (defun notmuch-address-options (original)
-  "Returns a list of completion candidates. Uses either
-elisp-based implementation or older implementation requiring
-external commands."
+  "Return a list of completion candidates.
+Use either elisp-based implementation or older implementation
+requiring external commands."
   (cond
    ((eq notmuch-address-command 'internal)
     (unless (notmuch-address--harvest-ready)
       ;; First, run quick synchronous harvest based on what the user
-      ;; entered so far
+      ;; entered so far.
       (notmuch-address-harvest original t))
     (prog1 (notmuch-address-matching original)
-      ;; Then start the (potentially long-running) full asynchronous harvest if necessary
+      ;; Then start the (potentially long-running) full asynchronous
+      ;; harvest if necessary.
       (notmuch-address-harvest-trigger)))
    (t
     (process-lines notmuch-address-command original))))
@@ -242,7 +244,8 @@ external commands."
            (push chosen notmuch-address-history)
            (delete-region beg end)
            (insert chosen)
-           (run-hook-with-args 'notmuch-address-post-completion-functions chosen))
+           (run-hook-with-args 'notmuch-address-post-completion-functions
+                               chosen))
        (message "No matches.")
        (ding))))
    (t nil)))
@@ -251,20 +254,20 @@ external commands."
 (defun notmuch-address-locate-command (command)
   "Return non-nil if `command' is an executable either on
 `exec-path' or an absolute pathname."
-  (when (stringp command)
-    (if (and (file-name-absolute-p command)
-            (file-executable-p command))
-       command
-      (setq command (file-name-nondirectory command))
-      (catch 'found-command
-       (let (bin)
-         (dolist (dir exec-path)
-           (setq bin (expand-file-name command dir))
-           (when (or (and (file-executable-p bin)
-                          (not (file-directory-p bin)))
-                     (and (file-executable-p (setq bin (concat bin ".exe")))
-                          (not (file-directory-p bin))))
-             (throw 'found-command bin))))))))
+  (and (stringp command)
+       (if (and (file-name-absolute-p command)
+               (file-executable-p command))
+          command
+        (setq command (file-name-nondirectory command))
+        (catch 'found-command
+          (let (bin)
+            (dolist (dir exec-path)
+              (setq bin (expand-file-name command dir))
+              (when (or (and (file-executable-p bin)
+                             (not (file-directory-p bin)))
+                        (and (file-executable-p (setq bin (concat bin ".exe")))
+                             (not (file-directory-p bin))))
+                (throw 'found-command bin))))))))
 
 (defun notmuch-address-harvest-addr (result)
   (let ((name-addr (plist-get result :name-addr)))
@@ -285,7 +288,7 @@ external commands."
 (defvar notmuch-address-harvest-procs '(nil . nil)
   "The currently running harvests.
 
-The car is a partial harvest, and the cdr is a full harvest")
+The car is a partial harvest, and the cdr is a full harvest.")
 
 (defun notmuch-address-harvest (&optional addr-prefix synchronous callback)
   "Collect addresses completion candidates.
@@ -301,21 +304,22 @@ matching ADDR-PREFIX*' are queried.
 Address harvesting may take some time so the address collection runs
 asynchronously unless SYNCHRONOUS is t. In case of asynchronous
 execution, CALLBACK is called when harvesting finishes."
-
   (let* ((sent (eq (car notmuch-address-internal-completion) 'sent))
         (config-query (cadr notmuch-address-internal-completion))
-        (prefix-query (when addr-prefix
-                        (format "%s:%s*" (if sent "to" "from") addr-prefix)))
+        (prefix-query (and addr-prefix
+                           (format "%s:%s*"
+                                   (if sent "to" "from")
+                                   addr-prefix)))
         (from-or-to-me-query
          (mapconcat (lambda (x)
                       (concat (if sent "from:" "to:") x))
                     (notmuch-user-emails) " or "))
         (query (if (or prefix-query config-query)
                    (concat (format "(%s)" from-or-to-me-query)
-                           (when prefix-query
-                             (format " and (%s)" prefix-query))
-                           (when config-query
-                             (format " and (%s)" config-query)))
+                           (and prefix-query
+                                (format " and (%s)" prefix-query))
+                           (and config-query
+                                (format " and (%s)" config-query)))
                  from-or-to-me-query))
         (args `("address" "--format=sexp" "--format-version=4"
                 ,(if sent "--output=recipients" "--output=sender")
@@ -323,7 +327,7 @@ execution, CALLBACK is called when harvesting finishes."
                 ,query)))
     (if synchronous
        (mapc #'notmuch-address-harvest-addr
-                                  (apply 'notmuch-call-notmuch-sexp args))
+             (apply 'notmuch-call-notmuch-sexp args))
       ;; Asynchronous
       (let* ((current-proc (if addr-prefix
                               (car notmuch-address-harvest-procs)
@@ -334,7 +338,6 @@ execution, CALLBACK is called when harvesting finishes."
        ;; Kill any existing process
        (when current-proc
          (kill-buffer (process-buffer current-proc))) ; this also kills the process
-
        (setq current-proc
              (apply 'notmuch-start-notmuch proc-name proc-buf
                     callback                           ; process sentinel
@@ -351,25 +354,25 @@ execution, CALLBACK is called when harvesting finishes."
   "Version format of the save hash.")
 
 (defun notmuch-address--get-address-hash ()
-  "Returns the saved address hash as a plist.
+  "Return the saved address hash as a plist.
 
 Returns nil if the save file does not exist, or it does not seem
 to be a saved address hash."
-  (when notmuch-address-save-filename
-    (condition-case nil
-       (with-temp-buffer
-         (insert-file-contents notmuch-address-save-filename)
-         (let ((name (read (current-buffer)))
-               (plist (read (current-buffer))))
-           ;; We do two simple sanity checks on the loaded file. We just
-           ;; check a version is specified, not that it is the current
-           ;; version, as we are allowed to over-write and a save-file with
-           ;; an older version.
-           (when (and (string= name "notmuch-address-hash")
-                      (plist-get plist :version))
-             plist)))
-      ;; The error case catches any of the reads failing.
-      (error nil))))
+  (and notmuch-address-save-filename
+       (condition-case nil
+          (with-temp-buffer
+            (insert-file-contents notmuch-address-save-filename)
+            (let ((name (read (current-buffer)))
+                  (plist (read (current-buffer))))
+              ;; We do two simple sanity checks on the loaded file.
+              ;; We just check a version is specified, not that
+              ;; it is the current version, as we are allowed to
+              ;; over-write and a save-file with an older version.
+              (and (string= name "notmuch-address-hash")
+                   (plist-get plist :version)
+                   plist)))
+        ;; The error case catches any of the reads failing.
+        (error nil))))
 
 (defun notmuch-address--load-address-hash ()
   "Read the saved address hash and set the corresponding variables."
@@ -382,22 +385,23 @@ to be a saved address hash."
                      notmuch-address-internal-completion)
               (equal (plist-get load-plist :version)
                      notmuch-address--save-hash-version))
-      (setq notmuch-address-last-harvest (plist-get load-plist :last-harvest)
-           notmuch-address-completions (plist-get load-plist :completions)
-           notmuch-address-full-harvest-finished t)
+      (setq notmuch-address-last-harvest (plist-get load-plist :last-harvest))
+      (setq notmuch-address-completions (plist-get load-plist :completions))
+      (setq notmuch-address-full-harvest-finished t)
       ;; Return t to say load was successful.
       t)))
 
 (defun notmuch-address--save-address-hash ()
   (when notmuch-address-save-filename
     (if (or (not (file-exists-p notmuch-address-save-filename))
-             ;; The file exists, check it is a file we saved
+           ;; The file exists, check it is a file we saved
            (notmuch-address--get-address-hash))
        (with-temp-file notmuch-address-save-filename
-         (let ((save-plist (list :version notmuch-address--save-hash-version
-                                 :completion-settings notmuch-address-internal-completion
-                                 :last-harvest notmuch-address-last-harvest
-                                 :completions notmuch-address-completions)))
+         (let ((save-plist
+                (list :version notmuch-address--save-hash-version
+                      :completion-settings notmuch-address-internal-completion
+                      :last-harvest notmuch-address-last-harvest
+                      :completions notmuch-address-completions)))
            (print "notmuch-address-hash" (current-buffer))
            (print save-plist (current-buffer))))
       (message "\
@@ -409,16 +413,17 @@ appear to be an address savefile.  Not overwriting."
   (let ((now (float-time)))
     (when (> (- now notmuch-address-last-harvest) 86400)
       (setq notmuch-address-last-harvest now)
-      (notmuch-address-harvest nil nil
-                              (lambda (proc event)
-                                ;; If harvest fails, we want to try
-                                ;; again when the trigger is next
-                                ;; called
-                                (if (string= event "finished\n")
-                                    (progn
-                                      (notmuch-address--save-address-hash)
-                                      (setq notmuch-address-full-harvest-finished t))
-                                  (setq notmuch-address-last-harvest 0)))))))
+      (notmuch-address-harvest
+       nil nil
+       (lambda (proc event)
+        ;; If harvest fails, we want to try
+        ;; again when the trigger is next
+        ;; called
+        (if (string= event "finished\n")
+            (progn
+              (notmuch-address--save-address-hash)
+              (setq notmuch-address-full-harvest-finished t))
+          (setq notmuch-address-last-harvest 0)))))))
 
 ;;
 
index 3e12e7a9f729984899dc32e5cbf2cb1b8e930710..9ee8ceca2922076ce3e459f098939c0e3025c289 100644 (file)
@@ -1,33 +1,39 @@
 ;;; notmuch-company.el --- Mail address completion for notmuch via company-mode  -*- lexical-binding: t -*-
-
-;; Authors: Trevor Jim <tjim@mac.com>
-;;         Michal Sojka <sojkam1@fel.cvut.cz>
 ;;
-;; Keywords: mail, completion
-
-;; 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
+;; Copyright © Trevor Jim
+;; Copyright © Michal Sojka
+;;
+;; This file is part of Notmuch.
+;;
+;; Notmuch 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.
-
+;;
+;; Notmuch 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/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
+;;
+;; Authors: Trevor Jim <tjim@mac.com>
+;;         Michal Sojka <sojkam1@fel.cvut.cz>
+;; Keywords: mail, completion
 
 ;;; Commentary:
 
-;; To enable this, install company mode (https://company-mode.github.io/)
+;; Mail address completion for notmuch via company-mode.  To enable
+;; this, install company mode from <https://company-mode.github.io/>.
 ;;
 ;; NB company-minimum-prefix-length defaults to 3 so you don't get
-;; completion unless you type 3 characters
+;; completion unless you type 3 characters.
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl-lib))
+
 (require 'notmuch-lib)
 
 (defvar notmuch-company-last-prefix nil)
@@ -56,7 +62,7 @@
   ;; internal completion) can still be accessed via standard company
   ;; functions, e.g., company-complete.
   (unless (eq notmuch-address-command 'internal)
-    (notmuch-setq-local company-idle-delay nil)))
+    (setq-local company-idle-delay nil)))
 
 ;;;###autoload
 (defun notmuch-company (command &optional arg &rest _ignore)
   (require 'company)
   (let ((case-fold-search t)
        (completion-ignore-case t))
-    (case command
+    (cl-case command
       (interactive (company-begin-backend 'notmuch-company))
       (prefix (and (derived-mode-p 'message-mode)
-                  (looking-back (concat notmuch-address-completion-headers-regexp ".*")
-                                (line-beginning-position))
-                  (setq notmuch-company-last-prefix (company-grab "[:,][ \t]*\\(.*\\)" 1 (point-at-bol)))))
+                  (looking-back
+                   (concat notmuch-address-completion-headers-regexp ".*")
+                   (line-beginning-position))
+                  (setq notmuch-company-last-prefix
+                        (company-grab "[:,][ \t]*\\(.*\\)" 1 (point-at-bol)))))
       (candidates (cond
                   ((notmuch-address--harvest-ready)
                    ;; Update harvested addressed from time to time
                   (t
                    (cons :async
                          (lambda (callback)
-                           ;; First run quick asynchronous harvest based on what the user entered so far
+                           ;; First run quick asynchronous harvest
+                           ;; based on what the user entered so far
                            (notmuch-address-harvest
                             arg nil
                             (lambda (_proc _event)
                               (funcall callback (notmuch-address-matching arg))
-                              ;; Then start the (potentially long-running) full asynchronous harvest if necessary
+                              ;; Then start the (potentially long-running)
+                              ;; full asynchronous harvest if necessary
                               (notmuch-address-harvest-trigger))))))))
       (match (if (string-match notmuch-company-last-prefix arg)
                 (match-end 0)
               0))
-      (post-completion (run-hook-with-args 'notmuch-address-post-completion-functions arg))
+      (post-completion
+       (run-hook-with-args 'notmuch-address-post-completion-functions arg))
       (no-cache t))))
 
 
index 2cedd39d5eb8e98604b4497ea70bf7106ab1a4c2..3ede6b36aeae6d7c0e7bfa90dfb9331e1dbb7ad6 100644 (file)
@@ -1,10 +1,26 @@
-;; Compatibility functions for earlier versions of emacs
-
+;;; notmuch-compat.el --- compatibility functions for earlier versions of emacs
+;;
 ;; The functions in this file are copied from more modern versions of
 ;; emacs and are Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2017
 ;; Free Software Foundation, Inc.
+;;
+;; This file is part of Notmuch.
+;;
+;; Notmuch 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.
+;;
+;; Notmuch 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 Notmuch.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; emacs master has a bugfix for folding long headers when sending
 ;; messages. Include the fix for earlier versions of emacs. To avoid
 ;; interfering with gnus we only run the hook when called from
 (unless (fboundp 'message--fold-long-headers)
   (add-hook 'message-header-hook 'notmuch-message--fold-long-headers))
 
-(if (fboundp 'setq-local)
-    (defalias 'notmuch-setq-local 'setq-local)
-  (defmacro notmuch-setq-local (var val)
-    "Set variable VAR to value VAL in current buffer.
-
-Backport of setq-local for emacs without setq-local (pre 24.3)."
-    `(set (make-local-variable ',var) ,val)))
-
-(if (fboundp 'read-char-choice)
-    (defalias 'notmuch-read-char-choice 'read-char-choice)
-  (defun notmuch-read-char-choice (prompt chars &optional inhibit-keyboard-quit)
-  "Read and return one of CHARS, prompting for PROMPT.
-Any input that is not one of CHARS is ignored.
-
-If optional argument INHIBIT-KEYBOARD-QUIT is non-nil, ignore
-keyboard-quit events while waiting for a valid input.
-
-This is an exact copy of this function from emacs 24 for use on
-emacs 23, except with the one emacs 24 only function it calls
-inlined."
-  (unless (consp chars)
-    (error "Called `read-char-choice' without valid char choices"))
-  (let (char done show-help (helpbuf " *Char Help*"))
-    (let ((cursor-in-echo-area t)
-          (executing-kbd-macro executing-kbd-macro)
-         (esc-flag nil))
-      (save-window-excursion         ; in case we call help-form-show
-       (while (not done)
-         (unless (get-text-property 0 'face prompt)
-           (setq prompt (propertize prompt 'face 'minibuffer-prompt)))
-         (setq char (let ((inhibit-quit inhibit-keyboard-quit))
-                      (read-key prompt)))
-         (and show-help (buffer-live-p (get-buffer helpbuf))
-              (kill-buffer helpbuf))
-         (cond
-          ((not (numberp char)))
-          ;; If caller has set help-form, that's enough.
-          ;; They don't explicitly have to add help-char to chars.
-          ((and help-form
-                (eq char help-char)
-                (setq show-help t)
-                ;; This is an inlined copy of help-form-show as that
-                ;; was introduced in emacs 24 too.
-                (let ((msg (eval help-form)))
-                  (if (stringp msg)
-                      (with-output-to-temp-buffer " *Char Help*"
-                        (princ msg))))))
-          ((memq char chars)
-           (setq done t))
-          ((and executing-kbd-macro (= char -1))
-           ;; read-event returns -1 if we are in a kbd macro and
-           ;; there are no more events in the macro.  Attempt to
-           ;; get an event interactively.
-           (setq executing-kbd-macro nil))
-          ((not inhibit-keyboard-quit)
-           (cond
-            ((and (null esc-flag) (eq char ?\e))
-             (setq esc-flag t))
-            ((memq char '(?\C-g ?\e))
-             (keyboard-quit))))))))
-    ;; Display the question with the answer.  But without cursor-in-echo-area.
-    (message "%s%s" prompt (char-to-string char))
-    char)))
-
 ;; End of compatibility functions
 
 (provide 'notmuch-compat)
+
+;;; notmuch-compat.el ends here
index 4216f58327a9fb0bef08670fa88f0b6e4723874a..276c98594e3db94c115845600064d81d2d7e800c 100644 (file)
@@ -1,4 +1,4 @@
-;;; notmuch-crypto.el --- functions for handling display of cryptographic metadata.
+;;; notmuch-crypto.el --- functions for handling display of cryptographic metadata
 ;;
 ;; Copyright © Jameson Rollins
 ;;
@@ -24,6 +24,8 @@
 (require 'epg)
 (require 'notmuch-lib)
 
+(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
+
 (defcustom notmuch-crypto-process-mime t
   "Should cryptographic MIME parts be processed?
 
@@ -43,6 +45,16 @@ mode."
   :package-version '(notmuch . "0.25")
   :group 'notmuch-crypto)
 
+(defcustom notmuch-crypto-get-keys-asynchronously t
+  "Retrieve gpg keys asynchronously."
+  :type 'boolean
+  :group 'notmuch-crypto)
+
+(defcustom notmuch-crypto-gpg-program epg-gpg-program
+  "The gpg executable."
+  :type 'string
+  :group 'notmuch-crypto)
+
 (defface notmuch-crypto-part-header
   '((((class color)
       (background dark))
@@ -91,34 +103,35 @@ mode."
   :supertype 'notmuch-button-type)
 
 (defun notmuch-crypto-insert-sigstatus-button (sigstatus from)
+  "Insert a button describing the signature status SIGSTATUS sent
+by user FROM."
   (let* ((status (plist-get sigstatus :status))
-        (help-msg nil)
         (show-button t)
-        (label nil)
         (face 'notmuch-crypto-signature-unknown)
-        (button-action (lambda (button) (message (button-get button 'help-echo)))))
+        (button-action (lambda (button) (message (button-get button 'help-echo))))
+        (keyid (concat "0x" (plist-get sigstatus :keyid)))
+        label help-msg)
     (cond
      ((string= status "good")
-      (let ((fingerprint (concat "0x" (plist-get sigstatus :fingerprint))))
-       ;; if userid present, userid has full or greater validity
-       (if (plist-member sigstatus :userid)
-           (let ((userid (plist-get sigstatus :userid)))
+      (let ((fingerprint (concat "0x" (plist-get sigstatus :fingerprint)))
+           (userid (plist-get sigstatus :userid)))
+       ;; If userid is present it has full or greater validity.
+       (if userid
+           (progn
              (setq label (concat "Good signature by: " userid))
              (setq face 'notmuch-crypto-signature-good))
-         (progn
-           (setq label (concat "Good signature by key: " fingerprint))
-           (setq face 'notmuch-crypto-signature-good-key)))
+         (setq label (concat "Good signature by key: " fingerprint))
+         (setq face 'notmuch-crypto-signature-good-key))
        (setq button-action 'notmuch-crypto-sigstatus-good-callback)
        (setq help-msg (concat "Click to list key ID 0x" fingerprint "."))))
      ((string= status "error")
-      (let ((keyid (concat "0x" (plist-get sigstatus :keyid))))
-       (setq label (concat "Unknown key ID " keyid " or unsupported algorithm"))
-       (setq button-action 'notmuch-crypto-sigstatus-error-callback)
-       (setq help-msg (concat "Click to retrieve key ID " keyid " from keyserver and redisplay."))))
+      (setq label (concat "Unknown key ID " keyid " or unsupported algorithm"))
+      (setq button-action 'notmuch-crypto-sigstatus-error-callback)
+      (setq help-msg (concat "Click to retrieve key ID " keyid
+                            " from key server.")))
      ((string= status "bad")
-      (let ((keyid (concat "0x" (plist-get sigstatus :keyid))))
-       (setq label (concat "Bad signature (claimed key ID " keyid ")"))
-       (setq face 'notmuch-crypto-signature-bad)))
+      (setq label (concat "Bad signature (claimed key ID " keyid ")"))
+      (setq face 'notmuch-crypto-signature-bad))
      (status
       (setq label (concat "Unknown signature status: " status)))
      (t
@@ -135,53 +148,117 @@ mode."
        :notmuch-from from)
       (insert "\n"))))
 
-(declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state))
-
 (defun notmuch-crypto-sigstatus-good-callback (button)
-  (let* ((sigstatus (button-get button :notmuch-sigstatus))
+  (let* ((id (notmuch-show-get-message-id))
+        (sigstatus (button-get button :notmuch-sigstatus))
         (fingerprint (concat "0x" (plist-get sigstatus :fingerprint)))
         (buffer (get-buffer-create "*notmuch-crypto-gpg-out*"))
-        (window (display-buffer buffer t nil)))
+        (window (display-buffer buffer)))
     (with-selected-window window
       (with-current-buffer buffer
        (goto-char (point-max))
-       (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--list-keys" fingerprint))
+       (insert (format "-- Key %s in message %s:\n"
+                       fingerprint id))
+       (call-process notmuch-crypto-gpg-program nil t t
+                     "--batch" "--no-tty" "--list-keys" fingerprint))
       (recenter -1))))
 
+(declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state))
+(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
+
+(defun notmuch-crypto--async-key-sentinel (process event)
+  "When the user asks for a GPG key to be retrieved
+asynchronously, handle completion of that task.
+
+If the retrieval is successful, the thread where the retrieval
+was initiated is still displayed and the cursor has not moved,
+redisplay the thread."
+  (let ((status (process-status process))
+       (exit-status (process-exit-status process))
+       (keyid (process-get process :gpg-key-id)))
+    (when (memq status '(exit signal))
+      (message "Getting the GPG key %s asynchronously...%s."
+              keyid
+              (if (= exit-status 0)
+                  "completed"
+                "failed"))
+      ;; If the original buffer is still alive and point didn't move
+      ;; (i.e. the user didn't move on or away), refresh the buffer to
+      ;; show the updated signature status.
+      (let ((show-buffer (process-get process :notmuch-show-buffer))
+           (show-point (process-get process :notmuch-show-point)))
+       (when (and (bufferp show-buffer)
+                  (buffer-live-p show-buffer)
+                  (= show-point
+                     (with-current-buffer show-buffer
+                       (point))))
+         (with-current-buffer show-buffer
+           (notmuch-show-refresh-view)))))))
+
+(defun notmuch-crypto--set-button-label (button label)
+  "Set the text displayed in BUTTON to LABEL."
+  (save-excursion
+    (let ((inhibit-read-only t))
+      ;; This knows rather too much about how we typically format
+      ;; buttons.
+      (goto-char (button-start button))
+      (forward-char 2)
+      (delete-region (point) (- (button-end button) 2))
+      (insert label))))
+
 (defun notmuch-crypto-sigstatus-error-callback (button)
+  "When signature validation has failed, try to retrieve the
+corresponding key when the status button is pressed."
   (let* ((sigstatus (button-get button :notmuch-sigstatus))
         (keyid (concat "0x" (plist-get sigstatus :keyid)))
-        (buffer (get-buffer-create "*notmuch-crypto-gpg-out*"))
-        (window (display-buffer buffer t nil)))
-    (with-selected-window window
-      (with-current-buffer buffer
-       (goto-char (point-max))
-       (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--recv-keys" keyid)
-       (insert "\n")
-       (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--list-keys" keyid))
-      (recenter -1))
-    (notmuch-show-refresh-view)))
+        (buffer (get-buffer-create "*notmuch-crypto-gpg-out*")))
+    (if notmuch-crypto-get-keys-asynchronously
+       (progn
+         (notmuch-crypto--set-button-label
+          button (format "Retrieving key %s asynchronously..." keyid))
+         (with-current-buffer buffer
+           (goto-char (point-max))
+           (insert (format "--- Retrieving key %s:\n" keyid)))
+         (let ((p (make-process
+                   :name "notmuch GPG key retrieval"
+                   :connection-type 'pipe
+                   :buffer buffer
+                   :stderr buffer
+                   :command (list notmuch-crypto-gpg-program "--recv-keys" keyid)
+                   :sentinel #'notmuch-crypto--async-key-sentinel)))
+           (process-put p :gpg-key-id keyid)
+           (process-put p :notmuch-show-buffer (current-buffer))
+           (process-put p :notmuch-show-point (point))
+           (message "Getting the GPG key %s asynchronously..." keyid)))
+      (let ((window (display-buffer buffer)))
+       (with-selected-window window
+         (with-current-buffer buffer
+           (goto-char (point-max))
+           (insert (format "--- Retrieving key %s:\n" keyid))
+           (call-process notmuch-crypto-gpg-program nil t t "--recv-keys" keyid)
+           (insert "\n")
+           (call-process notmuch-crypto-gpg-program nil t t "--list-keys" keyid))
+         (recenter -1))
+       (notmuch-show-refresh-view)))))
 
 (defun notmuch-crypto-insert-encstatus-button (encstatus)
-  (let* ((status (plist-get encstatus :status))
-        (help-msg nil)
-        (label "Decryption not attempted")
-        (face 'notmuch-crypto-decryption))
-    (cond
-     ((string= status "good")
-      (setq label "Decryption successful"))
-     ((string= status "bad")
-      (setq label "Decryption error"))
-     (t
-      (setq label (concat "Unknown encryption status"
-                         (if status (concat ": " status))))))
-    (insert-button
-     (concat "[ " label " ]")
-     :type 'notmuch-crypto-status-button-type
-     'help-echo help-msg
-     'face face
-     'mouse-face face)
-    (insert "\n")))
+  "Insert a button describing the encryption status ENCSTATUS."
+  (insert-button
+   (concat "[ "
+          (let ((status (plist-get encstatus :status)))
+            (cond
+             ((string= status "good")
+              "Decryption successful")
+             ((string= status "bad")
+              "Decryption error")
+             (t
+              (concat "Unknown encryption status"
+                      (and status (concat ": " status))))))
+          " ]")
+   :type 'notmuch-crypto-status-button-type
+   'face 'notmuch-crypto-decryption
+   'mouse-face 'notmuch-crypto-decryption)
+  (insert "\n"))
 
 ;;
 
index e22e0d1638b7b206fe4bd08470ec5e04a707b7f7..283830ad0d0097c2aa529cc754ea41cd90002281 100644 (file)
@@ -76,7 +76,7 @@ postponing and resuming a message."
 
 (defcustom notmuch-draft-save-plaintext 'ask
   "Should notmuch save/postpone in plaintext messages that seem
-  like they are intended to be sent encrypted
+like they are intended to be sent encrypted
 (i.e with an mml encryption tag in it)."
   :type '(radio
          (const :tag "Never" nil)
@@ -87,10 +87,10 @@ postponing and resuming a message."
 
 (defvar notmuch-draft-encryption-tag-regex
   "<#\\(part encrypt\\|secure.*mode=.*encrypt>\\)"
-  "Regular expression matching mml tags indicating encryption of part or message")
+  "Regular expression matching mml tags indicating encryption of part or message.")
 
 (defvar notmuch-draft-id nil
-  "Message-id of the most recent saved draft of this message")
+  "Message-id of the most recent saved draft of this message.")
 (make-variable-buffer-local 'notmuch-draft-id)
 
 (defun notmuch-draft--mark-deleted ()
@@ -152,16 +152,18 @@ Used when a new version is saved, or the message is sent."
   "Checks if we should save a message that should be encrypted.
 
 `notmuch-draft-save-plaintext' controls the behaviour."
-  (case notmuch-draft-save-plaintext
-       ((ask)
-        (unless (yes-or-no-p "(Customize `notmuch-draft-save-plaintext' to avoid this warning)
+  (cl-case notmuch-draft-save-plaintext
+    ((ask)
+     (unless (yes-or-no-p
+             "(Customize `notmuch-draft-save-plaintext' to avoid this warning)
 This message contains mml tags that suggest it is intended to be encrypted.
 Really save and index an unencrypted copy? ")
-          (error "Save aborted")))
-       ((nil)
-        (error "Refusing to save draft with encryption tags (see `notmuch-draft-save-plaintext')"))
-       ((t)
-        (ignore))))
+       (error "Save aborted")))
+    ((nil)
+     (error "Refusing to save draft with encryption tags (see `%s')"
+           'notmuch-draft-save-plaintext))
+    ((t)
+     (ignore))))
 
 (defun notmuch-draft--make-message-id ()
   ;; message-make-message-id gives the id inside a "<" ">" pair,
@@ -192,14 +194,16 @@ applied to newly inserted messages)."
        (message-remove-header "Message-ID")
        (message-add-header (concat "Message-ID: <" id ">")))
       (t
-       (message "You have customized emacs so Message-ID is not a deletable header, so not changing it")
+       (message "You have customized emacs so Message-ID is not a %s"
+               "deletable header, so not changing it")
        (setq id nil)))
      (cond
       ((member 'Date message-deletable-headers)
        (message-remove-header "Date")
        (message-add-header (concat "Date: " (message-make-date))))
       (t
-       (message "You have customized emacs so Date is not a deletable header, so not changing it")))
+       (message "You have customized emacs so Date is not a deletable %s"
+               "header, so not changing it")))
      (message-add-header "X-Notmuch-Emacs-Draft: True")
      (notmuch-draft-quote-some-mml)
      (notmuch-maildir-setup-message-for-saving)
@@ -228,7 +232,8 @@ applied to newly inserted messages)."
         (draft (equal tags (notmuch-update-tags tags notmuch-draft-tags))))
     (when (or draft
              (yes-or-no-p "Message does not appear to be a draft: edit as new? "))
-      (switch-to-buffer (get-buffer-create (concat "*notmuch-draft-" id "*")))
+      (pop-to-buffer-same-window
+       (get-buffer-create (concat "*notmuch-draft-" id "*")))
       (setq buffer-read-only nil)
       (erase-buffer)
       (let ((coding-system-for-read 'no-conversion))
@@ -259,7 +264,7 @@ applied to newly inserted messages)."
       ;; If the resumed message was a draft then set the draft
       ;; message-id so that we can delete the current saved draft if the
       ;; message is resaved or sent.
-      (setq notmuch-draft-id (when draft id)))))
+      (setq notmuch-draft-id (and draft id)))))
 
 
 (add-hook 'message-send-hook 'notmuch-draft--mark-deleted)
index 0d9af2a4cf39b74b444a1c010f0397583af2deca..752a1d7bea07a95f769769703e125ebad234b50c 100644 (file)
@@ -8,3 +8,4 @@ Icon=emblem-mail
 Terminal=false
 Type=Application
 Categories=Network;Email;
+Keywords=Mail;E-mail;Email;
index aff8beb510173fbf74cf8fe704828529ba3f458a..bb60a890f85e1a1e8680363f136d15761b4c728e 100644 (file)
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl-lib))
+
 (require 'widget)
 (require 'wid-edit) ; For `widget-forward'.
 
 (require 'notmuch-lib)
 (require 'notmuch-mua)
 
-(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line continuation))
+(declare-function notmuch-search "notmuch"
+                 (&optional query oldest-first target-thread target-line continuation))
 (declare-function notmuch-poll "notmuch" ())
 (declare-function notmuch-tree "notmuch-tree"
-                  (&optional query query-context target buffer-name open-target))
+                 (&optional query query-context target buffer-name open-target unthreaded))
+(declare-function notmuch-unthreaded
+                 (&optional query query-context target buffer-name open-target))
+
 
 (defun notmuch-saved-search-get (saved-search field)
   "Get FIELD from SAVED-SEARCH.
@@ -44,17 +49,19 @@ lists (NAME QUERY COUNT-QUERY)."
    ((keywordp (car saved-search))
     (plist-get saved-search field))
    ;; It is not a plist so it is an old-style entry.
-   ((consp (cdr saved-search)) ;; It is a list (NAME QUERY COUNT-QUERY)
-    (case field
-      (:name (first saved-search))
-      (:query (second saved-search))
-      (:count-query (third saved-search))
-      (t nil)))
-   (t  ;; It is a cons-cell (NAME . QUERY)
-    (case field
-      (:name (car saved-search))
-      (:query (cdr saved-search))
-      (t nil)))))
+   ((consp (cdr saved-search))
+    (pcase-let ((`(,name ,query ,count-query) saved-search))
+      (cl-case field
+       (:name name)
+       (:query query)
+       (:count-query count-query)
+       (t nil))))
+   (t
+    (pcase-let ((`(,name . ,query) saved-search))
+      (cl-case field
+       (:name name)
+       (:query query)
+       (t nil))))))
 
 (defun notmuch-hello-saved-search-to-plist (saved-search)
   "Return a copy of SAVED-SEARCH in plist form.
@@ -63,7 +70,7 @@ If saved search is a plist then just return a copy. In other
 cases, for backwards compatibility, convert to plist form and
 return that."
   (if (keywordp (car saved-search))
-      (copy-seq saved-search)
+      (copy-sequence saved-search)
     (let ((fields (list :name :query :count-query))
          plist-search)
       (dolist (field fields plist-search)
@@ -85,21 +92,32 @@ searches so they still work in customize."
   :tag "Saved Search"
   :args '((list :inline t
                :format "%v"
-               (group :format "%v" :inline t (const :format "   Name: " :name) (string :format "%v"))
-               (group :format "%v" :inline t (const :format "  Query: " :query) (string :format "%v")))
+               (group :format "%v" :inline t
+                      (const :format "   Name: " :name)
+                      (string :format "%v"))
+               (group :format "%v" :inline t
+                      (const :format "  Query: " :query)
+                      (string :format "%v")))
          (checklist :inline t
                     :format "%v"
-                    (group :format "%v" :inline t (const :format "Shortcut key: " :key) (key-sequence :format "%v"))
-                    (group :format "%v" :inline t (const :format "Count-Query: " :count-query) (string :format "%v"))
-                    (group :format "%v" :inline t (const :format "" :sort-order)
+                    (group :format "%v" :inline t
+                           (const :format "Shortcut key: " :key)
+                           (key-sequence :format "%v"))
+                    (group :format "%v" :inline t
+                           (const :format "Count-Query: " :count-query)
+                           (string :format "%v"))
+                    (group :format "%v" :inline t
+                           (const :format "" :sort-order)
                            (choice :tag " Sort Order"
                                    (const :tag "Default" nil)
                                    (const :tag "Oldest-first" oldest-first)
                                    (const :tag "Newest-first" newest-first)))
-                    (group :format "%v" :inline t (const :format "" :search-type)
+                    (group :format "%v" :inline t
+                           (const :format "" :search-type)
                            (choice :tag " Search Type"
                                    (const :tag "Search mode" nil)
-                                   (const :tag "Tree mode" tree))))))
+                                   (const :tag "Tree mode" tree)
+                                   (const :tag "Unthreaded mode" unthreaded))))))
 
 (defcustom notmuch-saved-searches
   `((:name "inbox" :query "tag:inbox" :key ,(kbd "i"))
@@ -122,17 +140,16 @@ a plist. Supported properties are
   :sort-order      Specify the sort order to be used for the search.
                    Possible values are 'oldest-first 'newest-first or
                    nil. Nil means use the default sort order.
-  :search-type     Specify whether to run the search in search-mode
-                   or tree mode. Set to 'tree to specify tree
-                   mode, set to nil (or anything except tree) to
-                   specify search mode.
+  :search-type     Specify whether to run the search in search-mode,
+                   tree mode or unthreaded mode. Set to 'tree to specify tree
+                   mode, 'unthreaded to specify unthreaded mode, and set to nil
+                   (or anything except tree and unthreaded) to specify search mode.
 
 Other accepted forms are a cons cell of the form (NAME . QUERY)
 or a list of the form (NAME QUERY COUNT-QUERY)."
-;; The saved-search format is also used by the all-tags notmuch-hello
-;; section. This section generates its own saved-search list in one of
-;; the latter two forms.
-
+  ;; The saved-search format is also used by the all-tags notmuch-hello
+  ;; section. This section generates its own saved-search list in one of
+  ;; the latter two forms.
   :get 'notmuch-hello--saved-searches-to-plist
   :type '(repeat notmuch-saved-search-plist)
   :tag "List of Saved Searches"
@@ -352,7 +369,7 @@ supported for \"Customized queries section\" items."
   :type 'boolean)
 
 (defvar notmuch-hello-hidden-sections nil
-  "List of sections titles whose contents are hidden")
+  "List of sections titles whose contents are hidden.")
 
 (defvar notmuch-hello-first-run t
   "True if `notmuch-hello' is run for the first time, set to nil
@@ -365,10 +382,10 @@ afterwards.")
       (setq n (/ n 1000)))
     (setq result (or result '(0)))
     (apply #'concat
-     (number-to-string (car result))
-     (mapcar (lambda (elem)
-             (format "%s%03d" notmuch-hello-thousands-separator elem))
-            (cdr result)))))
+          (number-to-string (car result))
+          (mapcar (lambda (elem)
+                    (format "%s%03d" notmuch-hello-thousands-separator elem))
+                  (cdr result)))))
 
 (defun notmuch-hello-trim (search)
   "Trim whitespace."
@@ -392,10 +409,10 @@ afterwards.")
                               notmuch-saved-searches)))
     ;; If an existing saved search with this name exists, remove it.
     (setq notmuch-saved-searches
-         (loop for elem in notmuch-saved-searches
-               if (not (equal name
-                              (notmuch-saved-search-get elem :name)))
-               collect elem))
+         (cl-loop for elem in notmuch-saved-searches
+                  if (not (equal name
+                                 (notmuch-saved-search-get elem :name)))
+                  collect elem))
     ;; Add the new one.
     (customize-save-variable 'notmuch-saved-searches
                             (add-to-list 'notmuch-saved-searches
@@ -413,37 +430,42 @@ afterwards.")
     (notmuch-hello-update)))
 
 (defun notmuch-hello-longest-label (searches-alist)
-  (or (loop for elem in searches-alist
-           maximize (length (notmuch-saved-search-get elem :name)))
+  (or (cl-loop for elem in searches-alist
+              maximize (length (notmuch-saved-search-get elem :name)))
       0))
 
 (defun notmuch-hello-reflect-generate-row (ncols nrows row list)
   (let ((len (length list)))
-    (loop for col from 0 to (- ncols 1)
-         collect (let ((offset (+ (* nrows col) row)))
-                   (if (< offset len)
-                       (nth offset list)
-                     ;; Don't forget to insert an empty slot in the
-                     ;; output matrix if there is no corresponding
-                     ;; value in the input matrix.
-                     nil)))))
+    (cl-loop for col from 0 to (- ncols 1)
+            collect (let ((offset (+ (* nrows col) row)))
+                      (if (< offset len)
+                          (nth offset list)
+                        ;; Don't forget to insert an empty slot in the
+                        ;; output matrix if there is no corresponding
+                        ;; value in the input matrix.
+                        nil)))))
 
 (defun notmuch-hello-reflect (list ncols)
   "Reflect a `ncols' wide matrix represented by `list' along the
 diagonal."
   ;; Not very lispy...
   (let ((nrows (ceiling (length list) ncols)))
-    (loop for row from 0 to (- nrows 1)
-         append (notmuch-hello-reflect-generate-row ncols nrows row list))))
+    (cl-loop for row from 0 to (- nrows 1)
+            append (notmuch-hello-reflect-generate-row ncols nrows row list))))
 
 (defun notmuch-hello-widget-search (widget &rest ignore)
-  (if (widget-get widget :notmuch-search-type)
-      (notmuch-tree (widget-get widget
-                               :notmuch-search-terms))
+  (cond
+   ((eq (widget-get widget :notmuch-search-type) 'tree)
+    (notmuch-tree (widget-get widget
+                             :notmuch-search-terms)))
+   ((eq (widget-get widget :notmuch-search-type) 'unthreaded)
+    (notmuch-unthreaded (widget-get widget
+                                   :notmuch-search-terms)))
+   (t
     (notmuch-search (widget-get widget
                                :notmuch-search-terms)
                    (widget-get widget
-                               :notmuch-search-oldest-first))))
+                               :notmuch-search-oldest-first)))))
 
 (defun notmuch-saved-search-count (search)
   (car (process-lines notmuch-command "count" search)))
@@ -459,19 +481,17 @@ should be. Returns a cons cell `(tags-per-line width)'."
                   ;; Count is 9 wide (8 digits plus space), 1 for the space
                   ;; after the name.
                   (+ 9 1 (max notmuch-column-control widest)))))
-
          ((floatp notmuch-column-control)
           (let* ((available-width (- (window-width) notmuch-hello-indent))
-                 (proposed-width (max (* available-width notmuch-column-control) widest)))
+                 (proposed-width (max (* available-width notmuch-column-control)
+                                      widest)))
             (floor available-width proposed-width)))
-
          (t
           (max 1
                (/ (- (window-width) notmuch-hello-indent)
                   ;; Count is 9 wide (8 digits plus space), 1 for the space
                   ;; after the name.
                   (+ 9 1 widest)))))))
-
     (cons tags-per-line (/ (max 1
                                (- (window-width) notmuch-hello-indent
                                   ;; Count is 9 wide (8 digits plus
@@ -489,8 +509,7 @@ If FILTER is a function, it is called with QUERY as a parameter and
 the string it returns is used as the query. If nil is returned,
 the entry is hidden.
 
-Otherwise, FILTER is ignored.
-"
+Otherwise, FILTER is ignored."
   (cond
    ((functionp filter) (funcall filter query))
    ((stringp filter)
@@ -521,17 +540,15 @@ options will be handled as specified for
          (notmuch-hello-filtered-query count-query
                                        (or (plist-get options :filter-count)
                                            (plist-get options :filter))))
-         "\n")))
-
+        "\n")))
     (unless (= (call-process-region (point-min) (point-max) notmuch-command
                                    t t nil "count" "--batch") 0)
-      (notmuch-logged-error "notmuch count --batch failed"
-                           "Please check that the notmuch CLI is new enough to support `count
+      (notmuch-logged-error
+       "notmuch count --batch failed"
+       "Please check that the notmuch CLI is new enough to support `count
 --batch'. In general we recommend running matching versions of
 the CLI and emacs interface."))
-
     (goto-char (point-min))
-
     (notmuch-remove-if-not
      #'identity
      (mapcar
@@ -542,7 +559,8 @@ the CLI and emacs interface."))
                                search-query (plist-get options :filter)))
               (message-count (prog1 (read (current-buffer))
                                (forward-line 1))))
-         (when (and filtered-query (or (plist-get options :show-empty-searches) (> message-count 0)))
+         (when (and filtered-query (or (plist-get options :show-empty-searches)
+                                       (> message-count 0)))
            (setq elem-plist (plist-put elem-plist :query filtered-query))
            (plist-put elem-plist :count message-count))))
       query-list))))
@@ -571,15 +589,15 @@ with `notmuch-hello-query-counts'."
     (mapc (lambda (elem)
            ;; (not elem) indicates an empty slot in the matrix.
            (when elem
-             (if (> column-indent 0)
-                 (widget-insert (make-string column-indent ? )))
+             (when (> column-indent 0)
+               (widget-insert (make-string column-indent ? )))
              (let* ((name (plist-get elem :name))
                     (query (plist-get elem :query))
-                    (oldest-first (case (plist-get elem :sort-order)
+                    (oldest-first (cl-case (plist-get elem :sort-order)
                                     (newest-first nil)
                                     (oldest-first t)
                                     (otherwise notmuch-search-oldest-first)))
-                    (search-type (eq (plist-get elem :search-type) 'tree))
+                    (search-type (plist-get elem :search-type))
                     (msg-count (plist-get elem :count)))
                (widget-insert (format "%8s "
                                       (notmuch-hello-nice-number msg-count)))
@@ -591,12 +609,11 @@ with `notmuch-hello-query-counts'."
                               name)
                (setq column-indent
                      (1+ (max 0 (- column-width (length name)))))))
-           (setq count (1+ count))
+           (cl-incf count)
            (when (eq (% count tags-per-line) 0)
              (setq column-indent 0)
              (widget-insert "\n")))
          reordered-list)
-
     ;; If the last line was not full (and hence did not include a
     ;; carriage return), insert one now.
     (unless (eq (% count tags-per-line) 0)
@@ -621,7 +638,7 @@ with `notmuch-hello-query-counts'."
     (dolist (window (window-list))
       (let ((last-buf (window-parameter window 'notmuch-hello-last-buffer))
            (cur-buf (window-buffer window)))
-       (when (not (eq last-buf cur-buf))
+       (unless (eq last-buf cur-buf)
          ;; This window changed or is new.  Update recorded buffer
          ;; for next time.
          (set-window-parameter window 'notmuch-hello-last-buffer cur-buf)
@@ -635,7 +652,7 @@ with `notmuch-hello-query-counts'."
       ;; 24, we can't do it right here because something in this
       ;; hook's call stack overrides hello's point placement.
       (run-at-time nil nil #'notmuch-hello t))
-    (when (null hello-buf)
+    (unless hello-buf
       ;; Clean up hook
       (remove-hook 'window-configuration-change-hook
                   #'notmuch-hello-window-configuration-change))))
@@ -644,7 +661,7 @@ with `notmuch-hello-query-counts'."
 (defvar notmuch-emacs-version)
 
 (defun notmuch-hello-versions ()
-  "Display the notmuch version(s)"
+  "Display the notmuch version(s)."
   (interactive)
   (let ((notmuch-cli-version (notmuch-cli-version)))
     (message "notmuch version %s"
@@ -670,10 +687,9 @@ with `notmuch-hello-query-counts'."
     (define-key map (kbd "<C-tab>") 'widget-backward)
     map)
   "Keymap for \"notmuch hello\" buffers.")
-(fset 'notmuch-hello-mode-map notmuch-hello-mode-map)
 
 (define-derived-mode notmuch-hello-mode fundamental-mode "notmuch-hello"
- "Major mode for convenient notmuch navigation. This is your entry portal into notmuch.
 "Major mode for convenient notmuch navigation. This is your entry portal into notmuch.
 
 Saved searches are \"bookmarks\" for arbitrary queries. Hit RET
 or click on a saved search to view matching threads. Edit saved
@@ -703,9 +719,9 @@ The screen may be customized via `\\[customize]'.
 Complete list of currently available key bindings:
 
 \\{notmuch-hello-mode-map}"
- (setq notmuch-buffer-refresh-function #'notmuch-hello-update)
- ;;(setq buffer-read-only t)
-)
 (setq notmuch-buffer-refresh-function #'notmuch-hello-update)
 ;;(setq buffer-read-only t)
+  )
 
 (defun notmuch-hello-generate-tag-alist (&optional hide-tags)
   "Return an alist from tags to queries to display in the all-tags section."
@@ -729,7 +745,9 @@ Complete list of currently available key bindings:
       ;; dark background.
       (setq image (cons 'image
                        (append (cdr image)
-                               (list :background (face-background 'notmuch-hello-logo-background)))))
+                               (list :background
+                                     (face-background
+                                      'notmuch-hello-logo-background)))))
       (insert-image image))
     (widget-insert "  "))
 
@@ -749,10 +767,10 @@ Complete list of currently available key bindings:
                             (notmuch-hello-update))
                   :help-echo "Refresh"
                   (notmuch-hello-nice-number
-                   (string-to-number (car (process-lines notmuch-command "count")))))
+                   (string-to-number
+                    (car (process-lines notmuch-command "count")))))
     (widget-insert " messages.\n")))
 
-
 (defun notmuch-hello-insert-saved-searches ()
   "Insert the saved-searches section."
   (let ((searches (notmuch-hello-query-counts
@@ -803,48 +821,48 @@ Complete list of currently available key bindings:
                   "clear")
     (widget-insert "\n\n")
     (let ((start (point)))
-      (loop for i from 1 to notmuch-hello-recent-searches-max
-           for search in notmuch-search-history do
-           (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i))))
-             (set widget-symbol
-                  (widget-create 'editable-field
-                                 ;; Don't let the search boxes be
-                                 ;; less than 8 characters wide.
-                                 :size (max 8
-                                            (- (window-width)
-                                               ;; Leave some space
-                                               ;; at the start and
-                                               ;; end of the
-                                               ;; boxes.
-                                               (* 2 notmuch-hello-indent)
-                                               ;; 1 for the space
-                                               ;; before the
-                                               ;; `[save]' button. 6
-                                               ;; for the `[save]'
-                                               ;; button.
-                                               1 6
-                                               ;; 1 for the space
-                                               ;; before the `[del]'
-                                               ;; button. 5 for the
-                                               ;; `[del]' button.
-                                               1 5))
-                                 :action (lambda (widget &rest ignore)
-                                           (notmuch-hello-search (widget-value widget)))
-                                 search))
-             (widget-insert " ")
-             (widget-create 'push-button
-                            :notify (lambda (widget &rest ignore)
-                                      (notmuch-hello-add-saved-search widget))
-                            :notmuch-saved-search-widget widget-symbol
-                            "save")
-             (widget-insert " ")
-             (widget-create 'push-button
-                            :notify (lambda (widget &rest ignore)
-                                      (when (y-or-n-p "Are you sure you want to delete this search? ")
-                                        (notmuch-hello-delete-search-from-history widget)))
-                            :notmuch-saved-search-widget widget-symbol
-                            "del"))
-           (widget-insert "\n"))
+      (cl-loop for i from 1 to notmuch-hello-recent-searches-max
+              for search in notmuch-search-history do
+              (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i))))
+                (set widget-symbol
+                     (widget-create 'editable-field
+                                    ;; Don't let the search boxes be
+                                    ;; less than 8 characters wide.
+                                    :size (max 8
+                                               (- (window-width)
+                                                  ;; Leave some space
+                                                  ;; at the start and
+                                                  ;; end of the
+                                                  ;; boxes.
+                                                  (* 2 notmuch-hello-indent)
+                                                  ;; 1 for the space
+                                                  ;; before the
+                                                  ;; `[save]' button. 6
+                                                  ;; for the `[save]'
+                                                  ;; button.
+                                                  1 6
+                                                  ;; 1 for the space
+                                                  ;; before the `[del]'
+                                                  ;; button. 5 for the
+                                                  ;; `[del]' button.
+                                                  1 5))
+                                    :action (lambda (widget &rest ignore)
+                                              (notmuch-hello-search (widget-value widget)))
+                                    search))
+                (widget-insert " ")
+                (widget-create 'push-button
+                               :notify (lambda (widget &rest ignore)
+                                         (notmuch-hello-add-saved-search widget))
+                               :notmuch-saved-search-widget widget-symbol
+                               "save")
+                (widget-insert " ")
+                (widget-create 'push-button
+                               :notify (lambda (widget &rest ignore)
+                                         (when (y-or-n-p "Are you sure you want to delete this search? ")
+                                           (notmuch-hello-delete-search-from-history widget)))
+                               :notmuch-saved-search-widget widget-symbol
+                               "del"))
+              (widget-insert "\n"))
       (indent-rigidly start (point) notmuch-hello-indent))
     nil))
 
@@ -871,8 +889,8 @@ Supports the following entries in OPTIONS as a plist:
    the same values as :filter. If :filter and :filter-count are specified, this
    will be used instead of :filter, not in conjunction with it."
   (widget-insert title ": ")
-  (if (and notmuch-hello-first-run (plist-get options :initially-hidden))
-      (add-to-list 'notmuch-hello-hidden-sections title))
+  (when (and notmuch-hello-first-run (plist-get options :initially-hidden))
+    (add-to-list 'notmuch-hello-hidden-sections title))
   (let ((is-hidden (member title notmuch-hello-hidden-sections))
        (start (point)))
     (if is-hidden
@@ -889,7 +907,7 @@ Supports the following entries in OPTIONS as a plist:
                                (notmuch-hello-update))
                     "hide"))
     (widget-insert "\n")
-    (when (not is-hidden)
+    (unless is-hidden
       (let ((searches (apply 'notmuch-hello-query-counts query-list options)))
        (when (or (not (plist-get options :hide-if-empty))
                  searches)
@@ -911,7 +929,7 @@ following:
         options))
 
 (defun notmuch-hello-insert-inbox ()
-  "Show an entry for each saved search and inboxed messages for each tag"
+  "Show an entry for each saved search and inboxed messages for each tag."
   (notmuch-hello-insert-searches "What's in your inbox"
                                 (append
                                  notmuch-saved-searches
@@ -919,7 +937,7 @@ following:
                                 :filter "tag:inbox"))
 
 (defun notmuch-hello-insert-alltags ()
-  "Insert a section displaying all tags and associated message counts"
+  "Insert a section displaying all tags and associated message counts."
   (notmuch-hello-insert-tags-section
    nil
    :initially-hidden (not notmuch-show-all-tags-list)
@@ -949,40 +967,32 @@ following:
 (defun notmuch-hello (&optional no-display)
   "Run notmuch and display saved searches, known tags, etc."
   (interactive)
-
   (notmuch-assert-cli-sane)
   ;; This may cause a window configuration change, so if the
   ;; auto-refresh hook is already installed, avoid recursive refresh.
   (let ((notmuch-hello-auto-refresh nil))
     (if no-display
        (set-buffer "*notmuch-hello*")
-      (switch-to-buffer "*notmuch-hello*")))
-
+      (pop-to-buffer-same-window "*notmuch-hello*")))
   ;; Install auto-refresh hook
   (when notmuch-hello-auto-refresh
     (add-hook 'window-configuration-change-hook
              #'notmuch-hello-window-configuration-change))
-
   (let ((target-line (line-number-at-pos))
        (target-column (current-column))
        (inhibit-read-only t))
-
     ;; Delete all editable widget fields.  Editable widget fields are
     ;; tracked in a buffer local variable `widget-field-list' (and
     ;; others).  If we do `erase-buffer' without properly deleting the
     ;; widgets, some widget-related functions are confused later.
     (mapc 'widget-delete widget-field-list)
-
     (erase-buffer)
-
     (unless (eq major-mode 'notmuch-hello-mode)
       (notmuch-hello-mode))
-
     (let ((all (overlay-lists)))
       ;; Delete all the overlays.
       (mapc 'delete-overlay (car all))
       (mapc 'delete-overlay (cdr all)))
-
     (mapc
      (lambda (section)
        (let ((point-before (point)))
@@ -995,7 +1005,6 @@ following:
           (widget-insert "\n"))))
      notmuch-hello-sections)
     (widget-setup)
-
     ;; Move point back to where it was before refresh. Use line and
     ;; column instead of point directly to be insensitive to additions
     ;; and removals of text within earlier lines.
index 3e20b8c7afc68e6b9d56a709ecc74283b173ae38..1e2d0497949322c26cd2c453ae215336551156d6 100644 (file)
@@ -22,7 +22,9 @@
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile
+  (require 'cl-lib)
+  (require 'pcase))
 
 (require 'notmuch-lib)
 (require 'notmuch-hello)
@@ -41,7 +43,6 @@ keys configured in the :key property of `notmuch-saved-searches'.
 Typically these shortcuts are a single key long, so this is a
 fast way to jump to a saved search from anywhere in Notmuch."
   (interactive)
-
   ;; Build the action map
   (let (action-map)
     (dolist (saved-search notmuch-saved-searches)
@@ -51,23 +52,28 @@ fast way to jump to a saved search from anywhere in Notmuch."
          (let ((name (plist-get saved-search :name))
                (query (plist-get saved-search :query))
                (oldest-first
-                (case (plist-get saved-search :sort-order)
+                (cl-case (plist-get saved-search :sort-order)
                   (newest-first nil)
                   (oldest-first t)
                   (otherwise (default-value 'notmuch-search-oldest-first)))))
            (push (list key name
-                       (if (eq (plist-get saved-search :search-type) 'tree)
-                           `(lambda () (notmuch-tree ',query))
-                         `(lambda () (notmuch-search ',query ',oldest-first))))
+                       (cond
+                        ((eq (plist-get saved-search :search-type) 'tree)
+                         `(lambda () (notmuch-tree ',query)))
+                        ((eq (plist-get saved-search :search-type) 'unthreaded)
+                         `(lambda () (notmuch-unthreaded ',query)))
+                        (t
+                         `(lambda () (notmuch-search ',query ',oldest-first)))))
                  action-map)))))
     (setq action-map (nreverse action-map))
-
     (if action-map
        (notmuch-jump action-map "Search: ")
-      (error "To use notmuch-jump, please customize shortcut keys in notmuch-saved-searches."))))
+      (error "To use notmuch-jump, \
+please customize shortcut keys in notmuch-saved-searches."))))
 
 (defvar notmuch-jump--action nil)
 
+;;;###autoload
 (defun notmuch-jump (action-map prompt)
   "Interactively prompt for one of the keys in ACTION-MAP.
 
@@ -82,9 +88,7 @@ ACTION-MAP must be a list of triples of the form
 where KEY is a key binding, LABEL is a string label to display in
 the buffer, and ACTION is a nullary function to call.  LABEL may
 be null, in which case the action will still be bound, but will
-not appear in the pop-up buffer.
-"
-
+not appear in the pop-up buffer."
   (let* ((items (notmuch-jump--format-actions action-map))
         ;; Format the table of bindings and the full prompt
         (table
@@ -109,7 +113,6 @@ not appear in the pop-up buffer.
         (notmuch-jump--action nil))
     ;; Read the action
     (read-from-minibuffer full-prompt nil minibuffer-map)
-
     ;; If we got an action, do it
     (when notmuch-jump--action
       (funcall notmuch-jump--action))))
@@ -120,21 +123,18 @@ not appear in the pop-up buffer.
 Returns a list of strings, one for each item with a label in
 ACTION-MAP.  These strings can be inserted into a tabular
 buffer."
-
   ;; Compute the maximum key description width
   (let ((key-width 1))
-    (dolist (entry action-map)
+    (pcase-dolist (`(,key ,desc) action-map)
       (setq key-width
            (max key-width
-                (string-width (format-kbd-macro (first entry))))))
+                (string-width (format-kbd-macro key)))))
     ;; Format each action
-    (mapcar (lambda (entry)
-             (let ((key (format-kbd-macro (first entry)))
-                   (desc (second entry)))
-               (concat
-                (propertize key 'face 'minibuffer-prompt)
-                (make-string (- key-width (length key)) ? )
-                " " desc)))
+    (mapcar (pcase-lambda (`(,key ,desc))
+             (setq key (format-kbd-macro key))
+             (concat (propertize key 'face 'minibuffer-prompt)
+                     (make-string (- key-width (length key)) ? )
+                     " " desc))
            action-map)))
 
 (defun notmuch-jump--insert-items (width items)
@@ -169,39 +169,38 @@ buffer."
   "Translate ACTION-MAP into a minibuffer keymap."
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map notmuch-jump-minibuffer-map)
-    (dolist (action action-map)
-      (if (= (length (first action)) 1)
-         (define-key map (first action)
-           `(lambda () (interactive)
-              (setq notmuch-jump--action ',(third action))
-              (exit-minibuffer)))))
+    (pcase-dolist (`(,key ,name ,fn) action-map)
+      (when (= (length key) 1)
+       (define-key map key
+         `(lambda () (interactive)
+            (setq notmuch-jump--action ',fn)
+            (exit-minibuffer)))))
     ;; By doing this in two passes (and checking if we already have a
     ;; binding) we avoid problems if the user specifies a binding which
     ;; is a prefix of another binding.
-    (dolist (action action-map)
-      (if (> (length (first action)) 1)
-         (let* ((key (elt (first action) 0))
-                (keystr (string key))
-                (new-prompt (concat prompt (format-kbd-macro keystr) " "))
-                (action-submap nil))
-           (unless (lookup-key map keystr)
-             (dolist (act action-map)
-               (when (= key (elt (first act) 0))
-                 (push (list (substring (first act) 1)
-                             (second act)
-                             (third act))
-                       action-submap)))
-             ;; We deal with backspace specially
-             (push (list (kbd "DEL")
-                         "Backup"
-                         (apply-partially #'notmuch-jump action-map prompt))
-                   action-submap)
-             (setq action-submap (nreverse action-submap))
-             (define-key map keystr
-               `(lambda () (interactive)
-                  (setq notmuch-jump--action
-                        ',(apply-partially #'notmuch-jump action-submap new-prompt))
-                  (exit-minibuffer)))))))
+    (pcase-dolist (`(,key ,name ,fn) action-map)
+      (when (> (length key) 1)
+       (let* ((key (elt key 0))
+              (keystr (string key))
+              (new-prompt (concat prompt (format-kbd-macro keystr) " "))
+              (action-submap nil))
+         (unless (lookup-key map keystr)
+           (pcase-dolist (`(,k ,n ,f) action-map)
+             (when (= key (elt k 0))
+               (push (list (substring k 1) n f) action-submap)))
+           ;; We deal with backspace specially
+           (push (list (kbd "DEL")
+                       "Backup"
+                       (apply-partially #'notmuch-jump action-map prompt))
+                 action-submap)
+           (setq action-submap (nreverse action-submap))
+           (define-key map keystr
+             `(lambda () (interactive)
+                (setq notmuch-jump--action
+                      ',(apply-partially #'notmuch-jump
+                                         action-submap
+                                         new-prompt))
+                (exit-minibuffer)))))))
     map))
 
 ;;
index 8acad267fc50eed0031be07f635ef69b95c9e92e..118faf1e37bddb1a9e5b0fad8374ac8fb4361941 100644 (file)
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
 
-;; This is an part of an emacs-based interface to the notmuch mail system.
-
 ;;; Code:
 
+(require 'cl-lib)
+
 (require 'mm-util)
 (require 'mm-view)
 (require 'mm-decode)
-(require 'cl)
+
 (require 'notmuch-compat)
 
 (unless (require 'notmuch-version nil t)
   (defconst notmuch-emacs-version "unknown"
     "Placeholder variable when notmuch-version.el[c] is not available."))
 
-(autoload 'notmuch-jump-search "notmuch-jump"
-  "Jump to a saved search by shortcut key." t)
-
 (defgroup notmuch nil
   "Notmuch mail reader for Emacs."
   :group 'mail)
@@ -54,9 +51,8 @@
 
 (defgroup notmuch-send nil
   "Sending messages from Notmuch."
-  :group 'notmuch)
-
-(custom-add-to-group 'notmuch-send 'message 'custom-group)
+  :group 'notmuch
+  :group 'message)
 
 (defgroup notmuch-tag nil
   "Tags and tagging in Notmuch."
@@ -153,7 +149,9 @@ For example, if you wanted to remove an \"inbox\" tag and add an
     (define-key map "?" 'notmuch-help)
     (define-key map "q" 'notmuch-bury-or-kill-this-buffer)
     (define-key map "s" 'notmuch-search)
+    (define-key map "t" 'notmuch-search-by-tag)
     (define-key map "z" 'notmuch-tree)
+    (define-key map "u" 'notmuch-unthreaded)
     (define-key map "m" 'notmuch-mua-new-mail)
     (define-key map "g" 'notmuch-refresh-this-buffer)
     (define-key map "=" 'notmuch-refresh-this-buffer)
@@ -185,7 +183,7 @@ If notmuch exits with a non-zero status, output from the process
 will appear in a buffer named \"*Notmuch errors*\" and an error
 will be signaled.
 
-Otherwise the output will be returned"
+Otherwise the output will be returned."
   (with-temp-buffer
     (let* ((status (apply #'call-process notmuch-command nil t nil args))
           (output (buffer-string)))
@@ -207,7 +205,7 @@ Otherwise the output will be returned"
   (unless (notmuch-cli-sane-p)
     (notmuch-logged-error
      "notmuch cli seems misconfigured or unconfigured."
-"Perhaps you haven't run \"notmuch setup\" yet? Try running this
+     "Perhaps you haven't run \"notmuch setup\" yet? Try running this
 on the command line, and then retry your notmuch command")))
 
 (defun notmuch-cli-version ()
@@ -255,11 +253,13 @@ on the command line, and then retry your notmuch command")))
 Invokes `notmuch-poll-script', \"notmuch new\", or does nothing
 depending on the value of `notmuch-poll-script'."
   (interactive)
+  (message "Polling mail...")
   (if (stringp notmuch-poll-script)
       (unless (string= notmuch-poll-script "")
        (unless (equal (call-process notmuch-poll-script nil nil) 0)
          (error "Notmuch: poll script `%s' failed!" notmuch-poll-script)))
-    (notmuch-call-notmuch-process "new")))
+    (notmuch-call-notmuch-process "new"))
+  (message "Polling mail...done"))
 
 (defun notmuch-bury-or-kill-this-buffer ()
   "Undisplay the current buffer.
@@ -295,7 +295,7 @@ This is basically just `format-kbd-macro' but we also convert ESC to M-."
 
 
 (defun notmuch-describe-key (actual-key binding prefix ua-keys tail)
-  "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL
+  "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL.
 
 It does not prepend if ACTUAL-KEY is already listed in TAIL."
   (let ((key-string (concat prefix (key-description actual-key))))
@@ -312,10 +312,12 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL."
                tail)))
       ;; Documentation for command
       (push (cons key-string
-                 (or (and (symbolp binding) (get binding 'notmuch-doc))
-                     (and (functionp binding) (notmuch-documentation-first-line binding))))
+                 (or (and (symbolp binding)
+                          (get binding 'notmuch-doc))
+                     (and (functionp binding)
+                          (notmuch-documentation-first-line binding))))
            tail)))
-    tail)
+  tail)
 
 (defun notmuch-describe-remaps (remap-keymap ua-keys base-keymap prefix tail)
   ;; Remappings are represented as a binding whose first "event" is
@@ -323,13 +325,13 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL."
   ;; binding whose "key" is 'remap, and whose "binding" is itself a
   ;; keymap that maps not from keys to commands, but from old (remapped)
   ;; functions to the commands to use in their stead.
-  (map-keymap
-   (lambda (command binding)
-     (mapc
-      (lambda (actual-key)
-       (setq tail (notmuch-describe-key actual-key binding prefix ua-keys tail)))
-      (where-is-internal command base-keymap)))
-   remap-keymap)
+  (map-keymap (lambda (command binding)
+               (mapc (lambda (actual-key)
+                       (setq tail
+                             (notmuch-describe-key actual-key binding
+                                                   prefix ua-keys tail)))
+                     (where-is-internal command base-keymap)))
+             remap-keymap)
   tail)
 
 (defun notmuch-describe-keymap (keymap ua-keys base-keymap &optional prefix tail)
@@ -352,9 +354,13 @@ prefix argument.  PREFIX and TAIL are used internally."
                      (notmuch-describe-remaps
                       binding ua-keys base-keymap prefix tail)
                    (notmuch-describe-keymap
-                    binding ua-keys base-keymap (notmuch-prefix-key-description key) tail))))
+                    binding ua-keys base-keymap
+                    (notmuch-prefix-key-description key)
+                    tail))))
           (binding
-           (setq tail (notmuch-describe-key (vector key) binding prefix ua-keys tail)))))
+           (setq tail
+                 (notmuch-describe-key (vector key)
+                                       binding prefix ua-keys tail)))))
    keymap)
   tail)
 
@@ -364,11 +370,15 @@ prefix argument.  PREFIX and TAIL are used internally."
     (while (string-match "\\\\{\\([^}[:space:]]*\\)}" doc beg)
       (let ((desc
             (save-match-data
-              (let* ((keymap-name (substring doc (match-beginning 1) (match-end 1)))
+              (let* ((keymap-name (substring doc
+                                             (match-beginning 1)
+                                             (match-end 1)))
                      (keymap (symbol-value (intern keymap-name)))
                      (ua-keys (where-is-internal 'universal-argument keymap t))
                      (desc-alist (notmuch-describe-keymap keymap ua-keys keymap))
-                     (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist)))
+                     (desc-list (mapcar (lambda (arg)
+                                          (concat (car arg) "\t" (cdr arg)))
+                                        desc-alist)))
                 (mapconcat #'identity desc-list "\n")))))
        (setq doc (replace-match desc 1 1 doc)))
       (setq beg (match-end 0)))
@@ -387,7 +397,8 @@ its prefixed behavior by setting the 'notmuch-prefix-doc property
 of its command symbol."
   (interactive)
   (let* ((mode major-mode)
-        (doc (substitute-command-keys (notmuch-substitute-command-keys (documentation mode t)))))
+        (doc (substitute-command-keys
+              (notmuch-substitute-command-keys (documentation mode t)))))
     (with-current-buffer (generate-new-buffer "*notmuch-help*")
       (insert doc)
       (goto-char (point-min))
@@ -398,17 +409,18 @@ of its command symbol."
   "Show help for a subkeymap."
   (interactive)
   (let* ((key (this-command-keys-vector))
-       (prefix (make-vector (1- (length key)) nil))
-       (i 0))
+        (prefix (make-vector (1- (length key)) nil))
+        (i 0))
     (while (< i (length prefix))
       (aset prefix i (aref key i))
-      (setq i (1+ i)))
-
+      (cl-incf i))
     (let* ((subkeymap (key-binding prefix))
           (ua-keys (where-is-internal 'universal-argument nil t))
           (prefix-string (notmuch-prefix-key-description prefix))
-          (desc-alist (notmuch-describe-keymap subkeymap ua-keys subkeymap prefix-string))
-          (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist))
+          (desc-alist (notmuch-describe-keymap
+                       subkeymap ua-keys subkeymap prefix-string))
+          (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg)))
+                             desc-alist))
           (desc (mapconcat #'identity desc-list "\n")))
       (with-help-window (help-buffer)
        (with-current-buffer standard-output
@@ -469,7 +481,6 @@ This includes newlines, tabs, and other funny characters."
 The caller is responsible for prepending the term prefix and a
 colon.  This performs minimal escaping in order to produce
 user-friendly queries."
-
   (save-match-data
     (if (or (equal term "")
            ;; To be pessimistic, only pass through terms composed
@@ -512,7 +523,7 @@ This replaces spaces, percents, and double quotes in STR with
   (let (out)
     (while list
       (when (funcall predicate (car list))
-        (push (car list) out))
+       (push (car list) out))
       (setq list (cdr list)))
     (nreverse out)))
 
@@ -526,11 +537,11 @@ This replaces spaces, percents, and double quotes in STR with
     (cdr xplist)))
 
 (defun notmuch-split-content-type (content-type)
-  "Split content/type into 'content' and 'type'"
+  "Split content/type into 'content' and 'type'."
   (split-string content-type "/"))
 
 (defun notmuch-match-content-type (t1 t2)
-  "Return t if t1 and t2 are matching content types, taking wildcards into account"
+  "Return t if t1 and t2 are matching content types, taking wildcards into account."
   (let ((st1 (notmuch-split-content-type t1))
        (st2 (notmuch-split-content-type t2)))
     (if (or (string= (cadr st1) "*")
@@ -540,12 +551,11 @@ This replaces spaces, percents, and double quotes in STR with
       (string= (downcase t1) (downcase t2)))))
 
 (defvar notmuch-multipart/alternative-discouraged
-  '(
-    ;; Avoid HTML parts.
+  '(;; Avoid HTML parts.
     "text/html"
-    ;; multipart/related usually contain a text/html part and some associated graphics.
-    "multipart/related"
-    ))
+    ;; multipart/related usually contain a text/html part and some
+    ;; associated graphics.
+    "multipart/related"))
 
 (defun notmuch-multipart/alternative-determine-discouraged (msg)
   "Return the discouraged alternatives for the specified message."
@@ -572,7 +582,7 @@ for this message, if present."
 (defun notmuch-parts-filter-by-type (parts type)
   "Given a list of message parts, return a list containing the ones matching
 the given type."
-  (remove-if-not
+  (cl-remove-if-not
    (lambda (part) (notmuch-match-content-type (plist-get part :content-type) type))
    parts))
 
@@ -594,12 +604,14 @@ the given type."
                       (set-buffer-multibyte nil))
                     (let ((args `("show" "--format=raw"
                                   ,(format "--part=%s" (plist-get part :id))
-                                  ,@(when process-crypto '("--decrypt=true"))
+                                  ,@(and process-crypto '("--decrypt=true"))
                                   ,(notmuch-id-to-query (plist-get msg :id))))
                           (coding-system-for-read
-                           (if binaryp 'no-conversion
-                             (let ((coding-system (mm-charset-to-coding-system
-                                                   (plist-get part :content-charset))))
+                           (if binaryp
+                               'no-conversion
+                             (let ((coding-system
+                                    (mm-charset-to-coding-system
+                                     (plist-get part :content-charset))))
                                ;; Sadly,
                                ;; `mm-charset-to-coding-system' seems
                                ;; to return things that are not
@@ -611,7 +623,8 @@ the given type."
                                  ;; charset is US-ASCII. RFC6657
                                  ;; complicates this somewhat.
                                  'us-ascii)))))
-                      (apply #'call-process notmuch-command nil '(t nil) nil args)
+                      (apply #'call-process
+                             notmuch-command nil '(t nil) nil args)
                       (buffer-string))))))
     (when (and cache data)
       (plist-put part plist-elem data))
@@ -643,16 +656,14 @@ MSG (if it isn't already)."
 ;; Workaround: The call to `mm-display-part' below triggers a bug in
 ;; Emacs 24 if it attempts to use the shr renderer to display an HTML
 ;; part with images in it (demonstrated in 24.1 and 24.2 on Debian and
-;; Fedora 17, though unreproducable in other configurations).
+;; Fedora 17, though unreproducible in other configurations).
 ;; `mm-shr' references the variable `gnus-inhibit-images' without
 ;; first loading gnus-art, which defines it, resulting in a
 ;; void-variable error.  Hence, we advise `mm-shr' to ensure gnus-art
 ;; is loaded.
-(if (>= emacs-major-version 24)
-    (defadvice mm-shr (before load-gnus-arts activate)
-      (require 'gnus-art nil t)
-      (ad-disable-advice 'mm-shr 'before 'load-gnus-arts)
-      (ad-activate 'mm-shr)))
+(define-advice mm-shr (:before (_handle) notmuch--load-gnus-args)
+  "Require `gnus-art' since we use its variables."
+  (require 'gnus-art nil t))
 
 (defun notmuch-mm-display-part-inline (msg part content-type process-crypto)
   "Use the mm-decode/mm-view functions to display a part in the
@@ -664,9 +675,11 @@ current buffer, if possible."
       ;; `gnus-decoded' charset.  Otherwise, we'll fetch the binary
       ;; part content and let mm-* decode it.
       (let* ((have-content (plist-member part :content))
-            (charset (if have-content 'gnus-decoded
+            (charset (if have-content
+                         'gnus-decoded
                        (plist-get part :content-charset)))
-            (handle (mm-make-handle (current-buffer) `(,content-type (charset . ,charset)))))
+            (handle (mm-make-handle (current-buffer)
+                                    `(,content-type (charset . ,charset)))))
        ;; If the user wants the part inlined, insert the content and
        ;; test whether we are able to inline it (which includes both
        ;; capability and suitability tests).
@@ -683,8 +696,8 @@ current buffer, if possible."
 ;; have symbols of the form :Header as keys, and the resulting alist will have
 ;; symbols of the form 'Header as keys.
 (defun notmuch-headers-plist-to-alist (plist)
-  (loop for (key value . rest) on plist by #'cddr
-       collect (cons (intern (substring (symbol-name key) 1)) value)))
+  (cl-loop for (key value . rest) on plist by #'cddr
+          collect (cons (intern (substring (symbol-name key) 1)) value)))
 
 (defun notmuch-face-ensure-list-form (face)
   "Return FACE in face list form.
@@ -710,7 +723,6 @@ must be a face name (a symbol or string), a property list of face
 attributes, or a list of these.  If START and/or END are omitted,
 they default to the beginning/end of OBJECT.  For convenience
 when applied to strings, this returns OBJECT."
-
   ;; A face property can have three forms: a face name (a string or
   ;; symbol), a property list, or a list of these two forms.  In the
   ;; list case, the faces will be combined, with the earlier faces
@@ -753,7 +765,6 @@ This logs MSG and EXTRA to the *Notmuch errors* buffer and
 signals MSG as an error.  If EXTRA is non-nil, text referring the
 user to the *Notmuch errors* buffer will be appended to the
 signaled error.  This function does not return."
-
   (with-current-buffer (get-buffer-create "*Notmuch errors*")
     (goto-char (point-max))
     (unless (bobp)
@@ -766,8 +777,7 @@ signaled error.  This function does not return."
        (insert extra)
        (unless (bolp)
          (newline)))))
-  (error "%s" (concat msg (when extra
-                           " (see *Notmuch errors* for more details)"))))
+  (error "%s%s" msg (if extra " (see *Notmuch errors* for more details)" "")))
 
 (defun notmuch-check-async-exit-status (proc msg &optional command err)
   "If PROC exited abnormally, pop up an error buffer and signal an error.
@@ -778,11 +788,12 @@ 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)
+        (cl-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))
+      (notmuch-check-exit-status exit-status
+                                (or command (process-command proc))
                                 nil err))))
 
 (defun notmuch-check-exit-status (exit-status command &optional output err)
@@ -797,7 +808,6 @@ command and its arguments.  OUTPUT, if provided, is a string
 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)
    ((eq exit-status 20)
@@ -818,24 +828,22 @@ You may need to restart Emacs or upgrade your notmuch package."))
                       command " "))
           (extra
            (concat "command: " command-string "\n"
-            (if (integerp exit-status)
-                (format "exit status: %s\n" exit-status)
-              (format "exit signal: %s\n" exit-status))
-            (when err
-              (concat "stderr:\n" err))
-            (when output
-              (concat "stdout:\n" output)))))
-       (if err
-           ;; We have an error message straight from the CLI.
-           (notmuch-logged-error
-            (replace-regexp-in-string "[ \n\r\t\f]*\\'" "" err) extra)
-         ;; We only have combined output from the CLI; don't inundate
-         ;; the user with it.  Mimic `process-lines'.
-         (notmuch-logged-error (format "%s exited with status %s"
-                                       (car command) exit-status)
-                               extra))
-       ;; `notmuch-logged-error' does not return.
-       ))))
+                   (if (integerp exit-status)
+                       (format "exit status: %s\n" exit-status)
+                     (format "exit signal: %s\n" exit-status))
+                   (and err    (concat "stderr:\n" err))
+                   (and output (concat "stdout:\n" output)))))
+      (if err
+         ;; We have an error message straight from the CLI.
+         (notmuch-logged-error
+          (replace-regexp-in-string "[ \n\r\t\f]*\\'" "" err) extra)
+       ;; We only have combined output from the CLI; don't inundate
+       ;; the user with it.  Mimic `process-lines'.
+       (notmuch-logged-error (format "%s exited with status %s"
+                                     (car command) exit-status)
+                             extra))
+      ;; `notmuch-logged-error' does not return.
+      ))))
 
 (defun notmuch-call-notmuch--helper (destination args)
   "Helper for synchronous notmuch invocation commands.
@@ -843,12 +851,11 @@ You may need to restart Emacs or upgrade your notmuch package."))
 This wraps `call-process'.  DESTINATION has the same meaning as
 for `call-process'.  ARGS is as described for
 `notmuch-call-notmuch-process'."
-
   (let (stdin-string)
     (while (keywordp (car args))
-      (case (car args)
-       (:stdin-string (setq stdin-string (cadr args)
-                            args (cddr args)))
+      (cl-case (car args)
+       (:stdin-string (setq stdin-string (cadr args))
+                      (setq args (cddr args)))
        (otherwise
         (error "Unknown keyword argument: %s" (car args)))))
     (if (null stdin-string)
@@ -881,7 +888,6 @@ notmuch's output as an S-expression and returns the parsed value.
 Like `notmuch-call-notmuch-process', if notmuch exits with a
 non-zero status, this will report its output and signal an
 error."
-
   (with-temp-buffer
     (let ((err-file (make-temp-file "nmerr")))
       (unwind-protect
@@ -909,11 +915,10 @@ when the process exits, or nil for none.  The caller must *not*
 invoke `set-process-sentinel' directly on the returned process,
 as that will interfere with the handling of stderr and the exit
 status."
-
   (let (err-file err-buffer proc err-proc
-       ;; Find notmuch using Emacs' `exec-path'
-       (command (or (executable-find notmuch-command)
-                    (error "Command not found: %s" notmuch-command))))
+                ;; 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*"))
@@ -927,14 +932,13 @@ status."
                      :buffer buffer
                      :command (cons command args)
                      :connection-type 'pipe
-                     :stderr err-buffer)
-               err-proc (get-buffer-process err-buffer))
+                     :stderr err-buffer))
+         (setq err-proc (get-buffer-process err-buffer))
          (process-put proc 'err-buffer err-buffer)
 
          (process-put err-proc 'err-file err-file)
          (process-put err-proc 'err-buffer err-buffer)
          (set-process-sentinel err-proc #'notmuch-start-notmuch-error-sentinel))
-
       ;; On Emacs versions before 25, there is no way to capture
       ;; stdout and stderr separately for asynchronous processes, or
       ;; even to redirect stderr to a file, so we use a trivial shell
@@ -947,7 +951,6 @@ status."
                          "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)
@@ -958,8 +961,8 @@ status."
   (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))))
+        (err (and (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
@@ -977,16 +980,17 @@ status."
          ;; If that didn't signal an error, then any error output was
          ;; really warning output.  Show warnings, if any.
          (let ((warnings
-                (when err
-                  (with-current-buffer err-buffer
-                    (goto-char (point-min))
-                    (end-of-line)
-                    ;; Show first line; stuff remaining lines in the
-                    ;; errors buffer.
-                    (let ((l1 (buffer-substring (point-min) (point))))
-                      (skip-chars-forward "\n")
-                      (cons l1 (unless (eobp)
-                                 (buffer-substring (point) (point-max)))))))))
+                (and err
+                     (with-current-buffer err-buffer
+                       (goto-char (point-min))
+                       (end-of-line)
+                       ;; Show first line; stuff remaining lines in the
+                       ;; errors buffer.
+                       (let ((l1 (buffer-substring (point-min) (point))))
+                         (skip-chars-forward "\n")
+                         (cons l1 (and (not (eobp))
+                                       (buffer-substring (point)
+                                                         (point-max)))))))))
            (when warnings
              (notmuch-logged-error (car warnings) (cdr warnings)))))
       (error
@@ -1018,14 +1022,10 @@ region if the region is active, or both `point' otherwise."
     (list (point) (point))))
 
 (define-obsolete-function-alias
-    'notmuch-search-interactive-region
-    'notmuch-interactive-region
+  'notmuch-search-interactive-region
+  'notmuch-interactive-region
   "notmuch 0.29")
 
 (provide 'notmuch-lib)
 
-;; Local Variables:
-;; byte-compile-warnings: (not cl-functions)
-;; End:
-
 ;;; notmuch-lib.el ends here
index ae56bacd50b549b395bf6c39460728b294fa6bf3..a9103a2069932b8fb050898ddf6b74578d5c428d 100644 (file)
@@ -1,28 +1,28 @@
-;;; notmuch-maildir-fcc.el ---
-
-;; This file 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 2, 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.
-
+;;; notmuch-maildir-fcc.el --- inserting using a fcc handler
+
+;; Copyright © Jesse Rosenthal
+;;
+;; This file is part of Notmuch.
+;;
+;; Notmuch 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.
+;;
+;; Notmuch 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 GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
-
-;;; Commentary:
-
-;; To use this as the fcc handler for message-mode,
-;; customize the notmuch-fcc-dirs variable
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
+;;
+;; Authors: Jesse Rosenthal <jrosenthal@jhu.edu>
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl-lib))
+
 (require 'message)
 
 (require 'notmuch-lib)
@@ -30,7 +30,7 @@
 (defvar notmuch-maildir-fcc-count 0)
 
 (defcustom notmuch-fcc-dirs "sent"
- "Determines the Fcc Header which says where to save outgoing mail.
 "Determines the Fcc Header which says where to save outgoing mail.
 
 Three types of values are permitted:
 
@@ -68,16 +68,16 @@ database.path option in the notmuch configuration file).
 In all cases you will be prompted to create the folder or
 directory if it does not exist yet when sending a mail."
 
- :type '(choice
-        (const :tag "No FCC header" nil)
-        (string :tag "A single folder")
-        (repeat :tag "A folder based on the From header"
-                (cons regexp (string :tag "Folder"))))
- :require 'notmuch-fcc-initialization
- :group 'notmuch-send)
 :type '(choice
+         (const :tag "No FCC header" nil)
+         (string :tag "A single folder")
+         (repeat :tag "A folder based on the From header"
+                 (cons regexp (string :tag "Folder"))))
 :require 'notmuch-fcc-initialization
 :group 'notmuch-send)
 
 (defcustom notmuch-maildir-use-notmuch-insert 't
-  "Should fcc use notmuch insert instead of simple fcc"
+  "Should fcc use notmuch insert instead of simple fcc."
   :type '(choice :tag "Fcc Method"
                 (const :tag "Use notmuch insert" t)
                 (const :tag "Use simple fcc" nil))
@@ -92,23 +92,19 @@ directory if it does not exist yet when sending a mail."
 Sets the Fcc header based on the values of `notmuch-fcc-dirs'.
 
 Originally intended to be use a hook function, but now called directly
-by notmuch-mua-mail"
-
+by notmuch-mua-mail."
   (let ((subdir
         (cond
          ((or (not notmuch-fcc-dirs)
               (message-field-value "Fcc"))
           ;; Nothing set or an existing header.
           nil)
-
          ((stringp notmuch-fcc-dirs)
           notmuch-fcc-dirs)
-
          ((and (listp notmuch-fcc-dirs)
                (stringp (car notmuch-fcc-dirs)))
           ;; Old style - no longer works.
           (error "Invalid `notmuch-fcc-dirs' setting (old style)"))
-
          ((listp notmuch-fcc-dirs)
           (let* ((from (message-field-value "From"))
                  (match
@@ -120,10 +116,8 @@ by notmuch-mua-mail"
                 (cdr match)
               (message "No Fcc header added.")
               nil)))
-
          (t
           (error "Invalid `notmuch-fcc-dirs' setting (neither string nor list)")))))
-
     (when subdir
       (if notmuch-maildir-use-notmuch-insert
          (notmuch-maildir-add-notmuch-insert-style-fcc-header subdir)
@@ -132,10 +126,10 @@ by notmuch-mua-mail"
 (defun notmuch-maildir-add-notmuch-insert-style-fcc-header (subdir)
   ;; Notmuch insert does not accept absolute paths, so check the user
   ;; really want this header inserted.
-
   (when (or (not (= (elt subdir 0) ?/))
-           (y-or-n-p (format "Fcc header %s is an absolute path and notmuch insert is requested.\nInsert header anyway? "
-                             subdir)))
+           (y-or-n-p
+            (format "Fcc header %s is an absolute path and notmuch insert is requested.
+Insert header anyway? " subdir)))
     (message-add-header (concat "Fcc: " subdir))))
 
 (defun notmuch-maildir-add-file-style-fcc-header (subdir)
@@ -220,7 +214,7 @@ This inserts the current buffer as a message into the notmuch
 database in folder FOLDER. If CREATE is non-nil it will supply
 the --create-folder flag to create the folder if necessary. TAGS
 should be a list of tag changes to apply to the inserted message."
-  (let* ((args (append (when create (list "--create-folder"))
+  (let* ((args (append (and create (list "--create-folder"))
                       (list (concat "--folder=" folder))
                       tags)))
     (apply 'notmuch-call-notmuch-process
@@ -248,15 +242,14 @@ If CREATE is non-nil then create the folder if necessary."
       ;; typo, or just the user want a new folder, let the user decide
       ;; how to deal with it.
       (error
-       (let ((response (notmuch-read-char-choice
-                       "Insert failed: (r)etry, (c)reate folder, (i)gnore, or (e)dit the header? "
-                       '(?r ?c ?i ?e))))
-        (case response
-              (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header))
-              (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't))
-              (?i 't)
-              (?e (notmuch-maildir-fcc-with-notmuch-insert
-                   (read-from-minibuffer "Fcc header: " fcc-header)))))))))
+       (let ((response (read-char-choice "Insert failed: \
+\(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " '(?r ?c ?i ?e))))
+        (cl-case response
+          (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header))
+          (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't))
+          (?i 't)
+          (?e (notmuch-maildir-fcc-with-notmuch-insert
+               (read-from-minibuffer "Fcc header: " fcc-header)))))))))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -273,16 +266,16 @@ If CREATE is non-nil then create the folder if necessary."
                            t))
 
 (defun notmuch-maildir-fcc-make-uniq-maildir-id ()
-   (let* ((ftime (float-time))
-         (microseconds (mod (* 1000000 ftime) 1000000))
-         (hostname (notmuch-maildir-fcc-host-fixer (system-name))))
-     (setq notmuch-maildir-fcc-count (+ notmuch-maildir-fcc-count 1))
-     (format "%d.%d_%d_%d.%s"
-            ftime
-            (emacs-pid)
-            microseconds
-            notmuch-maildir-fcc-count
-            hostname)))
+  (let* ((ftime (float-time))
+        (microseconds (mod (* 1000000 ftime) 1000000))
+        (hostname (notmuch-maildir-fcc-host-fixer (system-name))))
+    (setq notmuch-maildir-fcc-count (+ notmuch-maildir-fcc-count 1))
+    (format "%d.%d_%d_%d.%s"
+           ftime
+           (emacs-pid)
+           microseconds
+           notmuch-maildir-fcc-count
+           hostname)))
 
 (defun notmuch-maildir-fcc-dir-is-maildir-p (dir)
   (and (file-exists-p (concat dir "/cur/"))
@@ -321,7 +314,7 @@ if successful, nil if not."
 (defun notmuch-maildir-fcc-move-tmp-to-cur (destdir msg-id &optional mark-seen)
   (add-name-to-file
    (concat destdir "/tmp/" msg-id)
-   (concat destdir "/cur/" msg-id ":2," (when mark-seen "S"))))
+   (concat destdir "/cur/" msg-id ":2," (and mark-seen "S"))))
 
 (defun notmuch-maildir-fcc-file-fcc (fcc-header)
   "Write the message to the file specified by FCC-HEADER.
@@ -332,19 +325,19 @@ if needed."
       (notmuch-maildir-fcc-write-buffer-to-maildir fcc-header 't)
     ;; The fcc-header is not a valid maildir see if the user wants to
     ;; fix it in some way.
-    (let* ((prompt (format "Fcc %s is not a maildir: (r)etry, (c)reate folder, (i)gnore, or  (e)dit the header? "
-                          fcc-header))
-           (response (notmuch-read-char-choice prompt '(?r ?c ?i ?e))))
-        (case response
-              (?r (notmuch-maildir-fcc-file-fcc fcc-header))
-              (?c (if (file-writable-p fcc-header)
-                      (notmuch-maildir-fcc-create-maildir fcc-header)
-                    (message "No permission to create %s." fcc-header)
-                    (sit-for 2))
-                  (notmuch-maildir-fcc-file-fcc fcc-header))
-              (?i 't)
-              (?e (notmuch-maildir-fcc-file-fcc
-                   (read-from-minibuffer "Fcc header: " fcc-header)))))))
+    (let* ((prompt (format "Fcc %s is not a maildir: \
+\(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " fcc-header))
+          (response (read-char-choice prompt '(?r ?c ?i ?e))))
+      (cl-case response
+       (?r (notmuch-maildir-fcc-file-fcc fcc-header))
+       (?c (if (file-writable-p fcc-header)
+               (notmuch-maildir-fcc-create-maildir fcc-header)
+             (message "No permission to create %s." fcc-header)
+             (sit-for 2))
+           (notmuch-maildir-fcc-file-fcc fcc-header))
+       (?i 't)
+       (?e (notmuch-maildir-fcc-file-fcc
+            (read-from-minibuffer "Fcc header: " fcc-header)))))))
 
 (defun notmuch-maildir-fcc-write-buffer-to-maildir (destdir &optional mark-seen)
   "Writes the current buffer to maildir destdir. If mark-seen is
index 0164472f7b48e90d7a4ad74c27c390bccb270dc4..c224207098556b1c34a40572f6b90aa008b06dc4 100644 (file)
@@ -60,7 +60,8 @@ the first is a notmuch query and the rest are the tag changes to
 be applied to the matching messages.")
 
 (defun notmuch-message-apply-queued-tag-changes ()
-  ;; Apply the tag changes queued in the buffer-local variable notmuch-message-queued-tag-changes.
+  ;; Apply the tag changes queued in the buffer-local variable
+  ;; notmuch-message-queued-tag-changes.
   (dolist (query-and-tags notmuch-message-queued-tag-changes)
     (notmuch-tag (car query-and-tags)
                 (cdr query-and-tags))))
index 7fdd76bc6d54d08a6f6241e40ca98af71f4cd1a3..03c7cc97364f83c4e9f24d7193e444c10b423653 100644 (file)
@@ -21,6 +21,8 @@
 
 ;;; Code:
 
+(eval-when-compile (require 'cl-lib))
+
 (require 'message)
 (require 'mm-view)
 (require 'format-spec)
@@ -30,8 +32,6 @@
 (require 'notmuch-draft)
 (require 'notmuch-message)
 
-(eval-when-compile (require 'cl))
-
 (declare-function notmuch-show-insert-body "notmuch-show" (msg body depth))
 (declare-function notmuch-fcc-header-setup "notmuch-maildir-fcc" ())
 (declare-function notmuch-maildir-message-do-fcc "notmuch-maildir-fcc" ())
@@ -40,7 +40,7 @@
 
 ;;
 
-(defcustom notmuch-mua-send-hook '(notmuch-mua-message-send-hook)
+(defcustom notmuch-mua-send-hook nil
   "Hook run before sending messages."
   :type 'hook
   :group 'notmuch-send
@@ -58,7 +58,7 @@ window/frame that will be destroyed when the buffer is killed.
 You may want to customize `message-kill-buffer-on-exit'
 accordingly."
    (when (< emacs-major-version 24)
-          " Due to a known bug in Emacs 23, you should not set
+     " Due to a known bug in Emacs 23, you should not set
 this to `new-window' if `message-kill-buffer-on-exit' is
 disabled: this would result in an incorrect behavior."))
   :group 'notmuch-send
@@ -106,13 +106,13 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
 This function specifies which parts of a mime message with
 multiple parts get a header."
   :type '(radio (const :tag "No part headers"
-                              notmuch-show-reply-insert-header-p-never)
+                      notmuch-show-reply-insert-header-p-never)
                (const :tag "All except multipart/* and hidden parts"
-                              notmuch-show-reply-insert-header-p-trimmed)
+                      notmuch-show-reply-insert-header-p-trimmed)
                (const :tag "Only for included text parts"
-                              notmuch-show-reply-insert-header-p-minimal)
+                      notmuch-show-reply-insert-header-p-minimal)
                (const :tag "Exactly as in show view"
-                              notmuch-show-insert-header-p)
+                      notmuch-show-insert-header-p)
                (function :tag "Other"))
   :group 'notmuch-reply)
 
@@ -137,17 +137,21 @@ Typically this is added to `notmuch-mua-send-hook'."
         ;; When the message mentions attachment...
         (save-excursion
           (message-goto-body)
-          (loop while (re-search-forward notmuch-mua-attachment-regexp (point-max) t)
-                ;; For every instance of the "attachment" string
-                ;; found, examine the text properties. If the text
-                ;; has either a `face' or `syntax-table' property
-                ;; then it is quoted text and should *not* cause the
-                ;; user to be asked about a missing attachment.
-                if (let ((props (text-properties-at (match-beginning 0))))
-                     (not (or (memq 'syntax-table props)
-                              (memq 'face props))))
-                return t
-                finally return nil))
+          ;; Limit search from reaching other possible parts of the message
+          (let ((search-limit (search-forward "\n<#" nil t)))
+            (message-goto-body)
+            (cl-loop while (re-search-forward notmuch-mua-attachment-regexp
+                                              search-limit t)
+                     ;; For every instance of the "attachment" string
+                     ;; found, examine the text properties. If the text
+                     ;; has either a `face' or `syntax-table' property
+                     ;; then it is quoted text and should *not* cause the
+                     ;; user to be asked about a missing attachment.
+                     if (let ((props (text-properties-at (match-beginning 0))))
+                          (not (or (memq 'syntax-table props)
+                                   (memq 'face props))))
+                     return t
+                     finally return nil)))
         ;; ...but doesn't have a part with a filename...
         (save-excursion
           (message-goto-body)
@@ -194,17 +198,19 @@ Typically this is added to `notmuch-mua-send-hook'."
 (defun notmuch-mua-add-more-hidden-headers ()
   "Add some headers to the list that are hidden by default."
   (mapc (lambda (header)
-         (when (not (member header message-hidden-headers))
+         (unless (member header message-hidden-headers)
            (push header message-hidden-headers)))
        notmuch-mua-hidden-headers))
 
 (defun notmuch-mua-reply-crypto (parts)
   "Add mml sign-encrypt flag if any part of original message is encrypted."
-  (loop for part in parts
-       if (notmuch-match-content-type (plist-get part :content-type) "multipart/encrypted")
-         do (mml-secure-message-sign-encrypt)
-       else if (notmuch-match-content-type (plist-get part :content-type) "multipart/*")
-         do (notmuch-mua-reply-crypto (plist-get part :content))))
+  (cl-loop for part in parts
+          if (notmuch-match-content-type (plist-get part :content-type)
+                                         "multipart/encrypted")
+          do (mml-secure-message-sign-encrypt)
+          else if (notmuch-match-content-type (plist-get part :content-type)
+                                              "multipart/*")
+          do (notmuch-mua-reply-crypto (plist-get part :content))))
 
 ;; There is a bug in emacs 23's message.el that results in a newline
 ;; not being inserted after the References header, so the next header
@@ -222,22 +228,17 @@ Typically this is added to `notmuch-mua-send-hook'."
        original)
     (when process-crypto
       (setq args (append args '("--decrypt=true"))))
-
     (if reply-all
        (setq args (append args '("--reply-to=all")))
       (setq args (append args '("--reply-to=sender"))))
     (setq args (append args (list query-string)))
-
     ;; Get the reply object as SEXP, and parse it into an elisp object.
     (setq reply (apply #'notmuch-call-notmuch-sexp args))
-
     ;; Extract the original message to simplify the following code.
     (setq original (plist-get reply :original))
-
     ;; Extract the headers of both the reply and the original message.
     (let* ((original-headers (plist-get original :headers))
           (reply-headers (plist-get reply :reply-headers)))
-
       ;; If sender is non-nil, set the From: header to its value.
       (when sender
        (plist-put reply-headers :From sender))
@@ -245,28 +246,27 @@ Typically this is added to `notmuch-mua-send-hook'."
          ;; Overlay the composition window on that being used to read
          ;; the original message.
          ((same-window-regexps '("\\*mail .*")))
-
-       ;; We modify message-header-format-alist to get around a bug in message.el.
-       ;; See the comment above on notmuch-mua-insert-references.
+       ;; We modify message-header-format-alist to get around
+       ;; a bug in message.el.  See the comment above on
+       ;; notmuch-mua-insert-references.
        (let ((message-header-format-alist
-              (loop for pair in message-header-format-alist
-                    if (eq (car pair) 'References)
-                    collect (cons 'References
-                                  (apply-partially
-                                   'notmuch-mua-insert-references
-                                   (cdr pair)))
-                    else
-                    collect pair)))
+              (cl-loop for pair in message-header-format-alist
+                       if (eq (car pair) 'References)
+                       collect (cons 'References
+                                     (apply-partially
+                                      'notmuch-mua-insert-references
+                                      (cdr pair)))
+                       else
+                       collect pair)))
          (notmuch-mua-mail (plist-get reply-headers :To)
                            (notmuch-sanitize (plist-get reply-headers :Subject))
                            (notmuch-headers-plist-to-alist reply-headers)
                            nil (notmuch-mua-get-switch-function))))
-
-      ;; Create a buffer-local queue for tag changes triggered when sending the reply
+      ;; Create a buffer-local queue for tag changes triggered when
+      ;; sending the reply.
       (when notmuch-message-replied-tags
        (setq-local notmuch-message-queued-tag-changes
                    (list (cons query-string notmuch-message-replied-tags))))
-
       ;; Insert the message body - but put it in front of the signature
       ;; if one is present, and after any other content
       ;; message*setup-hooks may have added to the message body already.
@@ -275,62 +275,57 @@ Typically this is added to `notmuch-mua-send-hook'."
        (narrow-to-region (point) (point-max))
        (goto-char (point-max))
        (if (re-search-backward message-signature-separator nil t)
-           (if message-signature-insert-empty-line
-               (forward-line -1))
+           (when message-signature-insert-empty-line
+             (forward-line -1))
          (goto-char (point-max))))
-
       (let ((from (plist-get original-headers :From))
            (date (plist-get original-headers :Date))
            (start (point)))
-
        ;; notmuch-mua-cite-function constructs a citation line based
        ;; on the From and Date headers of the original message, which
        ;; are assumed to be in the buffer.
        (insert "From: " from "\n")
        (insert "Date: " date "\n\n")
-
-       (insert (with-temp-buffer
-                 (let
-                     ;; Don't attempt to clean up messages, excerpt
-                     ;; citations, etc. in the original message before
-                     ;; quoting.
-                     ((notmuch-show-insert-text/plain-hook nil)
-                      ;; Don't omit long parts.
-                      (notmuch-show-max-text-part-size 0)
-                      ;; Insert headers for parts as appropriate for replying.
-                      (notmuch-show-insert-header-p-function notmuch-mua-reply-insert-header-p-function)
-                      ;; Ensure that any encrypted parts are
-                      ;; decrypted during the generation of the reply
-                      ;; text.
-                      (notmuch-show-process-crypto process-crypto)
-                      ;; Don't indent multipart sub-parts.
-                      (notmuch-show-indent-multipart nil))
-                   ;; We don't want sigstatus buttons (an information leak and usually wrong anyway).
-                   (letf (((symbol-function 'notmuch-crypto-insert-sigstatus-button) #'ignore)
-                          ((symbol-function 'notmuch-crypto-insert-encstatus-button) #'ignore))
-                         (notmuch-show-insert-body original (plist-get original :body) 0)
-                         (buffer-substring-no-properties (point-min) (point-max))))))
-
+       (insert
+        (with-temp-buffer
+          (let
+              ;; Don't attempt to clean up messages, excerpt
+              ;; citations, etc. in the original message before
+              ;; quoting.
+              ((notmuch-show-insert-text/plain-hook nil)
+               ;; Don't omit long parts.
+               (notmuch-show-max-text-part-size 0)
+               ;; Insert headers for parts as appropriate for replying.
+               (notmuch-show-insert-header-p-function
+                notmuch-mua-reply-insert-header-p-function)
+               ;; Ensure that any encrypted parts are
+               ;; decrypted during the generation of the reply
+               ;; text.
+               (notmuch-show-process-crypto process-crypto)
+               ;; Don't indent multipart sub-parts.
+               (notmuch-show-indent-multipart nil))
+            ;; We don't want sigstatus buttons (an information leak and usually wrong anyway).
+            (cl-letf (((symbol-function 'notmuch-crypto-insert-sigstatus-button) #'ignore)
+                      ((symbol-function 'notmuch-crypto-insert-encstatus-button) #'ignore))
+              (notmuch-show-insert-body original (plist-get original :body) 0)
+              (buffer-substring-no-properties (point-min) (point-max))))))
        (set-mark (point))
        (goto-char start)
        ;; Quote the original message according to the user's configured style.
        (funcall notmuch-mua-cite-function)))
-
     ;; Crypto processing based crypto content of the original message
     (when process-crypto
       (notmuch-mua-reply-crypto (plist-get original :body))))
-
   ;; Push mark right before signature, if any.
   (message-goto-signature)
   (unless (eobp)
     (end-of-line -1))
   (push-mark)
-
   (message-goto-body)
   (set-buffer-modified-p nil))
 
 (define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
-  "Notmuch message composition mode. Mostly like `message-mode'"
+  "Notmuch message composition mode. Mostly like `message-mode'."
   (notmuch-address-setup))
 
 (put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
@@ -342,7 +337,7 @@ Typically this is added to `notmuch-mua-send-hook'."
 
 (defun notmuch-mua-pop-to-buffer (name switch-function)
   "Pop to buffer NAME, and warn if it already exists and is
-modified. This function is notmuch addaptation of
+modified. This function is notmuch adaptation of
 `message-pop-to-buffer'."
   (let ((buffer (get-buffer name)))
     (if (and buffer
@@ -371,18 +366,18 @@ modified. This function is notmuch addaptation of
                                   return-action &rest ignored)
   "Invoke the notmuch mail composition window."
   (interactive)
-
   (when notmuch-mua-user-agent-function
     (let ((user-agent (funcall notmuch-mua-user-agent-function)))
-      (when (not (string= "" user-agent))
+      (unless (string= "" user-agent)
        (push (cons 'User-Agent user-agent) other-headers))))
-
   (unless (assq 'From other-headers)
     (push (cons 'From (message-make-from
-                      (notmuch-user-name) (notmuch-user-primary-email))) other-headers))
-
+                      (notmuch-user-name)
+                      (notmuch-user-primary-email)))
+         other-headers))
   (notmuch-mua-pop-to-buffer (message-buffer-name "mail" to)
-                            (or switch-function (notmuch-mua-get-switch-function)))
+                            (or switch-function
+                                (notmuch-mua-get-switch-function)))
   (let ((headers
         (append
          ;; The following is copied from `message-mail'
@@ -393,7 +388,8 @@ modified. This function is notmuch addaptation of
          ;; https://lists.gnu.org/archive/html/emacs-devel/2011-01/msg00337.html
          ;; We need to convert any string input, eg from rmail-start-mail.
          (dolist (h other-headers other-headers)
-           (if (stringp (car h)) (setcar h (intern (capitalize (car h))))))))
+           (when (stringp (car h))
+             (setcar h (intern (capitalize (car h))))))))
        (args (list yank-action send-actions))
        ;; Cause `message-setup-1' to do things relevant for mail,
        ;; such as observe `message-default-mail-headers'.
@@ -409,7 +405,6 @@ modified. This function is notmuch addaptation of
   (message-hide-headers)
   (set-buffer-modified-p nil)
   (notmuch-mua-maybe-set-window-dedicated)
-
   (message-goto-to))
 
 (defcustom notmuch-identities nil
@@ -430,19 +425,6 @@ the From: header is already filled in by notmuch."
 
 (defvar notmuch-mua-sender-history nil)
 
-;; Workaround: Running `ido-completing-read' in emacs 23.1, 23.2 and 23.3
-;; without some explicit initialization fill freeze the operation.
-;; Hence, we advice `ido-completing-read' to ensure required initialization
-;; is done.
-(if (and (= emacs-major-version 23) (< emacs-minor-version 4))
-    (defadvice ido-completing-read (before notmuch-ido-mode-init activate)
-      (ido-init-completion-maps)
-      (add-hook 'minibuffer-setup-hook 'ido-minibuffer-setup)
-      (add-hook 'choose-completion-string-functions
-               'ido-choose-completion-string)
-      (ad-disable-advice 'ido-completing-read 'before 'notmuch-ido-mode-init)
-      (ad-activate 'ido-completing-read)))
-
 (defun notmuch-mua-prompt-for-sender ()
   "Prompt for a sender from the user's configured identities."
   (if notmuch-identities
@@ -466,8 +448,8 @@ If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
 the From: address first."
   (interactive "P")
   (let ((other-headers
-        (when (or prompt-for-sender notmuch-always-prompt-for-sender)
-          (list (cons 'From (notmuch-mua-prompt-for-sender))))))
+        (and (or prompt-for-sender notmuch-always-prompt-for-sender)
+             (list (cons 'From (notmuch-mua-prompt-for-sender))))))
     (notmuch-mua-mail nil nil other-headers nil (notmuch-mua-get-switch-function))))
 
 (defun notmuch-mua-new-forward-messages (messages &optional prompt-for-sender)
@@ -476,16 +458,16 @@ the From: address first."
 If PROMPT-FOR-SENDER is non-nil, the user will be prompteed for
 the From: address."
   (let* ((other-headers
-         (when (or prompt-for-sender notmuch-always-prompt-for-sender)
-           (list (cons 'From (notmuch-mua-prompt-for-sender)))))
-        forward-subject  ;; Comes from the first message and is
-                         ;; applied later.
-        forward-references ;; List of accumulated message-references of forwarded messages
-        forward-queries) ;; List of corresponding message-query
-
+         (and (or prompt-for-sender notmuch-always-prompt-for-sender)
+              (list (cons 'From (notmuch-mua-prompt-for-sender)))))
+        ;; Comes from the first message and is applied later.
+        forward-subject
+        ;; List of accumulated message-references of forwarded messages.
+        forward-references
+        ;; List of corresponding message-query.
+        forward-queries)
     ;; Generate the template for the outgoing message.
     (notmuch-mua-mail nil "" other-headers nil (notmuch-mua-get-switch-function))
-
     (save-excursion
       ;; Insert all of the forwarded messages.
       (mapc (lambda (id)
@@ -495,7 +477,8 @@ the From: address."
                (with-current-buffer temp-buffer
                  (erase-buffer)
                  (let ((coding-system-for-read 'no-conversion))
-                   (call-process notmuch-command nil t nil "show" "--format=raw" id))
+                   (call-process notmuch-command nil t nil
+                                 "show" "--format=raw" id))
                  ;; Because we process the messages in reverse order,
                  ;; always generate a forwarded subject, then use the
                  ;; last (i.e. first) one.
@@ -510,7 +493,6 @@ the From: address."
            ;; `message-forward-make-body' always puts the message at
            ;; the top, so do them in reverse order.
            (reverse messages))
-
       ;; Add in the appropriate subject.
       (save-restriction
        (message-narrow-to-headers)
@@ -519,15 +501,13 @@ the From: address."
        (message-remove-header "References")
        (message-add-header (concat "References: "
                                    (mapconcat 'identity forward-references " "))))
-
-      ;; Create a buffer-local queue for tag changes triggered when sending the message
+      ;; Create a buffer-local queue for tag changes triggered when
+      ;; sending the message.
       (when notmuch-message-forwarded-tags
        (setq-local notmuch-message-queued-tag-changes
-                   (loop for id in forward-queries
-                         collect
-                         (cons id
-                               notmuch-message-forwarded-tags))))
-
+                   (cl-loop for id in forward-queries
+                            collect
+                            (cons id notmuch-message-forwarded-tags))))
       ;; `message-forward-make-body' shows the User-agent header.  Hide
       ;; it again.
       (message-hide-headers)
@@ -539,22 +519,19 @@ the From: address."
 If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
 the From: address first.  If REPLY-ALL is non-nil, the message
 will be addressed to all recipients of the source message."
-
-;; In current emacs (24.3) select-active-regions is set to t by
-;; default. The reply insertion code sets the region to the quoted
-;; message to make it easy to delete (kill-region or C-w). These two
-;; things combine to put the quoted message in the primary selection.
-;;
-;; This is not what the user wanted and is a privacy risk (accidental
-;; pasting of the quoted message). We can avoid some of the problems
-;; by let-binding select-active-regions to nil. This fixes if the
-;; primary selection was previously in a non-emacs window but not if
-;; it was in an emacs window. To avoid the problem in the latter case
-;; we deactivate mark.
-
-  (let ((sender
-        (when prompt-for-sender
-          (notmuch-mua-prompt-for-sender)))
+  ;; In current emacs (24.3) select-active-regions is set to t by
+  ;; default. The reply insertion code sets the region to the quoted
+  ;; message to make it easy to delete (kill-region or C-w). These two
+  ;; things combine to put the quoted message in the primary selection.
+  ;;
+  ;; This is not what the user wanted and is a privacy risk (accidental
+  ;; pasting of the quoted message). We can avoid some of the problems
+  ;; by let-binding select-active-regions to nil. This fixes if the
+  ;; primary selection was previously in a non-emacs window but not if
+  ;; it was in an emacs window. To avoid the problem in the latter case
+  ;; we deactivate mark.
+  (let ((sender (and prompt-for-sender
+                    (notmuch-mua-prompt-for-sender)))
        (select-active-regions nil))
     (notmuch-mua-reply query-string sender reply-all)
     (deactivate-mark)))
@@ -606,10 +583,11 @@ unencrypted.  Really send? "))))
   (run-hooks 'notmuch-mua-send-hook)
   (when (and (notmuch-mua-check-no-misplaced-secure-tag)
             (notmuch-mua-check-secure-tag-has-newline))
-    (letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc))
-         (if exit
-             (message-send-and-exit arg)
-           (message-send arg)))))
+    (cl-letf (((symbol-function 'message-do-fcc)
+              #'notmuch-maildir-message-do-fcc))
+      (if exit
+         (message-send-and-exit arg)
+       (message-send arg)))))
 
 (defun notmuch-mua-send-and-exit (&optional arg)
   (interactive "P")
@@ -623,11 +601,6 @@ unencrypted.  Really send? "))))
   (interactive)
   (message-kill-buffer))
 
-(defun notmuch-mua-message-send-hook ()
-  "The default function used for `notmuch-mua-send-hook', this
-simply runs the corresponding `message-mode' hook functions."
-  (run-hooks 'message-send-hook))
-
 ;;
 
 (define-mail-user-agent 'notmuch-user-agent
index bb0379c102f653c2df12b08afd012186f3003a3e..3aa5bd8ff1cdc45f6265d9f2133e9e8b9e070c57 100644 (file)
@@ -21,7 +21,7 @@
 
 ;;; Code:
 
-(require 'cl)
+(eval-when-compile (require 'cl-lib))
 
 (defun notmuch-sexp-create-parser ()
   "Return a new streaming S-expression parser.
@@ -38,14 +38,10 @@ can return 'retry to indicate that not enough input is available.
 The parser always consumes input from point in the current
 buffer.  Hence, the caller is allowed to delete any data before
 point and may resynchronize after an error by moving point."
-
   (vector 'notmuch-sexp-parser
-         ;; List depth
-         0
-         ;; Partial parse position marker
-         nil
-         ;; Partial parse state
-         nil))
+         0     ; List depth
+         nil   ; Partial parse position marker
+         nil)) ; Partial parse state
 
 (defmacro notmuch-sexp--depth (sp)         `(aref ,sp 1))
 (defmacro notmuch-sexp--partial-pos (sp)   `(aref ,sp 2))
@@ -60,7 +56,6 @@ parser is currently inside a list and the next token ends the
 list, this moves point just past the terminator and returns 'end.
 Otherwise, this moves point to just past the end of the value and
 returns the value."
-
   (skip-chars-forward " \n\r\t")
   (cond ((eobp) 'retry)
        ((= (char-after) ?\))
@@ -70,7 +65,7 @@ returns the value."
             ;; error to be consistent with all other code paths.
             (read (current-buffer))
           ;; Go up a level and return an end token
-          (decf (notmuch-sexp--depth sp))
+          (cl-decf (notmuch-sexp--depth sp))
           (forward-char)
           'end))
        ((= (char-after) ?\()
@@ -80,7 +75,7 @@ returns the value."
         ;; parse, extend the partial parse to figure out when we
         ;; have a complete list.
         (catch 'return
-          (when (null (notmuch-sexp--partial-state sp))
+          (unless (notmuch-sexp--partial-state sp)
             (let ((start (point)))
               (condition-case nil
                   (throw 'return (read (current-buffer)))
@@ -94,8 +89,8 @@ returns the value."
                                  (notmuch-sexp--partial-state sp)))
                      ;; A complete value is available if we've
                      ;; reached depth 0.
-                     (depth (first new-state)))
-                (assert (>= depth 0))
+                     (depth (car new-state)))
+                (cl-assert (>= depth 0))
                 (if (= depth 0)
                     ;; Reset partial parse state
                     (setf (notmuch-sexp--partial-state sp) nil
@@ -134,12 +129,11 @@ a list, it moves point past the token that opens the list and
 returns t.  Later calls to `notmuch-sexp-read' will return the
 elements inside the list.  If the input in buffer is not the
 beginning of a list, throw invalid-read-syntax."
-
   (skip-chars-forward " \n\r\t")
   (cond ((eobp) 'retry)
        ((= (char-after) ?\()
         (forward-char)
-        (incf (notmuch-sexp--depth sp))
+        (cl-incf (notmuch-sexp--depth sp))
         t)
        (t
         ;; Skip over the bad character like `read' does
@@ -151,7 +145,6 @@ beginning of a list, throw invalid-read-syntax."
 
 Moves point to the beginning of any trailing data or to the end
 of the buffer if there is only trailing whitespace."
-
   (skip-chars-forward " \n\r\t")
   (unless (eobp)
     (error "Trailing garbage following expression")))
@@ -173,7 +166,6 @@ complete value in the list.  It operates incrementally and should
 be called whenever the input buffer has been extended with
 additional data.  The caller just needs to ensure it does not
 move point in the input buffer."
-
   ;; Set up the initial state
   (unless (local-variable-p 'notmuch-sexp--parser)
     (set (make-local-variable 'notmuch-sexp--parser)
@@ -181,7 +173,7 @@ move point in the input buffer."
     (set (make-local-variable 'notmuch-sexp--state) 'begin))
   (let (done)
     (while (not done)
-      (case notmuch-sexp--state
+      (cl-case notmuch-sexp--state
        (begin
         ;; Enter the list
         (if (eq (notmuch-sexp-begin-list notmuch-sexp--parser) 'retry)
@@ -190,7 +182,7 @@ move point in the input buffer."
        (result
         ;; Parse a result
         (let ((result (notmuch-sexp-read notmuch-sexp--parser)))
-          (case result
+          (cl-case result
             (retry (setq done t))
             (end   (setq notmuch-sexp--state 'end))
             (t     (with-current-buffer result-buffer
@@ -204,8 +196,4 @@ move point in the input buffer."
 
 (provide 'notmuch-parser)
 
-;; Local Variables:
-;; byte-compile-warnings: (not cl-functions)
-;; End:
-
 ;;; notmuch-parser.el ends here
index de97baac1b0aed9d2de4e72a9ae01f6b4f511492..85c631de39e1128365bc1dae3d88b54b7708c4ac 100644 (file)
@@ -3,4 +3,4 @@
   "notmuch"
   %VERSION%
   "Emacs based front-end (MUA) for notmuch"
-  nil)
+  '((emacs "25.1")))
index d9b3d449538f614911b047be194ed35c10daa2f4..6dd9f775a9ddba858c9358dc569f70b462f073b1 100644 (file)
@@ -1,4 +1,4 @@
-;;; notmuch-print.el --- printing messages from notmuch.
+;;; notmuch-print.el --- printing messages from notmuch
 ;;
 ;; Copyright © David Edmondson
 ;;
index 563e4acf57df3ab2c017085aea4c03d0677c3b72..3cfccbc3e891aebb9829886457018e302d42f0ed 100644 (file)
 
 A thread is a forest or list of trees. A tree is a two element
 list where the first element is a message, and the second element
-is a possibly empty forest of replies.
-"
+is a possibly empty forest of replies."
   (let ((args '("show" "--format=sexp" "--format-version=4")))
-    (if notmuch-show-process-crypto
-       (setq args (append args '("--decrypt=true"))))
+    (when notmuch-show-process-crypto
+      (setq args (append args '("--decrypt=true"))))
     (setq args (append args search-terms))
     (apply #'notmuch-call-notmuch-sexp args)))
 
@@ -40,37 +39,36 @@ is a possibly empty forest of replies.
 ;; Mapping functions across collections of messages.
 
 (defun notmuch-query-map-aux  (mapper function seq)
-  "private function to do the actual mapping and flattening"
+  "Private function to do the actual mapping and flattening."
   (apply 'append
         (mapcar
-          (lambda (tree)
-            (funcall mapper function tree))
-          seq)))
+         (lambda (tree)
+           (funcall mapper function tree))
+         seq)))
 
 (defun notmuch-query-map-threads (fn threads)
-  "apply FN to every thread in  THREADS. Flatten results to a list.
-
-See the function notmuch-query-get-threads for more information."
+  "Apply function FN to every thread in THREADS.
+Flatten results to a list.  See the function
+`notmuch-query-get-threads' for more information."
   (notmuch-query-map-aux 'notmuch-query-map-forest fn threads))
 
 (defun notmuch-query-map-forest (fn forest)
-  "apply function to every message in a forest. Flatten results to a list.
-
-See the function notmuch-query-get-threads for more information.
-"
+  "Apply function FN to every message in FOREST.
+Flatten results to a list.  See the function
+`notmuch-query-get-threads' for more information."
   (notmuch-query-map-aux 'notmuch-query-map-tree fn forest))
 
 (defun notmuch-query-map-tree (fn tree)
-  "Apply function FN to every message in TREE. Flatten results to a list
-
-See the function notmuch-query-get-threads for more information."
+  "Apply function FN to every message in TREE.
+Flatten results to a list.  See the function
+`notmuch-query-get-threads' for more information."
   (cons (funcall fn (car tree)) (notmuch-query-map-forest fn (cadr tree))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Predefined queries
 
 (defun notmuch-query-get-message-ids (&rest search-terms)
-  "Return a list of message-ids of messages that match SEARCH-TERMS"
+  "Return a list of message-ids of messages that match SEARCH-TERMS."
   (notmuch-query-map-threads
    (lambda (msg) (plist-get msg :id))
    (notmuch-query-get-threads search-terms)))
index e13ca3d76c50c8c2e73012c835ea2014cb81fb55..b08ceb973e19b0836b8a210d7b5be73dc241cc4a 100644 (file)
@@ -1,4 +1,4 @@
-;;; notmuch-show.el --- displaying notmuch forests.
+;;; notmuch-show.el --- displaying notmuch forests
 ;;
 ;; Copyright © Carl Worth
 ;; Copyright © David Edmondson
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile
+  (require 'cl-lib)
+  (require 'pcase))
+
 (require 'mm-view)
 (require 'message)
 (require 'mm-decode)
 (declare-function notmuch-count-attachments "notmuch" (mm-handle))
 (declare-function notmuch-save-attachments "notmuch" (mm-handle &optional queryp))
 (declare-function notmuch-tree "notmuch-tree"
-                 (&optional query query-context target buffer-name open-target))
+                 (&optional query query-context target buffer-name
+                            open-target unthreaded))
 (declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
+(declare-function notmuch-unthreaded
+                 (&optional query query-context target buffer-name open-target))
 (declare-function notmuch-read-query "notmuch" (prompt))
 (declare-function notmuch-draft-resume "notmuch-draft" (id))
 
@@ -90,10 +96,11 @@ visible for any given message."
   :group 'notmuch-show
   :group 'notmuch-hooks)
 
-(defcustom notmuch-show-insert-text/plain-hook '(notmuch-wash-wrap-long-lines
-                                                notmuch-wash-tidy-citations
-                                                notmuch-wash-elide-blank-lines
-                                                notmuch-wash-excerpt-citations)
+(defcustom notmuch-show-insert-text/plain-hook
+  '(notmuch-wash-wrap-long-lines
+    notmuch-wash-tidy-citations
+    notmuch-wash-elide-blank-lines
+    notmuch-wash-excerpt-citations)
   "Functions used to improve the display of text/plain parts."
   :type 'hook
   :options '(notmuch-wash-convert-inline-patch-to-part
@@ -174,7 +181,7 @@ indentation."
 (make-variable-buffer-local 'notmuch-show-indent-content)
 
 (defvar notmuch-show-attachment-debug nil
-  "If t log stdout and stderr from attachment handlers
+  "If t log stdout and stderr from attachment handlers.
 
 When set to nil (the default) stdout and stderr from attachment
 handlers is discarded. When set to t the stdout and stderr from
@@ -261,11 +268,11 @@ position of the message in the thread."
   :group 'notmuch-show)
 
 (defmacro with-current-notmuch-show-message (&rest body)
-  "Evaluate body with current buffer set to the text of current message"
+  "Evaluate body with current buffer set to the text of current message."
   `(save-excursion
      (let ((id (notmuch-show-get-message-id)))
        (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
-         (with-current-buffer buf
+        (with-current-buffer buf
           (let ((coding-system-for-read 'no-conversion))
             (call-process notmuch-command nil t nil "show" "--format=raw" id))
           ,@body)
@@ -290,13 +297,12 @@ position of the message in the thread."
    ;;
    ;; Any MIME part not explicitly mentioned here will be handled by an
    ;; external viewer as configured in the various mailcap files.
-   (let ((mm-inline-media-tests '(
-                                 ("text/.*" ignore identity)
-                                 ("application/pgp-signature" ignore identity)
-                                 ("multipart/alternative" ignore identity)
-                                 ("multipart/mixed" ignore identity)
-                                 ("multipart/related" ignore identity)
-                                )))
+   (let ((mm-inline-media-tests
+         '(("text/.*" ignore identity)
+           ("application/pgp-signature" ignore identity)
+           ("multipart/alternative" ignore identity)
+           ("multipart/mixed" ignore identity)
+           ("multipart/related" ignore identity))))
      (mm-display-parts (mm-dissect-buffer)))))
 
 (defun notmuch-show-save-attachments ()
@@ -313,7 +319,6 @@ position of the message in the thread."
 
 FN is called with one argument, the message properties. It should
 operation on the contents of the current buffer."
-
   ;; Remake the header to ensure that all information is available.
   (let* ((to (notmuch-show-get-to))
         (cc (notmuch-show-get-cc))
@@ -322,7 +327,6 @@ operation on the contents of the current buffer."
         (date (notmuch-show-get-date))
         (tags (notmuch-show-get-tags))
         (depth (notmuch-show-get-depth))
-
         (header (concat
                  "Subject: " subject "\n"
                  "To: " to "\n"
@@ -342,8 +346,10 @@ operation on the contents of the current buffer."
         (indenting notmuch-show-indent-content))
     (with-temp-buffer
       (insert all)
-      (if indenting
-         (indent-rigidly (point-min) (point-max) (- (* notmuch-show-indent-messages-width depth))))
+      (when indenting
+       (indent-rigidly (point-min)
+                       (point-max)
+                       (- (* notmuch-show-indent-messages-width depth))))
       ;; Remove the original header.
       (goto-char (point-min))
       (re-search-forward "^$" (point-max) nil)
@@ -366,7 +372,6 @@ operation on the contents of the current buffer."
                'message-header-subject)
               (t
                'message-header-other))))
-
     (overlay-put (make-overlay (point) (re-search-forward ":"))
                 'face 'message-header-name)
     (overlay-put (make-overlay (point) (re-search-forward ".*$"))
@@ -387,69 +392,62 @@ operation on the contents of the current buffer."
   "Update the displayed tags of the current message."
   (save-excursion
     (goto-char (notmuch-show-message-top))
-    (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
-       (let ((inhibit-read-only t))
-         (replace-match (concat "("
-                                (notmuch-tag-format-tags tags (notmuch-show-get-prop :orig-tags))
-                                ")"))))))
+    (when (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
+      (let ((inhibit-read-only t))
+       (replace-match (concat "("
+                              (notmuch-tag-format-tags
+                               tags
+                               (notmuch-show-get-prop :orig-tags))
+                              ")"))))))
 
 (defun notmuch-clean-address (address)
   "Try to clean a single email ADDRESS for display. Return a cons
 cell of (AUTHOR_EMAIL AUTHOR_NAME). Return (ADDRESS nil) if
 parsing fails."
   (condition-case nil
-    (let (p-name p-address)
-      ;; It would be convenient to use `mail-header-parse-address',
-      ;; but that expects un-decoded mailbox parts, whereas our
-      ;; mailbox parts are already decoded (and hence may contain
-      ;; UTF-8). Given that notmuch should handle most of the awkward
-      ;; cases, some simple string deconstruction should be sufficient
-      ;; here.
-      (cond
-       ;; "User <user@dom.ain>" style.
-       ((string-match "\\(.*\\) <\\(.*\\)>" address)
-       (setq p-name (match-string 1 address)
-             p-address (match-string 2 address)))
-
-       ;; "<user@dom.ain>" style.
-       ((string-match "<\\(.*\\)>" address)
-       (setq p-address (match-string 1 address)))
-
-       ;; Everything else.
-       (t
-       (setq p-address address)))
-
-      (when p-name
-       ;; Remove elements of the mailbox part that are not relevant for
-       ;; display, even if they are required during transport:
-       ;;
-       ;; Backslashes.
-       (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
-
-       ;; Outer single and double quotes, which might be nested.
-       (loop
-        with start-of-loop
-        do (setq start-of-loop p-name)
-
-        when (string-match "^\"\\(.*\\)\"$" p-name)
-        do (setq p-name (match-string 1 p-name))
-
-        when (string-match "^'\\(.*\\)'$" p-name)
-        do (setq p-name (match-string 1 p-name))
-
-        until (string= start-of-loop p-name)))
-
-      ;; If the address is 'foo@bar.com <foo@bar.com>' then show just
-      ;; 'foo@bar.com'.
-      (when (string= p-name p-address)
-       (setq p-name nil))
-
-      (cons p-address p-name))
+      (let (p-name p-address)
+       ;; It would be convenient to use `mail-header-parse-address',
+       ;; but that expects un-decoded mailbox parts, whereas our
+       ;; mailbox parts are already decoded (and hence may contain
+       ;; UTF-8). Given that notmuch should handle most of the awkward
+       ;; cases, some simple string deconstruction should be sufficient
+       ;; here.
+       (cond
+        ;; "User <user@dom.ain>" style.
+        ((string-match "\\(.*\\) <\\(.*\\)>" address)
+         (setq p-name (match-string 1 address))
+         (setq p-address (match-string 2 address)))
+
+        ;; "<user@dom.ain>" style.
+        ((string-match "<\\(.*\\)>" address)
+         (setq p-address (match-string 1 address)))
+        ;; Everything else.
+        (t
+         (setq p-address address)))
+       (when p-name
+         ;; Remove elements of the mailbox part that are not relevant for
+         ;; display, even if they are required during transport:
+         ;;
+         ;; Backslashes.
+         (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
+         ;; Outer single and double quotes, which might be nested.
+         (cl-loop with start-of-loop
+                  do   (setq start-of-loop p-name)
+                  when (string-match "^\"\\(.*\\)\"$" p-name)
+                  do   (setq p-name (match-string 1 p-name))
+                  when (string-match "^'\\(.*\\)'$" p-name)
+                  do   (setq p-name (match-string 1 p-name))
+                  until (string= start-of-loop p-name)))
+       ;; If the address is 'foo@bar.com <foo@bar.com>' then show just
+       ;; 'foo@bar.com'.
+       (when (string= p-name p-address)
+         (setq p-name nil))
+       (cons p-address p-name))
     (error (cons address nil))))
 
 (defun notmuch-show-clean-address (address)
-  "Try to clean a single email ADDRESS for display.  Return
-unchanged ADDRESS if parsing fails."
+  "Try to clean a single email ADDRESS for display.
+Return unchanged ADDRESS if parsing fails."
   (let* ((clean-address (notmuch-clean-address address))
         (p-address (car clean-address))
         (p-name (cdr clean-address)))
@@ -462,16 +460,26 @@ unchanged ADDRESS if parsing fails."
 (defun notmuch-show-insert-headerline (headers date tags depth)
   "Insert a notmuch style headerline based on HEADERS for a
 message at DEPTH in the current thread."
-  (let ((start (point)))
-    (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
-           (notmuch-sanitize
-            (notmuch-show-clean-address (plist-get headers :From)))
+  (let ((start (point))
+       (from (notmuch-sanitize
+              (notmuch-show-clean-address (plist-get headers :From)))))
+    (when (string-match "\\cR" from)
+      ;; If the From header has a right-to-left character add
+      ;; invisible U+200E LEFT-TO-RIGHT MARK character which forces
+      ;; the header paragraph as left-to-right text.
+      (insert (propertize (string ?\x200e) 'invisible t)))
+    (insert (if notmuch-show-indent-content
+               (notmuch-show-spaces-n (* notmuch-show-indent-messages-width
+                                         depth))
+             "")
+           from
            " ("
            date
            ") ("
            (notmuch-tag-format-tags tags tags)
            ")\n")
-    (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face)))
+    (overlay-put (make-overlay start (point))
+                'face 'notmuch-message-summary-face)))
 
 (defun notmuch-show-insert-header (header header-value)
   "Insert a single header."
@@ -483,9 +491,9 @@ message at DEPTH in the current thread."
     (mapc (lambda (header)
            (let* ((header-symbol (intern (concat ":" header)))
                   (header-value (plist-get headers header-symbol)))
-             (if (and header-value
-                      (not (string-equal "" header-value)))
-                 (notmuch-show-insert-header header header-value))))
+             (when (and header-value
+                        (not (string-equal "" header-value)))
+               (notmuch-show-insert-header header header-value))))
          notmuch-message-headers)
     (save-excursion
       (save-restriction
@@ -498,23 +506,19 @@ message at DEPTH in the current thread."
   'face 'message-mml
   :supertype 'notmuch-button-type)
 
-(defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment)
-  (let ((button)
-       (base-label (concat (when name (concat name ": "))
+(defun notmuch-show-insert-part-header (nth content-type declared-type
+                                           &optional name comment)
+  (let ((base-label (concat (and name (concat name ": "))
                            declared-type
-                           (unless (string-equal declared-type content-type)
-                             (concat " (as " content-type ")"))
+                           (and (not (string-equal declared-type content-type))
+                                (concat " (as " content-type ")"))
                            comment)))
-
-    (setq button
-         (insert-button
-          (concat "[ " base-label " ]")
-          :base-label base-label
-          :type 'notmuch-show-part-button-type
-          :notmuch-part-hidden nil))
-    (insert "\n")
-    ;; return button
-    button))
+    (prog1 (insert-button
+           (concat "[ " base-label " ]")
+           :base-label base-label
+           :type 'notmuch-show-part-button-type
+           :notmuch-part-hidden nil)
+      (insert "\n"))))
 
 (defun notmuch-show-toggle-part-invisibility (&optional button)
   (interactive)
@@ -522,8 +526,9 @@ message at DEPTH in the current thread."
     (when button
       (let ((overlay (button-get button 'overlay))
            (lazy-part (button-get button :notmuch-lazy-part)))
-       ;; We have a part to toggle if there is an overlay or if there is a lazy part.
-       ;; If neither is present we cannot toggle the part so we just return nil.
+       ;; We have a part to toggle if there is an overlay or if there
+       ;; is a lazy part.  If neither is present we cannot toggle the
+       ;; part so we just return nil.
        (when (or overlay lazy-part)
          (let* ((show (button-get button :notmuch-part-hidden))
                 (new-start (button-start button))
@@ -571,13 +576,13 @@ message at DEPTH in the current thread."
   ;; Recurse on sub-parts
   (let ((ctype (notmuch-split-content-type
                (downcase (plist-get part :content-type)))))
-    (cond ((equal (first ctype) "multipart")
+    (cond ((equal (car ctype) "multipart")
           (mapc (apply-partially #'notmuch-show--register-cids msg)
                 (plist-get part :content)))
          ((equal ctype '("message" "rfc822"))
           (notmuch-show--register-cids
            msg
-           (first (plist-get (first (plist-get part :content)) :body)))))))
+           (car (plist-get (car (plist-get part :content)) :body)))))))
 
 (defun notmuch-show--get-cid-content (cid)
   "Return a list (CID-content content-type) or nil.
@@ -588,8 +593,8 @@ enclosing angle brackets, a cid: prefix, or URL encoding.  This
 will return nil if the CID is unknown or cannot be retrieved."
   (let ((descriptor (cdr (assoc cid notmuch-show--cids))))
     (when descriptor
-      (let* ((msg (first descriptor))
-            (part (second descriptor))
+      (let* ((msg (car descriptor))
+            (part (cadr descriptor))
             ;; Request caching for this content, as some messages
             ;; reference the same cid: part many times (hundreds!).
             (content (notmuch-get-bodypart-binary
@@ -600,10 +605,10 @@ will return nil if the CID is unknown or cannot be retrieved."
 (defun notmuch-show-setup-w3m ()
   "Instruct w3m how to retrieve content from a \"related\" part of a message."
   (interactive)
-  (if (boundp 'w3m-cid-retrieve-function-alist)
-    (unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist)
-      (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve)
-           w3m-cid-retrieve-function-alist)))
+  (when (and (boundp 'w3m-cid-retrieve-function-alist)
+            (not (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist)))
+    (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve)
+         w3m-cid-retrieve-function-alist))
   (setq mm-html-inhibit-images nil))
 
 (defvar w3m-current-buffer) ;; From `w3m.el'.
@@ -614,8 +619,8 @@ will return nil if the CID is unknown or cannot be retrieved."
          (with-current-buffer w3m-current-buffer
            (notmuch-show--get-cid-content cid))))
     (when content-and-type
-      (insert (first content-and-type))
-      (second content-and-type))))
+      (insert (car content-and-type))
+      (cadr content-and-type))))
 
 ;; MIME part renderers
 
@@ -624,7 +629,8 @@ will return nil if the CID is unknown or cannot be retrieved."
          (plist-get part :content)))
 
 (defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth button)
-  (let ((chosen-type (car (notmuch-multipart/alternative-choose msg (notmuch-show-multipart/*-to-list part))))
+  (let ((chosen-type (car (notmuch-multipart/alternative-choose
+                          msg (notmuch-show-multipart/*-to-list part))))
        (inner-parts (plist-get part :content))
        (start (point)))
     ;; This inserts all parts of the chosen type rather than just one,
@@ -632,8 +638,8 @@ will return nil if the CID is unknown or cannot be retrieved."
     ;; should be chosen if there are more than one that match?
     (mapc (lambda (inner-part)
            (let* ((inner-type (plist-get inner-part :content-type))
-                 (hide (not (or notmuch-show-all-multipart/alternative-parts
-                          (string= chosen-type inner-type)))))
+                  (hide (not (or notmuch-show-all-multipart/alternative-parts
+                                 (string= chosen-type inner-type)))))
              (notmuch-show-insert-bodypart msg inner-part depth hide)))
          inner-parts)
 
@@ -644,14 +650,12 @@ will return nil if the CID is unknown or cannot be retrieved."
 (defun notmuch-show-insert-part-multipart/related (msg part content-type nth depth button)
   (let ((inner-parts (plist-get part :content))
        (start (point)))
-
     ;; Render the primary part.  FIXME: Support RFC 2387 Start header.
     (notmuch-show-insert-bodypart msg (car inner-parts) depth)
     ;; Add hidden buttons for the rest
     (mapc (lambda (inner-part)
            (notmuch-show-insert-bodypart msg inner-part depth t))
          (cdr inner-parts))
-
     (when notmuch-show-indent-multipart
       (indent-rigidly start (point) 1)))
   t)
@@ -659,18 +663,15 @@ will return nil if the CID is unknown or cannot be retrieved."
 (defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth button)
   (when button
     (button-put button 'face 'notmuch-crypto-part-header))
-
   ;; Insert a button detailing the signature status.
   (notmuch-crypto-insert-sigstatus-button (car (plist-get part :sigstatus))
                                          (notmuch-show-get-header :From msg))
-
   (let ((inner-parts (plist-get part :content))
        (start (point)))
     ;; Show all of the parts.
     (mapc (lambda (inner-part)
            (notmuch-show-insert-bodypart msg inner-part depth))
          inner-parts)
-
     (when notmuch-show-indent-multipart
       (indent-rigidly start (point) 1)))
   t)
@@ -678,21 +679,17 @@ will return nil if the CID is unknown or cannot be retrieved."
 (defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth button)
   (when button
     (button-put button 'face 'notmuch-crypto-part-header))
-
   ;; Insert a button detailing the encryption status.
   (notmuch-crypto-insert-encstatus-button (car (plist-get part :encstatus)))
-
   ;; Insert a button detailing the signature status.
   (notmuch-crypto-insert-sigstatus-button (car (plist-get part :sigstatus))
                                          (notmuch-show-get-header :From msg))
-
   (let ((inner-parts (plist-get part :content))
        (start (point)))
     ;; Show all of the parts.
     (mapc (lambda (inner-part)
            (notmuch-show-insert-bodypart msg inner-part depth))
          inner-parts)
-
     (when notmuch-show-indent-multipart
       (indent-rigidly start (point) 1)))
   t)
@@ -707,7 +704,6 @@ will return nil if the CID is unknown or cannot be retrieved."
     (mapc (lambda (inner-part)
            (notmuch-show-insert-bodypart msg inner-part depth))
          inner-parts)
-
     (when notmuch-show-indent-multipart
       (indent-rigidly start (point) 1)))
   t)
@@ -716,19 +712,15 @@ will return nil if the CID is unknown or cannot be retrieved."
   (let* ((message (car (plist-get part :content)))
         (body (car (plist-get message :body)))
         (start (point)))
-
     ;; Override `notmuch-message-headers' to force `From' to be
     ;; displayed.
     (let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date")))
       (notmuch-show-insert-headers (plist-get message :headers)))
-
     ;; Blank line after headers to be compatible with the normal
     ;; message display.
     (insert "\n")
-
     ;; Show the body
     (notmuch-show-insert-bodypart msg body depth)
-
     (when notmuch-show-indent-multipart
       (indent-rigidly start (point) 1)))
   t)
@@ -760,7 +752,8 @@ will return nil if the CID is unknown or cannot be retrieved."
              (unwind-protect
                  (progn
                    (unless (icalendar-import-buffer file t)
-                     (error "Icalendar import error. See *icalendar-errors* for more information"))
+                     (error "Icalendar import error. %s"
+                            "See *icalendar-errors* for more information"))
                    (set-buffer (get-file-buffer file))
                    (setq result (buffer-substring (point-min) (point-max)))
                    (set-buffer-modified-p nil)
@@ -773,42 +766,41 @@ will return nil if the CID is unknown or cannot be retrieved."
 (defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth button)
   (notmuch-show-insert-part-text/calendar msg part content-type nth depth button))
 
-(if (version< emacs-version "25.3")
-    ;; https://bugs.gnu.org/28350
-    ;;
-    ;; For newer emacs, we fall back to notmuch-show-insert-part-*/*
-    ;; (see notmuch-show-handlers-for)
-    (defun notmuch-show-insert-part-text/enriched (msg part content-type nth depth button)
-      ;; By requiring enriched below, we ensure that the function enriched-decode-display-prop
-      ;; is defined before it will be shadowed by the letf below. Otherwise the version
-      ;; in enriched.el may be loaded a bit later and used instead (for the first time).
-      (require 'enriched)
-      (letf (((symbol-function 'enriched-decode-display-prop)
-                (lambda (start end &optional param) (list start end))))
-       (notmuch-show-insert-part-*/* msg part content-type nth depth button))))
+(when (version< emacs-version "25.3")
+  ;; https://bugs.gnu.org/28350
+  ;;
+  ;; For newer emacs, we fall back to notmuch-show-insert-part-*/*
+  ;; (see notmuch-show-handlers-for)
+  (defun notmuch-show-insert-part-text/enriched
+      (msg part content-type nth depth button)
+    ;; By requiring enriched below, we ensure that the function
+    ;; enriched-decode-display-prop is defined before it will be
+    ;; shadowed by the letf below. Otherwise the version in
+    ;; enriched.el may be loaded a bit later and used instead (for
+    ;; the first time).
+    (require 'enriched)
+    (cl-letf (((symbol-function 'enriched-decode-display-prop)
+              (lambda (start end &optional param) (list start end))))
+      (notmuch-show-insert-part-*/* msg part content-type nth depth button))))
 
 (defun notmuch-show-get-mime-type-of-application/octet-stream (part)
   ;; If we can deduce a MIME type from the filename of the attachment,
   ;; we return that.
-  (if (plist-get part :filename)
-      (let ((extension (file-name-extension (plist-get part :filename)))
-           mime-type)
-       (if extension
-           (progn
-             (mailcap-parse-mimetypes)
-             (setq mime-type (mailcap-extension-to-mime extension))
-             (if (and mime-type
-                      (not (string-equal mime-type "application/octet-stream")))
-                 mime-type
-               nil))
-         nil))))
+  (and (plist-get part :filename)
+       (let ((extension (file-name-extension (plist-get part :filename))))
+        (and extension
+             (progn
+               (mailcap-parse-mimetypes)
+               (let ((mime-type (mailcap-extension-to-mime extension)))
+                 (and mime-type
+                      (not (string-equal mime-type "application/octet-stream"))
+                      mime-type)))))))
 
 (defun notmuch-show-insert-part-text/html (msg part content-type nth depth button)
   (if (eq mm-text-html-renderer 'shr)
       ;; It's easier to drive shr ourselves than to work around the
       ;; goofy things `mm-shr' does (like irreversibly taking over
       ;; content ID handling).
-
       ;; FIXME: If we block an image, offer a button to load external
       ;; images.
       (let ((shr-blocked-images notmuch-show-text/html-blocked-images))
@@ -841,7 +833,7 @@ will return nil if the CID is unknown or cannot be retrieved."
           ;; shr strips the "cid:" part of URL, but doesn't
           ;; URL-decode it (see RFC 2392).
           (let ((cid (url-unhex-string url)))
-            (first (notmuch-show--get-cid-content cid))))))
+            (car (notmuch-show--get-cid-content cid))))))
     (shr-insert-document dom)
     t))
 
@@ -856,8 +848,8 @@ will return nil if the CID is unknown or cannot be retrieved."
   "Return a list of content handlers for a part of type CONTENT-TYPE."
   (let (result)
     (mapc (lambda (func)
-           (if (functionp func)
-               (push func result)))
+           (when (functionp func)
+             (push func result)))
          ;; Reverse order of prefrence.
          (list (intern (concat "notmuch-show-insert-part-*/*"))
                (intern (concat
@@ -871,19 +863,19 @@ will return nil if the CID is unknown or cannot be retrieved."
 
 (defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth button)
   ;; Run the handlers until one of them succeeds.
-  (loop for handler in (notmuch-show-handlers-for content-type)
-       until (condition-case err
-                 (funcall handler msg part content-type nth depth button)
-               ;; Specifying `debug' here lets the debugger run if
-               ;; `debug-on-error' is non-nil.
-               ((debug error)
-                (insert "!!! Bodypart handler `" (prin1-to-string handler) "' threw an error:\n"
-                        "!!! " (error-message-string err) "\n")
-                nil))))
+  (cl-loop for handler in (notmuch-show-handlers-for content-type)
+          until (condition-case err
+                    (funcall handler msg part content-type nth depth button)
+                  ;; Specifying `debug' here lets the debugger run if
+                  ;; `debug-on-error' is non-nil.
+                  ((debug error)
+                   (insert "!!! Bodypart handler `" (prin1-to-string handler)
+                           "' threw an error:\n"
+                           "!!! " (error-message-string err) "\n")
+                   nil))))
 
 (defun notmuch-show-create-part-overlays (button beg end)
-  "Add an overlay to the part between BEG and END"
-
+  "Add an overlay to the part between BEG and END."
   ;; If there is no button (i.e., the part is text/plain and the first
   ;; part) or if the part has no content then we don't make the part
   ;; toggleable.
@@ -893,8 +885,7 @@ will return nil if the CID is unknown or cannot be retrieved."
     t))
 
 (defun notmuch-show-record-part-information (part beg end)
-  "Store PART as a text property from BEG to END"
-
+  "Store PART as a text property from BEG to END."
   ;; Record part information.  Since we already inserted subparts,
   ;; don't override existing :notmuch-part properties.
   (notmuch-map-text-property beg end :notmuch-part
@@ -905,13 +896,15 @@ will return nil if the CID is unknown or cannot be retrieved."
   ;; watch out for sticky specs of t, which means all properties are
   ;; front-sticky/rear-nonsticky.
   (notmuch-map-text-property beg end 'front-sticky
-                            (lambda (v) (if (listp v)
-                                            (pushnew :notmuch-part v)
-                                          v)))
+                            (lambda (v)
+                              (if (listp v)
+                                  (cl-pushnew :notmuch-part v)
+                                v)))
   (notmuch-map-text-property beg end 'rear-nonsticky
-                            (lambda (v) (if (listp v)
-                                            (pushnew :notmuch-part v)
-                                          v))))
+                            (lambda (v)
+                              (if (listp v)
+                                  (cl-pushnew :notmuch-part v)
+                                v))))
 
 (defun notmuch-show-lazy-part (part-args button)
   ;; Insert the lazy part after the button for the part. We would just
@@ -936,10 +929,12 @@ will return nil if the CID is unknown or cannot be retrieved."
        (narrow-to-region part-beg part-end)
        (delete-region part-beg part-end)
        (apply #'notmuch-show-insert-bodypart-internal part-args)
-       (indent-rigidly part-beg part-end (* notmuch-show-indent-messages-width depth)))
+       (indent-rigidly part-beg
+                       part-end
+                       (* notmuch-show-indent-messages-width depth)))
       (goto-char part-end)
       (delete-char 1)
-      (notmuch-show-record-part-information (second part-args)
+      (notmuch-show-record-part-information (cadr part-args)
                                            (button-start button)
                                            part-end)
       ;; Create the overlay. If the lazy-part turned out to be empty/not
@@ -988,33 +983,32 @@ this part.")
 HIDE determines whether to show or hide the part and the button
 as follows: If HIDE is nil, show the part and the button. If HIDE
 is t, hide the part initially and show the button."
-
   (let* ((content-type (downcase (plist-get part :content-type)))
         (mime-type (notmuch-show-mime-type part))
         (nth (plist-get part :id))
         (long (and (notmuch-match-content-type mime-type "text/*")
                    (> notmuch-show-max-text-part-size 0)
-                   (> (length (plist-get part :content)) notmuch-show-max-text-part-size)))
+                   (> (length (plist-get part :content))
+                      notmuch-show-max-text-part-size)))
         (beg (point))
         ;; This default header-p function omits the part button for
         ;; the first (or only) part if this is text/plain.
-        (button (when (funcall notmuch-show-insert-header-p-function part hide)
-                  (notmuch-show-insert-part-header nth mime-type content-type (plist-get part :filename))))
+        (button (and (funcall notmuch-show-insert-header-p-function part hide)
+                     (notmuch-show-insert-part-header
+                      nth mime-type content-type
+                      (plist-get part :filename))))
         ;; Hide the part initially if HIDE is t, or if it is too long
         ;; and we have a button to allow toggling.
         (show-part (not (or (equal hide t)
                             (and long button))))
         (content-beg (point)))
-
     ;; Store the computed mime-type for later use (e.g. by attachment handlers).
     (plist-put part :computed-type mime-type)
-
     (if show-part
-        (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
+       (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
       (when button
        (button-put button :notmuch-lazy-part
                    (list msg part mime-type nth depth button))))
-
     ;; Some of the body part handlers leave point somewhere up in the
     ;; part, so we make sure that we're down at the end.
     (goto-char (point-max))
@@ -1031,12 +1025,10 @@ is t, hide the part initially and show the button."
 
 (defun notmuch-show-insert-body (msg body depth)
   "Insert the body BODY at depth DEPTH in the current thread."
-
   ;; Register all content IDs for this message.  According to RFC
   ;; 2392, content IDs are *global*, but it's okay if an MUA treats
   ;; them as only global within a message.
-  (notmuch-show--register-cids msg (first body))
-
+  (notmuch-show--register-cids msg (car body))
   (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
 
 (defun notmuch-show-make-symbol (type)
@@ -1057,18 +1049,13 @@ is t, hide the part initially and show the button."
         content-start content-end
         headers-start headers-end
         (bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
-
     (setq message-start (point-marker))
-
     (notmuch-show-insert-headerline headers
-                                   (or (if notmuch-show-relative-dates
-                                           (plist-get msg :date_relative)
-                                         nil)
+                                   (or (and notmuch-show-relative-dates
+                                            (plist-get msg :date_relative))
                                        (plist-get headers :Date))
                                    (plist-get msg :tags) depth)
-
     (setq content-start (point-marker))
-
     ;; Set `headers-start' to point after the 'Subject:' header to be
     ;; compatible with the existing implementation. This just sets it
     ;; to after the first header.
@@ -1078,14 +1065,11 @@ is t, hide the part initially and show the button."
       ;; If the subject of this message is the same as that of the
       ;; previous message, don't display it when this message is
       ;; collapsed.
-      (when (not (string= notmuch-show-previous-subject
-                         bare-subject))
+      (unless (string= notmuch-show-previous-subject bare-subject)
        (forward-line 1))
       (setq headers-start (point-marker)))
     (setq headers-end (point-marker))
-
     (setq notmuch-show-previous-subject bare-subject)
-
     ;; A blank line between the headers and the body.
     (insert "\n")
     (notmuch-show-insert-body msg (plist-get msg :body)
@@ -1094,32 +1078,28 @@ is t, hide the part initially and show the button."
     (unless (bolp)
       (insert "\n"))
     (setq content-end (point-marker))
-
     ;; Indent according to the depth in the thread.
-    (if notmuch-show-indent-content
-       (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth)))
-
+    (when notmuch-show-indent-content
+      (indent-rigidly content-start
+                     content-end
+                     (* notmuch-show-indent-messages-width depth)))
     (setq message-end (point-max-marker))
-
     ;; Save the extents of this message over the whole text of the
     ;; message.
-    (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end))
-
+    (put-text-property message-start message-end
+                      :notmuch-message-extent
+                      (cons message-start message-end))
     ;; Create overlays used to control visibility
     (plist-put msg :headers-overlay (make-overlay headers-start headers-end))
     (plist-put msg :message-overlay (make-overlay headers-start content-end))
-
     (plist-put msg :depth depth)
-
     ;; Save the properties for this message. Currently this saves the
     ;; entire message (augmented it with other stuff), which seems
     ;; like overkill. We might save a reduced subset (for example, not
     ;; the content).
     (notmuch-show-set-message-properties msg)
-
     ;; Set header visibility.
     (notmuch-show-headers-visible msg notmuch-message-headers-visible)
-
     ;; Message visibility depends on whether it matched the search
     ;; criteria.
     (notmuch-show-message-visible msg (and (plist-get msg :match)
@@ -1137,7 +1117,8 @@ is t, hide the part initially and show the button."
 (defun notmuch-show-toggle-elide-non-matching ()
   "Toggle the display of non-matching messages."
   (interactive)
-  (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages))
+  (setq notmuch-show-elide-non-matching-messages
+       (not notmuch-show-elide-non-matching-messages))
   (message (if notmuch-show-elide-non-matching-messages
               "Showing matching messages only."
             "Showing all messages."))
@@ -1218,13 +1199,13 @@ buttons for a corresponding notmuch search."
                      (url-unhex-string (match-string 0 mid-cid)))))
          (push (list (match-beginning 0) (match-end 0)
                      (notmuch-id-to-query mid)) links)))
-      (dolist (link links)
+      (pcase-dolist (`(,beg ,end ,link) links)
        ;; Remove the overlay created by goto-address-mode
-       (remove-overlays (first link) (second link) 'goto-address t)
-       (make-text-button (first link) (second link)
+       (remove-overlays beg end 'goto-address t)
+       (make-text-button beg end
                          :type 'notmuch-button-type
                          'action `(lambda (arg)
-                                    (notmuch-show ,(third link) current-prefix-arg))
+                                    (notmuch-show ,link current-prefix-arg))
                          'follow-link t
                          'help-echo "Mouse-1, RET: search for this message"
                          'face goto-address-mail-face)))))
@@ -1263,38 +1244,33 @@ matched."
                    (eval (car (get 'mm-inline-override-types 'standard-value))))
             (cons "application/*" mm-inline-override-types)
           mm-inline-override-types)))
-    (switch-to-buffer (get-buffer-create buffer-name))
+    (pop-to-buffer-same-window (get-buffer-create buffer-name))
     ;; No need to track undo information for this buffer.
     (setq buffer-undo-list t)
-
     (notmuch-show-mode)
-
     ;; Set various buffer local variables to their appropriate initial
     ;; state. Do this after enabling `notmuch-show-mode' so that they
     ;; aren't wiped out.
-    (setq notmuch-show-thread-id thread-id
-         notmuch-show-parent-buffer parent-buffer
-         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.
-         notmuch-show-elide-non-matching-messages
+    (setq notmuch-show-thread-id thread-id)
+    (setq notmuch-show-parent-buffer parent-buffer)
+    (setq notmuch-show-query-context
+         (if (or (string= query-context "")
+                 (string= query-context "*"))
+             nil
+           query-context))
+    (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
+    ;; If `elide-toggle', invert the default value.
+    (setq notmuch-show-elide-non-matching-messages
          (if elide-toggle
              (not notmuch-show-only-matching-messages)
            notmuch-show-only-matching-messages))
-
     (add-hook 'post-command-hook #'notmuch-show-command-hook nil t)
     (jit-lock-register #'notmuch-show-buttonise-links)
-
     (notmuch-tag-clear-cache)
-
     (let ((inhibit-read-only t))
       (if (notmuch-show--build-buffer)
          ;; Messages were inserted into the buffer.
          (current-buffer)
-
        ;; No messages were inserted - presumably none matched the
        ;; query.
        (kill-buffer (current-buffer))
@@ -1311,7 +1287,8 @@ and THREAD.  The next query is THREAD alone, and serves as a
 fallback if the prior matches no messages."
   (let (queries)
     (push (list thread) queries)
-    (if context (push (list thread "and (" context ")") queries))
+    (when context
+      (push (list thread "and (" context ")") queries))
     queries))
 
 (defun notmuch-show--build-buffer (&optional state)
@@ -1322,8 +1299,8 @@ first relevant message.
 
 If no messages match the query return NIL."
   (let* ((cli-args (cons "--exclude=false"
-                        (when notmuch-show-elide-non-matching-messages
-                          (list "--entire-thread=false"))))
+                        (and notmuch-show-elide-non-matching-messages
+                             (list "--entire-thread=false"))))
         (queries (notmuch-show--build-queries
                   notmuch-show-thread-id notmuch-show-query-context))
         (forest nil)
@@ -1337,26 +1314,21 @@ If no messages match the query return NIL."
       (setq queries (cdr queries)))
     (when forest
       (notmuch-show-insert-forest forest)
-
       ;; Store the original tags for each message so that we can
       ;; display changes.
       (notmuch-show-mapc
        (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags))))
-
       ;; Set the header line to the subject of the first message.
       (setq header-line-format
            (replace-regexp-in-string "%" "%%"
                                      (notmuch-sanitize
                                       (notmuch-show-strip-re
                                        (notmuch-show-get-subject)))))
-
       (run-hooks 'notmuch-show-hook)
-
       (if state
          (notmuch-show-apply-state state)
        ;; With no state to apply, just go to the first message.
        (notmuch-show-goto-first-wanted-message)))
-
     ;; Report back to the caller whether any messages matched.
     forest))
 
@@ -1374,7 +1346,7 @@ This includes:
     (list win-id-combo (notmuch-show-get-message-ids-for-open-messages))))
 
 (defun notmuch-show-get-query ()
-  "Return the current query in this show buffer"
+  "Return the current query in this show buffer."
   (if notmuch-show-query-context
       (concat notmuch-show-thread-id
              " and ("
@@ -1385,9 +1357,9 @@ This includes:
 (defun notmuch-show-goto-message (msg-id)
   "Go to message with msg-id."
   (goto-char (point-min))
-  (unless (loop if (string= msg-id (notmuch-show-get-message-id))
-               return t
-               until (not (notmuch-show-goto-message-next)))
+  (unless (cl-loop if (string= msg-id (notmuch-show-get-message-id))
+                  return t
+                  until (not (notmuch-show-goto-message-next)))
     (goto-char (point-min))
     (message "Message-id not found."))
   (notmuch-show-message-adjust))
@@ -1401,13 +1373,12 @@ This includes:
  - moving to the correct current message in every displayed window."
   (let ((win-msg-alist (car state))
        (open (cadr state)))
-
     ;; Open those that were open.
     (goto-char (point-min))
-    (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
-                                          (member (notmuch-show-get-message-id) open))
-         until (not (notmuch-show-goto-message-next)))
-
+    (cl-loop do (notmuch-show-message-visible
+                (notmuch-show-get-message-properties)
+                (member (notmuch-show-get-message-id) open))
+            until (not (notmuch-show-goto-message-next)))
     (dolist (win-msg-pair win-msg-alist)
       (with-selected-window (car win-msg-pair)
        ;; Go to the previously open message in this window
@@ -1429,7 +1400,6 @@ reset based on the original query."
     ;; manually.
     (remove-overlays)
     (erase-buffer)
-
     (unless (notmuch-show--build-buffer state)
       ;; No messages were inserted.
       (kill-buffer (current-buffer))
@@ -1452,7 +1422,7 @@ reset based on the original query."
     (define-key map "G" 'notmuch-show-stash-git-send-email)
     (define-key map "?" 'notmuch-subkeymap-help)
     map)
-  "Submap for stash commands")
+  "Submap for stash commands.")
 (fset 'notmuch-show-stash-map notmuch-show-stash-map)
 
 (defvar notmuch-show-part-map
@@ -1464,13 +1434,14 @@ reset based on the original query."
     (define-key map "m" 'notmuch-show-choose-mime-of-part)
     (define-key map "?" 'notmuch-subkeymap-help)
     map)
-  "Submap for part commands")
+  "Submap for part commands.")
 (fset 'notmuch-show-part-map notmuch-show-part-map)
 
 (defvar notmuch-show-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map notmuch-common-keymap)
     (define-key map "Z" 'notmuch-tree-from-show-current-query)
+    (define-key map "U" 'notmuch-unthreaded-from-show-current-query)
     (define-key map (kbd "<C-tab>") 'widget-backward)
     (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
     (define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
@@ -1545,20 +1516,27 @@ All currently available key bindings:
 
 \\{notmuch-show-mode-map}"
   (setq notmuch-buffer-refresh-function #'notmuch-show-refresh-view)
-  (setq buffer-read-only t
-       truncate-lines t)
+  (setq buffer-read-only t)
+  (setq truncate-lines t)
   (setq imenu-prev-index-position-function
-        #'notmuch-show-imenu-prev-index-position-function)
+       #'notmuch-show-imenu-prev-index-position-function)
   (setq imenu-extract-index-name-function
-        #'notmuch-show-imenu-extract-index-name-function))
+       #'notmuch-show-imenu-extract-index-name-function))
 
 (defun notmuch-tree-from-show-current-query ()
-  "Call notmuch tree with the current query"
+  "Call notmuch tree with the current query."
   (interactive)
   (notmuch-tree notmuch-show-thread-id
                notmuch-show-query-context
                (notmuch-show-get-message-id)))
 
+(defun notmuch-unthreaded-from-show-current-query ()
+  "Call notmuch unthreaded with the current query."
+  (interactive)
+  (notmuch-unthreaded notmuch-show-thread-id
+                     notmuch-show-query-context
+                     (notmuch-show-get-message-id)))
+
 (defun notmuch-show-move-to-message-top ()
   (goto-char (notmuch-show-message-top)))
 
@@ -1610,8 +1588,8 @@ of the current message."
 effects."
   (save-excursion
     (goto-char (point-min))
-    (loop do (funcall function)
-         while (notmuch-show-goto-message-next))))
+    (cl-loop do (funcall function)
+            while (notmuch-show-goto-message-next))))
 
 ;; Functions relating to the visibility of messages and their
 ;; components.
@@ -1630,7 +1608,8 @@ effects."
 (defun notmuch-show-set-message-properties (props)
   (save-excursion
     (notmuch-show-move-to-message-top)
-    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
+    (put-text-property (point) (+ (point) 1)
+                      :notmuch-message-properties props)))
 
 (defun notmuch-show-get-message-properties ()
   "Return the properties of the current message as a plist.
@@ -1765,8 +1744,8 @@ We only mark it read once: if it is changed back then that is a
 user decision and we should not override it."
   (when (and (notmuch-show-message-visible-p)
             (not (notmuch-show-get-prop :seen)))
-       (notmuch-show-mark-read)
-       (notmuch-show-set-prop :seen t)))
+    (notmuch-show-mark-read)
+    (notmuch-show-set-prop :seen t)))
 
 (defvar notmuch-show--seen-has-errored nil)
 (make-variable-buffer-local 'notmuch-show--seen-has-errored)
@@ -1783,8 +1762,9 @@ user decision and we should not override it."
           (setq notmuch-show--seen-has-errored 't)
           (setq header-line-format
                 (concat header-line-format
-                        (propertize "  [some mark read tag changes may have failed]"
-                                    'face font-lock-warning-face)))))))))
+                        (propertize
+                         "  [some mark read tag changes may have failed]"
+                         'face font-lock-warning-face)))))))))
 
 (defun notmuch-show-filter-thread (query)
   "Filter or LIMIT the current thread based on a new query string.
@@ -1805,12 +1785,11 @@ Reshows the current thread with matches defined by the new query-string."
     (let (message-ids done)
       (goto-char (point-min))
       (while (not done)
-       (if (notmuch-show-message-visible-p)
-           (setq message-ids (append message-ids (list (notmuch-show-get-message-id)))))
-       (setq done (not (notmuch-show-goto-message-next)))
-       )
-      message-ids
-      )))
+       (when (notmuch-show-message-visible-p)
+         (setq message-ids
+               (append message-ids (list (notmuch-show-get-message-id)))))
+       (setq done (not (notmuch-show-goto-message-next))))
+      message-ids)))
 
 ;; Commands typically bound to keys.
 
@@ -1839,16 +1818,13 @@ current window), advance to the next open message."
           (> visible-end-of-this-message (window-end)))
       ;; The bottom of this message is not visible - scroll.
       (scroll-up nil))
-
      ((not (= end-of-this-message (point-max)))
       ;; This is not the last message - move to the next visible one.
       (notmuch-show-next-open-message))
-
      ((not (= (point) (point-max)))
       ;; This is the last message, but the cursor is not at the end of
       ;; the buffer. Move it there.
       (goto-char (point-max)))
-
      (t
       ;; This is the last message - change the return value
       (setq ret t)))
@@ -1866,11 +1842,12 @@ archives the entire current thread, (apply changes in
 thread from the search from which this thread was originally
 shown."
   (interactive)
-  (if (notmuch-show-advance)
-      (notmuch-show-archive-thread-then-next)))
+  (when (notmuch-show-advance)
+    (notmuch-show-archive-thread-then-next)))
 
 (defun notmuch-show-rewind ()
-  "Backup through the thread (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
+  "Backup through the thread (reverse scrolling compared to \
+\\[notmuch-show-advance-and-archive]).
 
 Specifically, if the beginning of the previous email is fewer
 than `window-height' lines from the current point, move to it
@@ -1885,9 +1862,9 @@ any effects from previous calls to
   (let ((start-of-message (notmuch-show-message-top))
        (start-of-window (window-start)))
     (cond
-      ;; Either this message is properly aligned with the start of the
-      ;; window or the start of this message is not visible on the
-      ;; screen - scroll.
+     ;; Either this message is properly aligned with the start of the
+     ;; window or the start of this message is not visible on the
+     ;; screen - scroll.
      ((or (= start-of-message start-of-window)
          (< start-of-message start-of-window))
       (scroll-down)
@@ -1996,7 +1973,7 @@ to show, nil otherwise."
     (notmuch-show-message-visible props (plist-get props :match))))
 
 (defun notmuch-show-goto-first-wanted-message ()
-  "Move to the first open message and mark it read"
+  "Move to the first open message and mark it read."
   (goto-char (point-min))
   (unless (notmuch-show-message-visible-p)
     (notmuch-show-next-open-message))
@@ -2024,7 +2001,7 @@ to show, nil otherwise."
   (let* ((id (notmuch-show-get-message-id))
         (buf (get-buffer-create (concat "*notmuch-raw-" id "*")))
         (inhibit-read-only t))
-    (switch-to-buffer buf)
+    (pop-to-buffer-same-window buf)
     (erase-buffer)
     (let ((coding-system-for-read 'no-conversion))
       (call-process notmuch-command nil t nil "show" "--format=raw" id))
@@ -2062,11 +2039,14 @@ message."
        (setq shell-command
              (concat notmuch-command " show --format=mbox --exclude=false "
                      (shell-quote-argument
-                      (mapconcat 'identity (notmuch-show-get-message-ids-for-open-messages) " OR "))
+                      (mapconcat 'identity
+                                 (notmuch-show-get-message-ids-for-open-messages)
+                                 " OR "))
                      " | " command))
       (setq shell-command
            (concat notmuch-command " show --format=raw "
-                   (shell-quote-argument (notmuch-show-get-message-id)) " | " command)))
+                   (shell-quote-argument (notmuch-show-get-message-id))
+                   " | " command)))
     (let ((cwd default-directory)
          (buf (get-buffer-create (concat "*notmuch-pipe*"))))
       (with-current-buffer buf
@@ -2080,7 +2060,7 @@ message."
          (set-buffer-modified-p nil)
          (setq buffer-read-only t)
          (unless (zerop exit-code)
-           (switch-to-buffer-other-window buf)
+           (pop-to-buffer buf)
            (message (format "Command '%s' exited abnormally with code %d"
                             shell-command exit-code))))))))
 
@@ -2167,9 +2147,10 @@ argument, hide all of the messages."
   (interactive)
   (save-excursion
     (goto-char (point-min))
-    (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
-                                          (not current-prefix-arg))
-         until (not (notmuch-show-goto-message-next))))
+    (cl-loop do (notmuch-show-message-visible
+                (notmuch-show-get-message-properties)
+                (not current-prefix-arg))
+            until (not (notmuch-show-goto-message-next))))
   (force-window-update))
 
 (defun notmuch-show-next-button ()
@@ -2427,7 +2408,7 @@ MIME-TYPE is given then set the handle's mime-type to MIME-TYPE."
         (buf (notmuch-show-generate-part-buffer msg part))
         (computed-type (or mime-type (plist-get part :computed-type)))
         (filename (plist-get part :filename))
-        (disposition (if filename `(attachment (filename . ,filename)))))
+        (disposition (and filename `(attachment (filename . ,filename)))))
     (mm-make-handle buf (list computed-type) nil nil disposition)))
 
 (defun notmuch-show-apply-to-current-part-handle (fn &optional mime-type)
@@ -2480,7 +2461,6 @@ part to be treated as if it had that mime-type."
   (interactive)
   (notmuch-show-apply-to-current-part-handle #'mm-pipe-part))
 
-
 (defun notmuch-show--mm-display-part (handle)
   "Use mm-display-part to display HANDLE in a new buffer.
 
@@ -2488,7 +2468,7 @@ If the part is displayed in an external application then close
 the new buffer."
   (let ((buf (get-buffer-create (generate-new-buffer-name
                                 (concat " *notmuch-internal-part*")))))
-    (switch-to-buffer buf)
+    (pop-to-buffer-same-window buf)
     (if (eq (mm-display-part handle) 'external)
        (kill-buffer buf)
       (goto-char (point-min))
@@ -2496,11 +2476,12 @@ the new buffer."
       (view-buffer buf 'kill-buffer-if-not-modified))))
 
 (defun notmuch-show-choose-mime-of-part (mime-type)
-  "Choose the mime type to use for displaying part"
+  "Choose the mime type to use for displaying part."
   (interactive
    (list (completing-read "Mime type to use (default text/plain): "
                          (mailcap-mime-types) nil nil nil nil "text/plain")))
-  (notmuch-show-apply-to-current-part-handle #'notmuch-show--mm-display-part mime-type))
+  (notmuch-show-apply-to-current-part-handle #'notmuch-show--mm-display-part
+                                            mime-type))
 
 (defun notmuch-show-imenu-prev-index-position-function ()
   "Move point to previous message in notmuch-show buffer.
@@ -2527,9 +2508,9 @@ beginning of the line."
 message."
   `(save-excursion
      (save-restriction
-      (let ((extent (notmuch-show-message-extent)))
-       (narrow-to-region (car extent) (cdr extent))
-       ,@body))))
+       (let ((extent (notmuch-show-message-extent)))
+        (narrow-to-region (car extent) (cdr extent))
+        ,@body))))
 
 (defun notmuch-show--gather-urls ()
   "Gather any URLs in the current message."
@@ -2540,12 +2521,16 @@ message."
        (push (match-string-no-properties 0) urls))
      (reverse urls))))
 
-(defun notmuch-show-browse-urls ()
-  "Offer to browse any URLs in the current message."
-  (interactive)
-  (let ((urls (notmuch-show--gather-urls)))
+(defun notmuch-show-browse-urls (&optional kill)
+  "Offer to browse any URLs in the current message.
+With a prefix argument, copy the URL to the kill ring rather than
+browsing."
+  (interactive "P")
+  (let ((urls (notmuch-show--gather-urls))
+       (prompt (if kill "Copy URL to kill ring: " "Browse URL: "))
+       (fn (if kill #'kill-new #'browse-url)))
     (if urls
-       (browse-url (completing-read "Browse URL: " (cdr urls) nil nil (car urls)))
+       (funcall fn (completing-read prompt urls nil nil nil nil (car urls)))
       (message "No URLs found."))))
 
 (provide 'notmuch-show)
index 0500927d37ce2b1e0173fb0f6ff7bc159a85018e..5d4a6865c4c4ffd7aa98be764e0ada386fef4f02 100644 (file)
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
 ;;          Damien Cassou <damien.cassou@gmail.com>
-;;
+
 ;;; Code:
-;;
 
-(require 'cl)
+(require 'cl-lib)
+(eval-when-compile
+  (require 'pcase))
+
 (require 'crm)
-(require 'notmuch-lib)
 
-(declare-function notmuch-search-tag "notmuch" tag-changes)
-(declare-function notmuch-show-tag "notmuch-show" tag-changes)
-(declare-function notmuch-tree-tag "notmuch-tree" tag-changes)
+(require 'notmuch-lib)
 
-(autoload 'notmuch-jump "notmuch-jump")
+(declare-function notmuch-search-tag "notmuch"
+                 (tag-changes &optional beg end only-matched))
+(declare-function notmuch-show-tag "notmuch-show" (tag-changes))
+(declare-function notmuch-tree-tag "notmuch-tree" (tag-changes))
+(declare-function notmuch-jump "notmuch-jump" (action-map prompt))
 
 (define-widget 'notmuch-tag-key-type 'list
   "A single key tagging binding."
@@ -40,7 +43,9 @@
   :args '((list :inline t
                :format "%v"
                (key-sequence :tag "Key")
-               (radio :tag "Tag operations" (repeat :tag "Tag list" (string :format "%v" :tag "change"))
+               (radio :tag "Tag operations"
+                      (repeat :tag "Tag list"
+                              (string :format "%v" :tag "change"))
                       (variable :tag "Tag variable"))
                (string :tag "Name"))))
 
@@ -80,7 +85,7 @@ from TAGGING-OPERATIONS."
   :group 'notmuch-tag)
 
 (define-widget 'notmuch-tag-format-type 'lazy
-  "Customize widget for notmuch-tag-format and friends"
+  "Customize widget for notmuch-tag-format and friends."
   :type '(alist :key-type (regexp :tag "Tag")
                :extra-offset -3
                :value-type
@@ -230,7 +235,7 @@ DATA is the content of an SVG picture (e.g., as returned by
 (defun notmuch-tag-star-icon ()
   "Return SVG data representing a star icon.
 This can be used with `notmuch-tag-format-image-data'."
-"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
+  "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
 <svg version=\"1.1\" width=\"16\" height=\"16\">
   <g transform=\"translate(-242.81601,-315.59635)\">
     <path
@@ -277,10 +282,10 @@ This can be used with `notmuch-tag-format-image-data'."
   (save-match-data
     ;; Don't use assoc-default since there's no way to distinguish a
     ;; missing key from a present key with a null cdr.
-    (assoc* tag format-alist
-           :test (lambda (tag key)
-                   (and (eq (string-match key tag) 0)
-                        (= (match-end 0) (length tag)))))))
+    (cl-assoc tag format-alist
+             :test (lambda (tag key)
+                     (and (eq (string-match key tag) 0)
+                          (= (match-end 0) (length tag)))))))
 
 (defun notmuch-tag--do-format (tag formatted-tag formats)
   "Apply a tag-formats entry to TAG."
@@ -309,13 +314,15 @@ are not in TAGS) are shown using formats from
 are in TAGS but are not in ORIG-TAGS) are shown using formats
 from `notmuch-tag-added-formats' and tags which have not been
 changed (the normal case) are shown using formats from
-`notmuch-tag-formats'"
+`notmuch-tag-formats'."
   (let* ((tag-state (cond ((not (member tag tags)) 'deleted)
                          ((not (member tag orig-tags)) 'added)))
-        (formatted-tag (gethash (cons tag tag-state) notmuch-tag--format-cache 'missing)))
+        (formatted-tag (gethash (cons tag tag-state)
+                                notmuch-tag--format-cache
+                                'missing)))
     (when (eq formatted-tag 'missing)
       (let ((base (notmuch-tag--get-formats tag notmuch-tag-formats))
-           (over (case tag-state
+           (over (cl-case tag-state
                    (deleted (notmuch-tag--get-formats
                              tag notmuch-tag-deleted-formats))
                    (added (notmuch-tag--get-formats
@@ -323,7 +330,6 @@ changed (the normal case) are shown using formats from
                    (otherwise nil))))
        (setq formatted-tag (notmuch-tag--do-format tag tag base))
        (setq formatted-tag (notmuch-tag--do-format tag formatted-tag over))
-
        (puthash (cons tag tag-state) formatted-tag notmuch-tag--format-cache)))
     formatted-tag))
 
@@ -334,9 +340,9 @@ changed (the normal case) are shown using formats from
     (notmuch-apply-face
      (mapconcat #'identity
                ;; nil indicated that the tag was deliberately hidden
-               (delq nil (mapcar
-                          (apply-partially #'notmuch-tag-format-tag tags orig-tags)
-                          all-tags))
+               (delq nil (mapcar (apply-partially #'notmuch-tag-format-tag
+                                                  tags orig-tags)
+                                 all-tags))
                " ")
      face
      t)))
@@ -347,8 +353,7 @@ changed (the normal case) are shown using formats from
 'tag-changes' will contain the tags that are about to be added or removed as
 a list of strings of the form \"+TAG\" or \"-TAG\".
 'query' will be a string containing the search query that determines
-the messages that are about to be tagged"
-
+the messages that are about to be tagged."
   :type 'hook
   :options '(notmuch-hl-line-mode)
   :group 'notmuch-hooks)
@@ -359,7 +364,7 @@ the messages that are about to be tagged"
 'tag-changes' will contain the tags that were added or removed as
 a list of strings of the form \"+TAG\" or \"-TAG\".
 'query' will be a string containing the search query that determines
-the messages that were tagged"
+the messages that were tagged."
   :type 'hook
   :options '(notmuch-hl-line-mode)
   :group 'notmuch-hooks)
@@ -376,8 +381,8 @@ the messages that were tagged"
   "Return a list of tags for messages matching SEARCH-TERMS.
 
 Returns all tags if no search terms are given."
-  (if (null search-terms)
-      (setq search-terms (list "*")))
+  (unless search-terms
+    (setq search-terms (list "*")))
   (split-string
    (with-output-to-string
      (with-current-buffer standard-output
@@ -398,7 +403,6 @@ completions.  CURRENT-TAGS may contain duplicates.  PROMPT, if
 non-nil, is the query string to present in the minibuffer.  It
 defaults to \"Tags\".  INITIAL-INPUT, if non-nil, will be the
 initial input in the minibuffer."
-
   (let* ((all-tag-list (notmuch-tag-completions))
         (add-tag-list (mapcar (apply-partially 'concat "+") all-tag-list))
         (remove-tag-list (mapcar (apply-partially 'concat "-") current-tags))
@@ -436,7 +440,7 @@ from TAGS if present."
     (dolist (tag-change tag-changes)
       (let ((op (string-to-char tag-change))
            (tag (unless (string= tag-change "") (substring tag-change 1))))
-       (case op
+       (cl-case op
          (?+ (unless (member tag result-tags)
                (push tag result-tags)))
          (?- (setq result-tags (delete tag result-tags)))
@@ -511,28 +515,28 @@ and vice versa."
   ;; REVERSE is specified.
   (interactive "P")
   (let (action-map)
-    (dolist (binding notmuch-tagging-keys)
-      (let* ((tag-function (case major-mode
+    (pcase-dolist (`(,key ,tag ,name) notmuch-tagging-keys)
+      (let* ((tag-function (cl-case major-mode
                             (notmuch-search-mode #'notmuch-search-tag)
                             (notmuch-show-mode #'notmuch-show-tag)
                             (notmuch-tree-mode #'notmuch-tree-tag)))
-            (key (first binding))
-            (forward-tag-change (if (symbolp (second binding))
-                                    (symbol-value (second binding))
-                                  (second binding)))
+            (tag (if (symbolp tag)
+                     (symbol-value tag)
+                   tag))
             (tag-change (if reverse
-                            (notmuch-tag-change-list forward-tag-change 't)
-                          forward-tag-change))
-            (name (or (and (not (string= (third binding) ""))
-                           (third binding))
-                      (and (symbolp (second binding))
-                           (symbol-name (second binding)))))
+                            (notmuch-tag-change-list tag 't)
+                          tag))
+            (name (or (and (not (string= name ""))
+                           name)
+                      (and (symbolp name)
+                           (symbol-name name))))
             (name-string (if name
-                             (if reverse (concat "Reverse " name)
+                             (if reverse
+                                 (concat "Reverse " name)
                                name)
                            (mapconcat #'identity tag-change " "))))
        (push (list key name-string
-                    `(lambda () (,tag-function ',tag-change)))
+                   `(lambda () (,tag-function ',tag-change)))
              action-map)))
     (push (list notmuch-tag-jump-reverse-key
                (if reverse
@@ -547,6 +551,4 @@ and vice versa."
 
 (provide 'notmuch-tag)
 
-;; Local Variables:
-;; byte-compile-warnings: (not cl-functions)
-;; End:
+;;; notmuch-tag.el ends here
index c00315e8dd40edaadfc369a95b2545fce49affbf..f342f85aadd89bc5c9300426629c35124fa67f2c 100644 (file)
@@ -1,4 +1,4 @@
-;;; notmuch-tree.el --- displaying notmuch forests.
+;;; notmuch-tree.el --- displaying notmuch forests
 ;;
 ;; Copyright © Carl Worth
 ;; Copyright © David Edmondson
@@ -24,6 +24,8 @@
 
 ;;; Code:
 
+(eval-when-compile (require 'cl-lib))
+
 (require 'mail-parse)
 
 (require 'notmuch-lib)
 (require 'notmuch-show)
 (require 'notmuch-tag)
 (require 'notmuch-parser)
+(require 'notmuch-jump)
 
-(eval-when-compile (require 'cl))
-(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line))
+(declare-function notmuch-search "notmuch"
+                 (&optional query oldest-first target-thread target-line))
 (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
 (declare-function notmuch-read-query "notmuch" (prompt))
 (declare-function notmuch-search-find-thread-id "notmuch" (&optional bare))
 ;; the following variable is defined in notmuch.el
 (defvar notmuch-search-query-string)
 
+;; this variable distinguishes the unthreaded display from the normal tree display
+(defvar notmuch-tree-unthreaded nil
+  "A buffer local copy of argument unthreaded to the function notmuch-tree.")
+(make-variable-buffer-local 'notmuch-tree-unthreaded)
+
 (defgroup notmuch-tree nil
   "Showing message and thread structure."
   :group 'notmuch)
   :type 'boolean
   :group 'notmuch-tree)
 
+(defcustom notmuch-unthreaded-show-out t
+  "View selected messages in new window rather than split-pane."
+  :type 'boolean
+  :group 'notmuch-tree)
+
+(defun notmuch-tree-show-out ()
+  (if notmuch-tree-unthreaded
+      notmuch-unthreaded-show-out
+    notmuch-tree-show-out))
+
 (defcustom notmuch-tree-result-format
   `(("date" . "%12s  ")
     ("authors" . "%-20s")
     ((("tree" . "%s")("subject" . "%s")) ." %-54s ")
     ("tags" . "(%s)"))
-  "Result formatting for Tree view. Supported fields are: date,
-        authors, subject, tree, tags.  Tree means the thread tree
-        box graphics. The field may also be a list in which case
-        the formatting rules are applied recursively and then the
-        output of all the fields in the list is inserted
-        according to format-string.
+  "Result formatting for tree view. Supported fields are: date,
+authors, subject, tree, tags.  Tree means the thread tree
+box graphics. The field may also be a list in which case
+the formatting rules are applied recursively and then the
+output of all the fields in the list is inserted
+according to format-string.
 
 Note the author string should not contain
-        whitespace (put it in the neighbouring fields instead).
-        For example:
+whitespace (put it in the neighbouring fields instead).
+For example:
         (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
-                                             \(\"subject\" . \"%s\"\)\)\)"
+                                          \(\"subject\" . \"%s\"\)\)\)"
   :type '(alist :key-type (string) :value-type (string))
   :group 'notmuch-tree)
 
+(defcustom notmuch-unthreaded-result-format
+  `(("date" . "%12s  ")
+    ("authors" . "%-20s")
+    ((("subject" . "%s")) ." %-54s ")
+    ("tags" . "(%s)"))
+  "Result formatting for unthreaded tree view. Supported fields are: date,
+authors, subject, tree, tags.  Tree means the thread tree
+box graphics. The field may also be a list in which case
+the formatting rules are applied recursively and then the
+output of all the fields in the list is inserted
+according to format-string.
+
+Note the author string should not contain
+whitespace (put it in the neighbouring fields instead).
+For example:
+        (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
+                                          \(\"subject\" . \"%s\"\)\)\)"
+  :type '(alist :key-type (string) :value-type (string))
+  :group 'notmuch-tree)
+
+(defun notmuch-tree-result-format ()
+  (if notmuch-tree-unthreaded
+      notmuch-unthreaded-result-format
+    notmuch-tree-result-format))
+
 ;; Faces for messages that match the query.
 (defface notmuch-tree-match-face
   '((t :inherit default))
@@ -125,7 +168,7 @@ Note the author string should not contain
 ;; Faces for messages that do not match the query.
 (defface notmuch-tree-no-match-face
   '((t (:foreground "gray")))
-  "Default face used in tree mode face for non-matching messages"
+  "Default face used in tree mode face for non-matching messages."
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
@@ -160,25 +203,28 @@ Note the author string should not contain
   :group 'notmuch-faces)
 
 (defvar notmuch-tree-previous-subject
-  "The subject of the most recent result shown during the async display")
+  "The subject of the most recent result shown during the async display.")
 (make-variable-buffer-local 'notmuch-tree-previous-subject)
 
 (defvar notmuch-tree-basic-query nil
-  "A buffer local copy of argument query to the function notmuch-tree")
+  "A buffer local copy of argument query to the function notmuch-tree.")
 (make-variable-buffer-local 'notmuch-tree-basic-query)
 
 (defvar notmuch-tree-query-context nil
-  "A buffer local copy of argument query-context to the function notmuch-tree")
+  "A buffer local copy of argument query-context to the function notmuch-tree.")
 (make-variable-buffer-local 'notmuch-tree-query-context)
 
 (defvar notmuch-tree-target-msg nil
-  "A buffer local copy of argument target to the function notmuch-tree")
+  "A buffer local copy of argument target to the function notmuch-tree.")
 (make-variable-buffer-local 'notmuch-tree-target-msg)
 
 (defvar notmuch-tree-open-target nil
-  "A buffer local copy of argument open-target to the function notmuch-tree")
+  "A buffer local copy of argument open-target to the function notmuch-tree.")
 (make-variable-buffer-local 'notmuch-tree-open-target)
 
+(defvar notmuch-tree-parent-buffer nil)
+(make-variable-buffer-local 'notmuch-tree-parent-buffer)
+
 (defvar notmuch-tree-message-window nil
   "The window of the message pane.
 
@@ -203,21 +249,21 @@ This function returns a function (so can be used as a keybinding)
 which executes function FUNC in the message pane if it is
 open (if the message pane is closed it does nothing)."
   `(lambda ()
-      ,(concat "(In message pane) " (documentation func t))
+     ,(concat "(In message pane) " (documentation func t))
      (interactive)
      (when (window-live-p notmuch-tree-message-window)
        (with-selected-window notmuch-tree-message-window
         (call-interactively #',func)))))
 
 (defun notmuch-tree-inherit-from-message-pane (sym)
-  "Return value of SYM in message-pane if open, or tree-pane if not"
+  "Return value of SYM in message-pane if open, or tree-pane if not."
   (if (window-live-p notmuch-tree-message-window)
       (with-selected-window notmuch-tree-message-window
        (symbol-value sym))
     (symbol-value sym)))
 
 (defun notmuch-tree-button-activate (&optional button)
-  "Activate BUTTON or button at point
+  "Activate BUTTON or button at point.
 
 This function does not give an error if there is no button."
   (interactive)
@@ -231,7 +277,7 @@ This function returns a function (so can be used as a keybinding)
 which closes the message pane if open and then executes function
 FUNC."
   `(lambda ()
-      ,(concat "(Close message pane and) " (documentation func t))
+     ,(concat "(Close message pane and) " (documentation func t))
      (interactive)
      (let ((notmuch-show-process-crypto
            (notmuch-tree-inherit-from-message-pane 'notmuch-show-process-crypto)))
@@ -243,17 +289,22 @@ FUNC."
     (set-keymap-parent map notmuch-common-keymap)
     ;; The following override the global keymap.
     ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-help] (notmuch-tree-close-message-pane-and #'notmuch-help))
+    (define-key map [remap notmuch-help]
+      (notmuch-tree-close-message-pane-and #'notmuch-help))
     ;; Override because we first close message pane and then close tree buffer.
     (define-key map [remap notmuch-bury-or-kill-this-buffer] 'notmuch-tree-quit)
     ;; Override because we close message pane after the search query is entered.
     (define-key map [remap notmuch-search] 'notmuch-tree-to-search)
     ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-mua-new-mail] (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
+    (define-key map [remap notmuch-mua-new-mail]
+      (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
     ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-jump-search] (notmuch-tree-close-message-pane-and #'notmuch-jump-search))
+    (define-key map [remap notmuch-jump-search]
+      (notmuch-tree-close-message-pane-and #'notmuch-jump-search))
 
     (define-key map "S" 'notmuch-search-from-tree-current-query)
+    (define-key map "U" 'notmuch-unthreaded-from-tree-current-query)
+    (define-key map "Z" 'notmuch-tree-from-unthreaded-current-query)
 
     ;; these use notmuch-show functions directly
     (define-key map "|" 'notmuch-show-pipe-message)
@@ -263,22 +314,31 @@ FUNC."
     (define-key map "b" 'notmuch-show-resend-message)
 
     ;; these apply to the message pane
-    (define-key map (kbd "M-TAB") (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
-    (define-key map (kbd "<backtab>")  (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
-    (define-key map (kbd "TAB") (notmuch-tree-to-message-pane #'notmuch-show-next-button))
-    (define-key map "$" (notmuch-tree-to-message-pane #'notmuch-show-toggle-process-crypto))
+    (define-key map (kbd "M-TAB")
+      (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
+    (define-key map (kbd "<backtab>")
+      (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
+    (define-key map (kbd "TAB")
+      (notmuch-tree-to-message-pane #'notmuch-show-next-button))
+    (define-key map "$"
+      (notmuch-tree-to-message-pane #'notmuch-show-toggle-process-crypto))
 
     ;; bindings from show (or elsewhere) but we close the message pane first.
-    (define-key map "f" (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message))
-    (define-key map "r" (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender))
-    (define-key map "R" (notmuch-tree-close-message-pane-and #'notmuch-show-reply))
-    (define-key map "V" (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message))
+    (define-key map "f"
+      (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message))
+    (define-key map "r"
+      (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender))
+    (define-key map "R"
+      (notmuch-tree-close-message-pane-and #'notmuch-show-reply))
+    (define-key map "V"
+      (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message))
 
     ;; The main tree view bindings
     (define-key map (kbd "RET") 'notmuch-tree-show-message)
     (define-key map [mouse-1] 'notmuch-tree-show-message)
-    (define-key map "x" 'notmuch-tree-quit)
-    (define-key map "A" 'notmuch-tree-archive-thread)
+    (define-key map "x" 'notmuch-tree-archive-message-then-next-or-exit)
+    (define-key map "X" 'notmuch-tree-archive-thread-then-exit)
+    (define-key map "A" 'notmuch-tree-archive-thread-then-next)
     (define-key map "a" 'notmuch-tree-archive-message-then-next)
     (define-key map "z" 'notmuch-tree-to-tree)
     (define-key map "n" 'notmuch-tree-next-matching-message)
@@ -302,7 +362,7 @@ FUNC."
 
 Some useful entries are:
 :headers - Property list containing the headers :Date, :Subject, :From, etc.
-:tags - Tags for this message"
+:tags - Tags for this message."
   (save-excursion
     (beginning-of-line)
     (get-text-property (point) :notmuch-message-properties)))
@@ -310,7 +370,9 @@ Some useful entries are:
 (defun notmuch-tree-set-message-properties (props)
   (save-excursion
     (beginning-of-line)
-    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
+    (put-text-property (point)
+                      (+ (point) 1)
+                      :notmuch-message-properties props)))
 
 (defun notmuch-tree-set-prop (prop val &optional props)
   (let ((inhibit-read-only t)
@@ -363,7 +425,8 @@ updated."
     ;; from overwriting the buffer local copy of
     ;; notmuch-tree-previous-subject if this is called while the
     ;; buffer is displaying.
-    (let ((notmuch-tree-previous-subject (notmuch-tree-get-prop :previous-subject)))
+    (let ((notmuch-tree-previous-subject
+          (notmuch-tree-get-prop :previous-subject)))
       (delete-region (point) (1+ (line-end-position)))
       (notmuch-tree-insert-msg msg))
     (let ((new-end (line-end-position)))
@@ -388,7 +451,7 @@ NOT change the database."
            (notmuch-show-update-tags new-tags)))))))
 
 (defun notmuch-tree-tag (tag-changes)
-  "Change tags for the current message"
+  "Change tags for the current message."
   (interactive
    (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message")))
   (notmuch-tag (notmuch-tree-get-message-id) tag-changes)
@@ -428,14 +491,32 @@ NOT change the database."
     (notmuch-search query)))
 
 (defun notmuch-tree-to-tree ()
-  "Run a query and display results in Tree view"
+  "Run a query and display results in tree view."
   (interactive)
   (let ((query (notmuch-read-query "Notmuch tree view search: ")))
     (notmuch-tree-close-message-window)
     (notmuch-tree query)))
 
+(defun notmuch-tree-archive-thread-then-next ()
+  "Archive all messages in the current buffer, then show next thread from search."
+  (interactive)
+  (notmuch-tree-archive-thread)
+  (notmuch-tree-next-thread))
+
+(defun notmuch-unthreaded-from-tree-current-query ()
+  "Switch from tree view to unthreaded view."
+  (interactive)
+  (unless notmuch-tree-unthreaded
+    (notmuch-tree-refresh-view 'unthreaded)))
+
+(defun notmuch-tree-from-unthreaded-current-query ()
+  "Switch from unthreaded view to tree view."
+  (interactive)
+  (when notmuch-tree-unthreaded
+    (notmuch-tree-refresh-view 'tree)))
+
 (defun notmuch-search-from-tree-current-query ()
-  "Call notmuch search with the current query"
+  "Call notmuch search with the current query."
   (interactive)
   (notmuch-tree-close-message-window)
   (notmuch-search (notmuch-tree-get-query)))
@@ -471,9 +552,14 @@ NOT change the database."
       (setq notmuch-tree-message-window
            (split-window-vertically (/ (window-height) 4)))
       (with-selected-window notmuch-tree-message-window
-       ;; Since we are only displaying one message do not indent.
-       (let ((notmuch-show-indent-messages-width 0)
-             (notmuch-show-only-matching-messages t))
+       (let (;; Since we are only displaying one message do not indent.
+             (notmuch-show-indent-messages-width 0)
+             (notmuch-show-only-matching-messages t)
+             ;; Ensure that `pop-to-buffer-same-window' uses the
+             ;; window we want it to use.
+             (display-buffer-overriding-action
+                '((display-buffer-same-window)
+                  (inhibit-same-window . nil))))
          (setq buffer (notmuch-show id))))
       ;; We need the `let' as notmuch-tree-message-window is buffer local.
       (let ((window notmuch-tree-message-window))
@@ -501,13 +587,13 @@ NOT change the database."
 Shows in split pane or whole window according to value of
 `notmuch-tree-show-out'. A prefix argument reverses the choice."
   (interactive "P")
-  (if (or (and notmuch-tree-show-out  (not arg))
-         (and (not notmuch-tree-show-out) arg))
+  (if (or (and (notmuch-tree-show-out) (not arg))
+         (and (not (notmuch-tree-show-out)) arg))
       (notmuch-tree-show-message-out)
     (notmuch-tree-show-message-in)))
 
 (defun notmuch-tree-scroll-message-window ()
-  "Scroll the message window (if it exists)"
+  "Scroll the message window (if it exists)."
   (interactive)
   (when (window-live-p notmuch-tree-message-window)
     (with-selected-window notmuch-tree-message-window
@@ -516,7 +602,7 @@ Shows in split pane or whole window according to value of
        (scroll-up)))))
 
 (defun notmuch-tree-scroll-message-window-back ()
-  "Scroll the message window back(if it exists)"
+  "Scroll the message window back (if it exists)."
   (interactive)
   (when (window-live-p notmuch-tree-message-window)
     (with-selected-window notmuch-tree-message-window
@@ -525,22 +611,24 @@ Shows in split pane or whole window according to value of
        (scroll-down)))))
 
 (defun notmuch-tree-scroll-or-next ()
-  "Scroll the message window. If it at end go to next message."
+  "Scroll the message window.
+If it at end go to next message."
   (interactive)
   (when (notmuch-tree-scroll-message-window)
     (notmuch-tree-next-matching-message)))
 
-(defun notmuch-tree-quit ()
+(defun notmuch-tree-quit (&optional kill-both)
   "Close the split view or exit tree."
-  (interactive)
-  (unless (notmuch-tree-close-message-window)
+  (interactive "P")
+  (when (or (not (notmuch-tree-close-message-window)) kill-both)
     (kill-buffer (current-buffer))))
 
 (defun notmuch-tree-close-message-window ()
   "Close the message-window. Return t if close succeeds."
   (interactive)
   (when (and (window-live-p notmuch-tree-message-window)
-            (eq (window-buffer notmuch-tree-message-window) notmuch-tree-message-buffer))
+            (eq (window-buffer notmuch-tree-message-window)
+                notmuch-tree-message-buffer))
     (delete-window notmuch-tree-message-window)
     (unless (get-buffer-window-list notmuch-tree-message-buffer)
       (kill-buffer notmuch-tree-message-buffer))
@@ -555,7 +643,8 @@ message will be \"unarchived\", i.e. the tag changes in
 `notmuch-archive-tags' will be reversed."
   (interactive "P")
   (when notmuch-archive-tags
-    (notmuch-tree-tag (notmuch-tag-change-list notmuch-archive-tags unarchive))))
+    (notmuch-tree-tag
+     (notmuch-tag-change-list notmuch-archive-tags unarchive))))
 
 (defun notmuch-tree-archive-message-then-next (&optional unarchive)
   "Archive the current message and move to next matching message."
@@ -563,6 +652,21 @@ message will be \"unarchived\", i.e. the tag changes in
   (notmuch-tree-archive-message unarchive)
   (notmuch-tree-next-matching-message))
 
+(defun notmuch-tree-archive-thread-then-exit ()
+  "Archive all messages in the current buffer, then exit notmuch-tree."
+  (interactive)
+  (notmuch-tree-archive-thread)
+  (notmuch-tree-quit t))
+
+(defun notmuch-tree-archive-message-then-next-or-exit ()
+  "Archive current message, then show next open message in current thread.
+
+If at the last open message in the current thread, then exit back
+to search results."
+  (interactive)
+  (notmuch-tree-archive-message)
+  (notmuch-tree-next-matching-message t))
+
 (defun notmuch-tree-next-message ()
   "Move to next message."
   (interactive)
@@ -577,63 +681,118 @@ message will be \"unarchived\", i.e. the tag changes in
   (when (window-live-p notmuch-tree-message-window)
     (notmuch-tree-show-message-in)))
 
-(defun notmuch-tree-prev-matching-message ()
+(defun notmuch-tree-goto-matching-message (&optional prev)
+  "Move to the next or previous matching message.
+
+Returns t if there was a next matching message in the thread to show,
+nil otherwise."
+  (let ((dir (if prev -1 nil))
+       (eobfn (if prev #'bobp #'eobp)))
+    (while (and (not (funcall eobfn))
+               (not (notmuch-tree-get-match)))
+      (forward-line dir))
+    (not (funcall eobfn))))
+
+(defun notmuch-tree-matching-message (&optional prev pop-at-end)
+  "Move to the next or previous matching message."
+  (interactive "P")
+  (forward-line (if prev -1 nil))
+  (if (and (not (notmuch-tree-goto-matching-message prev)) pop-at-end)
+      (notmuch-tree-quit pop-at-end)
+    (when (window-live-p notmuch-tree-message-window)
+      (notmuch-tree-show-message-in))))
+
+(defun notmuch-tree-prev-matching-message (&optional pop-at-end)
   "Move to previous matching message."
-  (interactive)
-  (forward-line -1)
-  (while (and (not (bobp)) (not (notmuch-tree-get-match)))
-    (forward-line -1))
-  (when (window-live-p notmuch-tree-message-window)
-    (notmuch-tree-show-message-in)))
+  (interactive "P")
+  (notmuch-tree-matching-message t pop-at-end))
 
-(defun notmuch-tree-next-matching-message ()
+(defun notmuch-tree-next-matching-message (&optional pop-at-end)
   "Move to next matching message."
-  (interactive)
-  (forward-line)
-  (while (and (not (eobp)) (not (notmuch-tree-get-match)))
-    (forward-line))
-  (when (window-live-p notmuch-tree-message-window)
-    (notmuch-tree-show-message-in)))
+  (interactive "P")
+  (notmuch-tree-matching-message nil pop-at-end))
 
-(defun notmuch-tree-refresh-view ()
+(defun notmuch-tree-refresh-view (&optional 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)
+       (unthreaded (cond ((eq view 'unthreaded) t)
+                         ((eq view 'tree) nil)
+                         (t notmuch-tree-unthreaded)))
        (query-context notmuch-tree-query-context)
        (target (notmuch-tree-get-message-id)))
     (erase-buffer)
     (notmuch-tree-worker basic-query
                         query-context
-                        target)))
+                        target
+                        nil
+                        unthreaded)))
 
 (defun notmuch-tree-thread-top ()
   (when (notmuch-tree-get-message-properties)
     (while (not (or (notmuch-tree-get-prop :first) (eobp)))
       (forward-line -1))))
 
-(defun notmuch-tree-prev-thread ()
+(defun notmuch-tree-prev-thread-in-tree ()
+  "Move to the previous thread in the current tree"
   (interactive)
   (forward-line -1)
-  (notmuch-tree-thread-top))
+  (notmuch-tree-thread-top)
+  (not (bobp)))
 
-(defun notmuch-tree-next-thread ()
+(defun notmuch-tree-next-thread-in-tree ()
+  "Get the next thread in the current tree. Returns t if a thread was
+found or nil if not."
   (interactive)
   (forward-line 1)
   (while (not (or (notmuch-tree-get-prop :first) (eobp)))
-    (forward-line 1)))
+    (forward-line 1))
+  (not (eobp)))
+
+(defun notmuch-tree-next-thread-from-search (&optional previous)
+  "Move to the next thread in the parent search results, if any.
+
+If PREVIOUS is non-nil, move to the previous item in the
+search results instead."
+  (interactive "P")
+  (let ((parent-buffer notmuch-tree-parent-buffer))
+    (notmuch-tree-quit t)
+    (when (buffer-live-p parent-buffer)
+      (switch-to-buffer parent-buffer)
+      (if previous
+         (notmuch-search-previous-thread)
+       (notmuch-search-next-thread))
+      (notmuch-tree-from-search-thread))))
+
+(defun notmuch-tree-next-thread (&optional previous)
+  "Move to the next thread in the current tree or parent search
+results
+
+If PREVIOUS is non-nil, move to the previous thread in the tree or
+search results instead."
+  (interactive)
+  (unless (if previous (notmuch-tree-prev-thread-in-tree)
+           (notmuch-tree-next-thread-in-tree))
+    (notmuch-tree-next-thread-from-search previous)))
+
+(defun notmuch-tree-prev-thread ()
+  "Move to the previous thread in the current tree or parent search
+results"
+  (interactive)
+  (notmuch-tree-next-thread t))
 
 (defun notmuch-tree-thread-mapcar (function)
   "Iterate through all messages in the current thread
  and call FUNCTION for side effects."
   (save-excursion
     (notmuch-tree-thread-top)
-    (loop collect (funcall function)
-         do (forward-line)
-         while (and (notmuch-tree-get-message-properties)
-                    (not (notmuch-tree-get-prop :first))))))
+    (cl-loop collect (funcall function)
+            do (forward-line)
+            while (and (notmuch-tree-get-message-properties)
+                       (not (notmuch-tree-get-prop :first))))))
 
 (defun notmuch-tree-get-messages-ids-thread-search ()
   "Return a search string for all message ids of messages in the current thread."
@@ -642,7 +801,7 @@ message will be \"unarchived\", i.e. the tag changes in
             " or "))
 
 (defun notmuch-tree-tag-thread (tag-changes)
-  "Tag all messages in the current thread"
+  "Tag all messages in the current thread."
   (interactive
    (let ((tags (apply #'append (notmuch-tree-thread-mapcar
                                (lambda () (notmuch-tree-get-tags))))))
@@ -683,7 +842,7 @@ unchanged ADDRESS if parsing fails."
     (or p-name p-address)))
 
 (defun notmuch-tree-format-field (field format-string msg)
-  "Format a FIELD of MSG according to FORMAT-STRING and return string"
+  "Format a FIELD of MSG according to FORMAT-STRING and return string."
   (let* ((headers (plist-get msg :headers))
         (match (plist-get msg :match)))
     (cond
@@ -694,7 +853,8 @@ unchanged ADDRESS if parsing fails."
       (let ((face (if match
                      'notmuch-tree-match-date-face
                    'notmuch-tree-no-match-date-face)))
-       (propertize (format format-string (plist-get msg :date_relative)) 'face face)))
+       (propertize (format format-string (plist-get msg :date_relative))
+                   'face face)))
 
      ((string-equal field "tree")
       (let ((tree-status (plist-get msg :tree-status))
@@ -739,7 +899,7 @@ unchanged ADDRESS if parsing fails."
        (format format-string (notmuch-tag-format-tags tags orig-tags face)))))))
 
 (defun notmuch-tree-format-field-list (field-list msg)
-  "Format fields of MSG according to FIELD-LIST and return string"
+  "Format fields of MSG according to FIELD-LIST and return string."
   (let ((face (if (plist-get msg :match)
                  'notmuch-tree-match-face
                'notmuch-tree-no-match-face))
@@ -750,17 +910,17 @@ unchanged ADDRESS if parsing fails."
     (notmuch-apply-face result-string face t)))
 
 (defun notmuch-tree-insert-msg (msg)
-  "Insert the message MSG according to notmuch-tree-result-format"
+  "Insert the message MSG according to notmuch-tree-result-format."
   ;; We need to save the previous subject as it will get overwritten
   ;; by the insert-field calls.
   (let ((previous-subject notmuch-tree-previous-subject))
-    (insert (notmuch-tree-format-field-list notmuch-tree-result-format msg))
+    (insert (notmuch-tree-format-field-list (notmuch-tree-result-format) msg))
     (notmuch-tree-set-message-properties msg)
     (notmuch-tree-set-prop :previous-subject previous-subject)
     (insert "\n")))
 
 (defun notmuch-tree-goto-and-insert-msg (msg)
-  "Insert msg at the end of the buffer. Move point to msg if it is the target"
+  "Insert msg at the end of the buffer. Move point to msg if it is the target."
   (save-excursion
     (goto-char (point-max))
     (notmuch-tree-insert-msg msg))
@@ -781,43 +941,41 @@ A message tree is another name for a single sub-thread: i.e., a
 message together with all its descendents."
   (let ((msg (car tree))
        (replies (cadr tree)))
-
-      (cond
-       ((and (< 0 depth) (not last))
-       (push "├" tree-status))
-       ((and (< 0 depth) last)
-       (push "╰" tree-status))
-       ((and (eq 0 depth) first last)
-;;       (push "─" tree-status)) choice between this and next line is matter of taste.
-       (push " " tree-status))
-       ((and (eq 0 depth) first (not last))
-         (push "┬" tree-status))
-       ((and (eq 0 depth) (not first) last)
-       (push "╰" tree-status))
-       ((and (eq 0 depth) (not first) (not last))
-       (push "├" tree-status)))
-
-      (push (concat (if replies "┬" "─") "►") tree-status)
-      (setq msg (plist-put msg :first (and first (eq 0 depth))))
-      (setq msg (plist-put msg :tree-status tree-status))
-      (setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
-      (notmuch-tree-goto-and-insert-msg msg)
-      (pop tree-status)
-      (pop tree-status)
-
-      (if last
-         (push " " tree-status)
-       (push "│" tree-status))
-
+    (cond
+     ((and (< 0 depth) (not last))
+      (push "├" tree-status))
+     ((and (< 0 depth) last)
+      (push "╰" tree-status))
+     ((and (eq 0 depth) first last)
+      ;; Choice between these two variants is a matter of taste.
+      ;; (push "─" tree-status))
+      (push " " tree-status))
+     ((and (eq 0 depth) first (not last))
+      (push "┬" tree-status))
+     ((and (eq 0 depth) (not first) last)
+      (push "╰" tree-status))
+     ((and (eq 0 depth) (not first) (not last))
+      (push "├" tree-status)))
+    (push (concat (if replies "┬" "─") "►") tree-status)
+    (setq msg (plist-put msg :first (and first (eq 0 depth))))
+    (setq msg (plist-put msg :tree-status tree-status))
+    (setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
+    (notmuch-tree-goto-and-insert-msg msg)
+    (pop tree-status)
+    (pop tree-status)
+    (if last
+       (push " " tree-status)
+      (push "│" tree-status))
     (notmuch-tree-insert-thread replies (1+ depth) tree-status)))
 
 (defun notmuch-tree-insert-thread (thread depth tree-status)
   "Insert the collection of sibling sub-threads THREAD at depth DEPTH in the current forest."
   (let ((n (length thread)))
-    (loop for tree in thread
-         for count from 1 to n
-
-         do (notmuch-tree-insert-tree tree depth tree-status (eq count 1) (eq count n)))))
+    (cl-loop for tree in thread
+            for count from 1 to n
+            do (notmuch-tree-insert-tree tree depth tree-status
+                                         (eq count 1)
+                                         (eq count n)))))
 
 (defun notmuch-tree-insert-forest-thread (forest-thread)
   "Insert a single complete thread."
@@ -846,51 +1004,50 @@ Pressing \\[notmuch-tree-show-message] on any line displays that message.
 Complete list of currently available key bindings:
 
 \\{notmuch-tree-mode-map}"
-
   (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view)
   (hl-line-mode 1)
-  (setq buffer-read-only t
-       truncate-lines t))
+  (setq buffer-read-only t)
+  (setq truncate-lines t))
 
 (defun notmuch-tree-process-sentinel (proc msg)
-  "Add a message to let user know when \"notmuch tree\" exits"
+  "Add a message to let user know when \"notmuch tree\" exits."
   (let ((buffer (process-buffer proc))
        (status (process-status proc))
        (exit-status (process-exit-status proc))
        (never-found-target-thread nil))
     (when (memq status '(exit signal))
-        (kill-buffer (process-get proc 'parse-buf))
-       (if (buffer-live-p buffer)
-           (with-current-buffer buffer
-             (save-excursion
-               (let ((inhibit-read-only t)
-                     (atbob (bobp)))
-                 (goto-char (point-max))
-                 (if (eq status 'signal)
-                     (insert "Incomplete search results (tree view process was killed).\n"))
-                 (when (eq status 'exit)
-                   (insert "End of search results.")
-                   (unless (= exit-status 0)
-                     (insert (format " (process returned %d)" exit-status)))
-                   (insert "\n")))))))))
+      (kill-buffer (process-get proc 'parse-buf))
+      (when (buffer-live-p buffer)
+       (with-current-buffer buffer
+         (save-excursion
+           (let ((inhibit-read-only t)
+                 (atbob (bobp)))
+             (goto-char (point-max))
+             (when (eq status 'signal)
+               (insert "Incomplete search results (tree view process was killed).\n"))
+             (when (eq status 'exit)
+               (insert "End of search results.")
+               (unless (= exit-status 0)
+                 (insert (format " (process returned %d)" exit-status)))
+               (insert "\n")))))))))
 
 (defun notmuch-tree-process-filter (proc string)
-  "Process and filter the output of \"notmuch show\" for tree view"
+  "Process and filter the output of \"notmuch show\" for tree view."
   (let ((results-buf (process-buffer proc))
-        (parse-buf (process-get proc 'parse-buf))
-        (inhibit-read-only t)
-        done)
+       (parse-buf (process-get proc 'parse-buf))
+       (inhibit-read-only t)
+       done)
     (if (not (buffer-live-p results-buf))
-        (delete-process proc)
+       (delete-process proc)
       (with-current-buffer parse-buf
-        ;; Insert new data
-        (save-excursion
-          (goto-char (point-max))
-          (insert string))
+       ;; Insert new data
+       (save-excursion
+         (goto-char (point-max))
+         (insert string))
        (notmuch-sexp-parse-partial-list 'notmuch-tree-insert-forest-thread
                                         results-buf)))))
 
-(defun notmuch-tree-worker (basic-query &optional query-context target open-target)
+(defun notmuch-tree-worker (basic-query &optional query-context target open-target unthreaded)
   "Insert the tree view of the search in the current buffer.
 
 This is is a helper function for notmuch-tree. The arguments are
@@ -898,10 +1055,12 @@ the same as for the function notmuch-tree."
   (interactive)
   (notmuch-tree-mode)
   (add-hook 'post-command-hook #'notmuch-tree-command-hook t t)
+  (setq notmuch-tree-unthreaded unthreaded)
   (setq notmuch-tree-basic-query basic-query)
   (setq notmuch-tree-query-context (if (or (string= query-context "")
                                           (string= query-context "*"))
-                                      nil 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
@@ -909,15 +1068,14 @@ the same as for the function notmuch-tree."
   ;; (such as reply) do. It is a buffer local variable so setting it
   ;; will not affect genuine show buffers.
   (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
-
   (erase-buffer)
   (goto-char (point-min))
   (let* ((search-args (concat basic-query
-                      (if query-context (concat " and (" query-context ")"))
-                      ))
-        (message-arg "--entire-thread"))
-    (if (equal (car (process-lines notmuch-command "count" search-args)) "0")
-       (setq search-args basic-query))
+                             (and query-context
+                                  (concat " and (" query-context ")"))))
+        (message-arg (if unthreaded "--unthreaded" "--entire-thread")))
+    (when (equal (car (process-lines notmuch-command "count" search-args)) "0")
+      (setq search-args basic-query))
     (notmuch-tag-clear-cache)
     (let ((proc (notmuch-start-notmuch
                 "notmuch-tree" (current-buffer) #'notmuch-tree-process-sentinel
@@ -932,7 +1090,7 @@ the same as for the function notmuch-tree."
       (set-process-query-on-exit-flag proc nil))))
 
 (defun notmuch-tree-get-query ()
-  "Return the current query in this tree buffer"
+  "Return the current query in this tree buffer."
   (if notmuch-tree-query-context
       (concat notmuch-tree-basic-query
              " and ("
@@ -940,8 +1098,8 @@ the same as for the function notmuch-tree."
              ")")
     notmuch-tree-basic-query))
 
-(defun notmuch-tree (&optional query query-context target buffer-name open-target)
-  "Display threads matching QUERY in Tree View.
+(defun notmuch-tree (&optional query query-context target buffer-name open-target unthreaded parent-buffer)
+  "Display threads matching QUERY in tree view.
 
 The arguments are:
   QUERY: the main query. This can be any query but in many cases will be
@@ -953,23 +1111,29 @@ The arguments are:
       current if it appears in the tree view results.
   BUFFER-NAME: the name of the buffer to display the tree view. If
       it is nil \"*notmuch-tree\" followed by QUERY is used.
-  OPEN-TARGET: If TRUE open the target message in the message pane."
+  OPEN-TARGET: If TRUE open the target message in the message pane.
+  UNTHREADED: If TRUE only show matching messages in an unthreaded view."
   (interactive)
-  (if (null query)
-      (setq query (notmuch-read-query "Notmuch tree view search: ")))
+  (unless query
+    (setq query (notmuch-read-query (concat "Notmuch "
+                                           (if unthreaded "unthreaded " "tree ")
+                                           "view search: "))))
   (let ((buffer (get-buffer-create (generate-new-buffer-name
                                    (or buffer-name
-                                       (concat "*notmuch-tree-" query "*")))))
+                                       (concat "*notmuch-"
+                                               (if unthreaded "unthreaded-" "tree-")
+                                               query "*")))))
        (inhibit-read-only t))
-
-    (switch-to-buffer buffer))
+    (pop-to-buffer-same-window buffer))
   ;; Don't track undo information for this buffer
   (set 'buffer-undo-list t)
-
-  (notmuch-tree-worker query query-context target open-target)
-
+  (notmuch-tree-worker query query-context target open-target unthreaded)
+  (setq notmuch-tree-parent-buffer parent-buffer)
   (setq truncate-lines t))
 
+(defun notmuch-unthreaded (&optional query query-context target buffer-name open-target)
+  (interactive)
+  (notmuch-tree query query-context target buffer-name open-target t))
 
 ;;
 
index abf52f1772696461ff6fb559e37a5413f0c06e21..9730829525321c489388513d34636e94a7559d57 100644 (file)
@@ -1,5 +1,4 @@
-;;; notmuch-version.el --- Version of notmuch
-;; -*- emacs-lisp -*-
+;;; notmuch-version.el --- version of notmuch  -*- emacs-lisp -*-
 ;;
 ;; %AG%
 ;;
index 54108d93607bf83b228f5f2b17ada5f215e62192..ce4b963727696986405ad59d387994ba1f7c1a94 100644 (file)
@@ -25,7 +25,9 @@
 
 (require 'coolj)
 (require 'notmuch-lib)
-(declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth &optional hide))
+
+(declare-function notmuch-show-insert-bodypart "notmuch-show"
+                 (msg part depth &optional hide))
 (defvar notmuch-show-indent-messages-width)
 
 ;;
@@ -186,24 +188,25 @@ message parts."
   (let* ((type (overlay-get overlay 'type))
         (invis-spec (overlay-get overlay 'invisible))
         (state (if (invisible-p invis-spec) "hidden" "visible"))
-        (label-format (symbol-value (intern-soft (concat "notmuch-wash-button-"
-                                                         type "-" state "-format"))))
-        (lines-count (count-lines (overlay-start overlay) (overlay-end overlay))))
+        (label-format (symbol-value
+                       (intern-soft
+                        (format "notmuch-wash-button-%s-%s-format"
+                                type state))))
+        (lines-count (count-lines (overlay-start overlay)
+                                  (overlay-end overlay))))
     (format label-format lines-count)))
 
 (defun notmuch-wash-region-to-button (msg beg end type &optional prefix)
-  "Auxiliary function to do the actual making of overlays and buttons
+  "Auxiliary function to do the actual making of overlays and buttons.
 
 BEG and END are buffer locations. TYPE should a string, either
 \"citation\" or \"signature\". Optional PREFIX is some arbitrary
 text to insert before the button, probably for indentation.  Note
 that PREFIX should not include a newline."
-
   ;; This uses some slightly tricky conversions between strings and
   ;; symbols because of the way the button code works. Note that
   ;; replacing intern-soft with make-symbol will cause this to fail,
   ;; since the newly created symbol has no plist.
-
   (let ((overlay (make-overlay beg end))
        (button-type (intern-soft (concat "notmuch-wash-button-"
                                          type "-toggle-type"))))
@@ -213,8 +216,8 @@ that PREFIX should not include a newline."
     (goto-char (1+ end))
     (save-excursion
       (goto-char beg)
-      (if prefix
-         (insert-before-markers prefix))
+      (when prefix
+       (insert-before-markers prefix))
       (let ((button-beg (point)))
        (insert-before-markers (notmuch-wash-button-label overlay) "\n")
        (let ((button (make-button button-beg (1- (point))
@@ -226,19 +229,20 @@ that PREFIX should not include a newline."
   "Excerpt citations and up to one signature."
   (goto-char (point-min))
   (beginning-of-line)
-  (if (and (< (point) (point-max))
-          (re-search-forward notmuch-wash-original-regexp nil t))
-      (let* ((msg-start (match-beginning 0))
-            (msg-end (point-max))
-            (msg-lines (count-lines msg-start msg-end)))
-       (notmuch-wash-region-to-button
-        msg msg-start msg-end "original")))
+  (when (and (< (point) (point-max))
+            (re-search-forward notmuch-wash-original-regexp nil t))
+    (let* ((msg-start (match-beginning 0))
+          (msg-end (point-max))
+          (msg-lines (count-lines msg-start msg-end)))
+      (notmuch-wash-region-to-button
+       msg msg-start msg-end "original")))
   (while (and (< (point) (point-max))
              (re-search-forward notmuch-wash-citation-regexp nil t))
     (let* ((cite-start (match-beginning 0))
           (cite-end (match-end 0))
           (cite-lines (count-lines cite-start cite-end)))
-      (overlay-put (make-overlay cite-start cite-end) 'face 'notmuch-wash-cited-text)
+      (overlay-put (make-overlay cite-start cite-end)
+                  'face 'notmuch-wash-cited-text)
       (when (> cite-lines (+ notmuch-wash-citation-lines-prefix
                             notmuch-wash-citation-lines-suffix
                             1))
@@ -250,48 +254,44 @@ that PREFIX should not include a newline."
          (notmuch-wash-region-to-button
           msg hidden-start (point-marker)
           "citation")))))
-  (if (and (not (eobp))
-          (re-search-forward notmuch-wash-signature-regexp nil t))
-      (let* ((sig-start (match-beginning 0))
-            (sig-end (match-end 0))
-            (sig-lines (count-lines sig-start (point-max))))
-       (if (<= sig-lines notmuch-wash-signature-lines-max)
-           (let ((sig-start-marker (make-marker))
-                 (sig-end-marker (make-marker)))
-             (set-marker sig-start-marker sig-start)
-             (set-marker sig-end-marker (point-max))
-             (overlay-put (make-overlay sig-start-marker sig-end-marker) 'face 'message-cited-text)
-             (notmuch-wash-region-to-button
-              msg sig-start-marker sig-end-marker
-              "signature"))))))
+  (when (and (not (eobp))
+            (re-search-forward notmuch-wash-signature-regexp nil t))
+    (let* ((sig-start (match-beginning 0))
+          (sig-end (match-end 0))
+          (sig-lines (count-lines sig-start (point-max))))
+      (when (<= sig-lines notmuch-wash-signature-lines-max)
+       (let ((sig-start-marker (make-marker))
+             (sig-end-marker (make-marker)))
+         (set-marker sig-start-marker sig-start)
+         (set-marker sig-end-marker (point-max))
+         (overlay-put (make-overlay sig-start-marker sig-end-marker)
+                      'face 'message-cited-text)
+         (notmuch-wash-region-to-button
+          msg sig-start-marker sig-end-marker
+          "signature"))))))
 
 ;;
 
 (defun notmuch-wash-elide-blank-lines (msg depth)
   "Elide leading, trailing and successive blank lines."
-
   ;; Algorithm derived from `article-strip-multiple-blank-lines' in
   ;; `gnus-art.el'.
-
   ;; Make all blank lines empty.
   (goto-char (point-min))
   (while (re-search-forward "^[[:space:]\t]+$" nil t)
     (replace-match "" nil t))
-
   ;; Replace multiple empty lines with a single empty line.
   (goto-char (point-min))
   (while (re-search-forward "^\n\\(\n+\\)" nil t)
     (delete-region (match-beginning 1) (match-end 1)))
-
   ;; Remove a leading blank line.
   (goto-char (point-min))
-  (if (looking-at "\n")
-      (delete-region (match-beginning 0) (match-end 0)))
-
+  (when (looking-at "\n")
+    (delete-region (match-beginning 0) (match-end 0)))
   ;; Remove a trailing blank line.
   (goto-char (point-max))
-  (if (looking-at "\n")
-      (delete-region (match-beginning 0) (match-end 0))))
+  (when (looking-at "\n")
+    (delete-region (match-beginning 0) (match-end 0))))
 
 ;;
 
@@ -306,20 +306,15 @@ Perform several transformations on the message body:
   text,
 - Remove citation trailers standing alone after a block of cited
   text."
-
   ;; Remove lines of repeated citation leaders with no other content.
   (goto-char (point-min))
   (while (re-search-forward "\\(^>[> ]*\n\\)\\{2,\\}" nil t)
     (replace-match "\\1"))
-
-  ;; Remove citation leaders standing alone before a block of cited
-  ;; text.
+  ;; Remove citation leaders standing alone before a block of cited text.
   (goto-char (point-min))
   (while (re-search-forward "\\(\n\\|^[^>].*\\)\n\\(^>[> ]*\n\\)" nil t)
     (replace-match "\\1\n"))
-
-  ;; Remove citation trailers standing alone after a block of cited
-  ;; text.
+  ;; Remove citation trailers standing alone after a block of cited text.
   (goto-char (point-min))
   (while (re-search-forward "\\(^>[> ]*\n\\)\\(^$\\|^[^>].*\\)" nil t)
     (replace-match "\\2")))
@@ -334,7 +329,6 @@ the message lines to the minimum of the width of the window or
 its value. Otherwise, this function will wrap long lines in the
 message at the window width. When doing so, citation leaders in
 the wrapped text are maintained."
-
   (let* ((coolj-wrap-follows-window-size nil)
         (indent (* depth notmuch-show-indent-messages-width))
         (limit (if (numberp notmuch-wash-wrap-lines-length)
@@ -376,10 +370,10 @@ filename, before trimming any trailing . and - characters."
 
 Return the patch sequence number N from the last \"[PATCH N/M]\"
 style prefix in SUBJECT, or nil if such a prefix can't be found."
-  (when (string-match
-        "^ *\\(\\[[^]]*\\] *\\)*\\[[^]]*?\\([0-9]+\\)/[0-9]+[^]]*\\].*"
-        subject)
-      (string-to-number (substring subject (match-beginning 2) (match-end 2)))))
+  (and (string-match
+       "^ *\\(\\[[^]]*\\] *\\)*\\[[^]]*?\\([0-9]+\\)/[0-9]+[^]]*\\].*"
+       subject)
+       (string-to-number (substring subject (match-beginning 2) (match-end 2)))))
 
 (defun notmuch-wash-subject-to-patch-filename (subject)
   "Convert a patch mail SUBJECT into a filename.
@@ -398,7 +392,6 @@ original filename the sender had."
 Given that this function guesses whether a buffer includes a
 patch and then guesses the extent of the patch, there is scope
 for error."
-
   (goto-char (point-min))
   (when (re-search-forward diff-file-header-re nil t)
     (beginning-of-line -1)
@@ -406,12 +399,12 @@ for error."
          (patch-end (point-max))
          part)
       (goto-char patch-start)
-      (if (or
-          ;; Patch ends with signature.
-          (re-search-forward notmuch-wash-signature-regexp nil t)
-          ;; Patch ends with bugtraq comment.
-          (re-search-forward "^\\*\\*\\* " nil t))
-         (setq patch-end (match-beginning 0)))
+      (when (or
+            ;; Patch ends with signature.
+            (re-search-forward notmuch-wash-signature-regexp nil t)
+            ;; Patch ends with bugtraq comment.
+            (re-search-forward "^\\*\\*\\* " nil t))
+       (setq patch-end (match-beginning 0)))
       (save-restriction
        (narrow-to-region patch-start patch-end)
        (setq part (plist-put part :content-type "inline patch"))
index 773d12065db0f5c3697e9d54b485676bfc292429..83bcee57c675bce1959275315a782c4319c9622a 100644 (file)
@@ -18,7 +18,7 @@
 ;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
-;; Homepage: https://notmuchmail.org/
+;; Homepage: https://notmuchmail.org
 
 ;;; Commentary:
 
 ;;
 ;; TL;DR: notmuch-emacs from MELPA and notmuch from distro packages is
 ;; NOT SUPPORTED.
-;;
+
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl-lib))
+
 (require 'mm-view)
 (require 'message)
 
@@ -112,27 +113,27 @@ there will be called at other points of notmuch execution."
   :group 'notmuch)
 
 (defvar notmuch-query-history nil
-  "Variable to store minibuffer history for notmuch queries")
+  "Variable to store minibuffer history for notmuch queries.")
 
 (defun notmuch-foreach-mime-part (function mm-handle)
   (cond ((stringp (car mm-handle))
-         (dolist (part (cdr mm-handle))
-           (notmuch-foreach-mime-part function part)))
-        ((bufferp (car mm-handle))
-         (funcall function mm-handle))
-        (t (dolist (part mm-handle)
-             (notmuch-foreach-mime-part function part)))))
+        (dolist (part (cdr mm-handle))
+          (notmuch-foreach-mime-part function part)))
+       ((bufferp (car mm-handle))
+        (funcall function mm-handle))
+       (t (dolist (part mm-handle)
+            (notmuch-foreach-mime-part function part)))))
 
 (defun notmuch-count-attachments (mm-handle)
   (let ((count 0))
     (notmuch-foreach-mime-part
      (lambda (p)
        (let ((disposition (mm-handle-disposition p)))
-         (and (listp disposition)
-              (or (equal (car disposition) "attachment")
-                  (and (equal (car disposition) "inline")
-                       (assq 'filename disposition)))
-              (incf count))))
+        (and (listp disposition)
+             (or (equal (car disposition) "attachment")
+                 (and (equal (car disposition) "inline")
+                      (assq 'filename disposition)))
+             (cl-incf count))))
      mm-handle)
     count))
 
@@ -141,13 +142,13 @@ there will be called at other points of notmuch execution."
    (lambda (p)
      (let ((disposition (mm-handle-disposition p)))
        (and (listp disposition)
-            (or (equal (car disposition) "attachment")
-                (and (equal (car disposition) "inline")
-                     (assq 'filename disposition)))
-            (or (not queryp)
-                (y-or-n-p
-                 (concat "Save '" (cdr (assq 'filename disposition)) "' ")))
-            (mm-save-part p))))
+           (or (equal (car disposition) "attachment")
+               (and (equal (car disposition) "inline")
+                    (assq 'filename disposition)))
+           (or (not queryp)
+               (y-or-n-p
+                (concat "Save '" (cdr (assq 'filename disposition)) "' ")))
+           (mm-save-part p))))
    mm-handle))
 
 (require 'hl-line)
@@ -188,7 +189,9 @@ there will be called at other points of notmuch execution."
     (define-key map "-" 'notmuch-search-remove-tag)
     (define-key map "+" 'notmuch-search-add-tag)
     (define-key map (kbd "RET") 'notmuch-search-show-thread)
+    (define-key map (kbd "M-RET") 'notmuch-tree-from-search-thread)
     (define-key map "Z" 'notmuch-tree-from-search-current-query)
+    (define-key map "U" 'notmuch-unthreaded-from-search-current-query)
     map)
   "Keymap for \"notmuch search\" buffers.")
 (fset 'notmuch-search-mode-map notmuch-search-mode-map)
@@ -199,7 +202,7 @@ there will be called at other points of notmuch execution."
     (define-key map "q" 'notmuch-stash-query)
     (define-key map "?" 'notmuch-subkeymap-help)
     map)
-  "Submap for stash commands")
+  "Submap for stash commands.")
 (fset 'notmuch-search-stash-map notmuch-search-stash-map)
 
 (defun notmuch-search-stash-thread-id ()
@@ -261,7 +264,8 @@ there will be called at other points of notmuch execution."
   (goto-char (point-max))
   (forward-line -2)
   (let ((beg (notmuch-search-result-beginning)))
-    (when beg (goto-char beg))))
+    (when beg
+      (goto-char beg))))
 
 (defun notmuch-search-first-thread ()
   "Select the first thread in the search results."
@@ -269,11 +273,15 @@ there will be called at other points of notmuch execution."
   (goto-char (point-min)))
 
 (defface notmuch-message-summary-face
- '((((class color) (background light)) (:background "#f0f0f0"))
-   (((class color) (background dark)) (:background "#303030")))
- "Face for the single-line message summary in notmuch-show-mode."
- :group 'notmuch-show
- :group 'notmuch-faces)
+  `((((class color) (background light))
+     ,@(and (>= emacs-major-version 27) '(:extend t))
+     :background "#f0f0f0")
+    (((class color) (background dark))
+     ,@(and (>= emacs-major-version 27) '(:extend t))
+     :background "#303030"))
+  "Face for the single-line message summary in notmuch-show-mode."
+  :group 'notmuch-show
+  :group 'notmuch-faces)
 
 (defface notmuch-search-date
   '((t :inherit default))
@@ -389,9 +397,9 @@ Complete list of currently available key bindings:
   (setq truncate-lines t)
   (setq buffer-read-only t)
   (setq imenu-prev-index-position-function
-        #'notmuch-search-imenu-prev-index-position-function)
+       #'notmuch-search-imenu-prev-index-position-function)
   (setq imenu-extract-index-name-function
-        #'notmuch-search-imenu-extract-index-name-function))
+       #'notmuch-search-imenu-extract-index-name-function))
 
 (defun notmuch-search-get-result (&optional pos)
   "Return the result object for the thread at POS (or point).
@@ -403,46 +411,46 @@ If there is no thread at POS (or point), returns nil."
   "Return the point at the beginning of the thread at POS (or point).
 
 If there is no thread at POS (or point), returns nil."
-  (when (notmuch-search-get-result pos)
-    ;; We pass 1+point because previous-single-property-change starts
-    ;; searching one before the position we give it.
-    (previous-single-property-change (1+ (or pos (point)))
-                                    'notmuch-search-result nil (point-min))))
+  (and (notmuch-search-get-result pos)
+       ;; We pass 1+point because previous-single-property-change starts
+       ;; searching one before the position we give it.
+       (previous-single-property-change (1+ (or pos (point)))
+                                       'notmuch-search-result nil
+                                       (point-min))))
 
 (defun notmuch-search-result-end (&optional pos)
   "Return the point at the end of the thread at POS (or point).
 
 The returned point will be just after the newline character that
 ends the result line.  If there is no thread at POS (or point),
-returns nil"
-  (when (notmuch-search-get-result pos)
-    (next-single-property-change (or pos (point)) 'notmuch-search-result
-                                nil (point-max))))
+returns nil."
+  (and (notmuch-search-get-result pos)
+       (next-single-property-change (or pos (point))
+                                   'notmuch-search-result nil
+                                   (point-max))))
 
 (defun notmuch-search-foreach-result (beg end fn)
   "Invoke FN for each result between BEG and END.
 
-FN should take one argument.  It will be applied to the
-character position of the beginning of each result that overlaps
-the region between points BEG and END.  As a special case, if (=
-BEG END), FN will be applied to the result containing point
-BEG."
-
-  (lexical-let ((pos (notmuch-search-result-beginning beg))
-               ;; End must be a marker in case fn changes the
-               ;; text.
-               (end (copy-marker end))
-               ;; Make sure we examine at least one result, even if
-               ;; (= beg end).
-               (first t))
+FN should take one argument.  It will be applied to the character
+position of the beginning of each result that overlaps the region
+between points BEG and END.  As a special case, if (= BEG END),
+FN will be applied to the result containing point BEG."
+  (let ((pos (notmuch-search-result-beginning beg))
+       ;; End must be a marker in case fn changes the
+       ;; text.
+       (end (copy-marker end))
+       ;; Make sure we examine at least one result, even if
+       ;; (= beg end).
+       (first t))
     ;; We have to be careful if the region extends beyond the results.
     ;; In this case, pos could be null or there could be no result at
     ;; pos.
     (while (and pos (or (< pos end) first))
       (when (notmuch-search-get-result pos)
        (funcall fn pos))
-      (setq pos (notmuch-search-result-end pos)
-           first nil))))
+      (setq pos (notmuch-search-result-end pos))
+      (setq first nil))))
 ;; Unindent the function argument of notmuch-search-foreach-result so
 ;; the indentation of callers doesn't get out of hand.
 (put 'notmuch-search-foreach-result 'lisp-indent-function 2)
@@ -455,11 +463,12 @@ BEG."
     output))
 
 (defun notmuch-search-find-thread-id (&optional bare)
-  "Return the thread for the current thread
+  "Return the thread for the current thread.
 
-If BARE is set then do not prefix with \"thread:\""
+If BARE is set then do not prefix with \"thread:\"."
   (let ((thread (plist-get (notmuch-search-get-result) :thread)))
-    (when thread (concat (unless bare "thread:") thread))))
+    (when thread
+      (concat (and (not bare) "thread:") thread))))
 
 (defun notmuch-search-find-stable-query ()
   "Return the stable queries for the current thread.
@@ -476,27 +485,27 @@ is nil, include both matched and unmatched messages. If there are
 no messages in the region then return nil."
   (let ((query-list nil) (all (not only-matched)))
     (dolist (queries (notmuch-search-properties-in-region :query beg end))
-      (when (first queries)
-       (push (first queries) query-list))
-      (when (and all (second queries))
-       (push (second queries) query-list)))
-    (when query-list
-      (concat "(" (mapconcat 'identity query-list ") or (") ")"))))
+      (when (car queries)
+       (push (car queries) query-list))
+      (when (and all (cadr queries))
+       (push (cadr queries) query-list)))
+    (and query-list
+        (concat "(" (mapconcat 'identity query-list ") or (") ")"))))
 
 (defun notmuch-search-find-authors ()
-  "Return the authors for the current thread"
+  "Return the authors for the current thread."
   (plist-get (notmuch-search-get-result) :authors))
 
 (defun notmuch-search-find-authors-region (beg end)
-  "Return a list of authors for the current region"
+  "Return a list of authors for the current region."
   (notmuch-search-properties-in-region :authors beg end))
 
 (defun notmuch-search-find-subject ()
-  "Return the subject for the current thread"
+  "Return the subject for the current thread."
   (plist-get (notmuch-search-get-result) :subject))
 
 (defun notmuch-search-find-subject-region (beg end)
-  "Return a list of authors for the current region"
+  "Return a list of authors for the current region."
   (notmuch-search-properties-in-region :subject beg end))
 
 (defun notmuch-search-show-thread (&optional elide-toggle)
@@ -514,22 +523,29 @@ thread."
                      (current-buffer)
                      notmuch-search-query-string
                      ;; Name the buffer based on the subject.
-                     (concat "*" (truncate-string-to-width subject 30 nil nil t) "*"))
+                     (concat "*"
+                             (truncate-string-to-width subject 30 nil nil t)
+                             "*"))
       (message "End of search results."))))
 
 (defun notmuch-tree-from-search-current-query ()
-  "Call notmuch tree with the current query"
+  "Call notmuch tree with the current query."
   (interactive)
   (notmuch-tree notmuch-search-query-string))
 
+(defun notmuch-unthreaded-from-search-current-query ()
+  "Call notmuch tree with the current query."
+  (interactive)
+  (notmuch-unthreaded notmuch-search-query-string))
+
 (defun notmuch-tree-from-search-thread ()
-  "Show the selected thread with notmuch-tree"
+  "Show the selected thread with notmuch-tree."
   (interactive)
   (notmuch-tree (notmuch-search-find-thread-id)
-                notmuch-search-query-string
+               notmuch-search-query-string
                nil
-                (notmuch-prettify-subject (notmuch-search-find-subject))
-               t))
+               (notmuch-prettify-subject (notmuch-search-find-subject))
+               t nil (current-buffer)))
 
 (defun notmuch-search-reply-to-thread (&optional prompt-for-sender)
   "Begin composing a reply-all to the entire current thread in a new buffer."
@@ -561,12 +577,11 @@ thread."
   "Prompt for tag changes for the current thread or region.
 
 Returns (TAG-CHANGES REGION-BEGIN REGION-END)."
-  (let* ((region (notmuch-interactive-region))
-        (beg (first region)) (end (second region))
-        (prompt (if (= beg end) "Tag thread" "Tag region")))
-    (cons (notmuch-read-tag-changes
-          (notmuch-search-get-tags-region beg end) prompt initial-input)
-         region)))
+  (pcase-let ((`(,beg ,end) (notmuch-interactive-region)))
+    (list (notmuch-read-tag-changes (notmuch-search-get-tags-region beg end)
+                                   (if (= beg end) "Tag thread" "Tag region")
+                                   initial-input)
+         beg end)))
 
 (defun notmuch-search-tag (tag-changes &optional beg end only-matched)
   "Change tags for the currently selected thread or region.
@@ -581,8 +596,8 @@ is inactive this applies to the thread at point.
 If ONLY-MATCHED is non-nil, only tag matched messages."
   (interactive (notmuch-search-interactive-tag-changes))
   (unless (and beg end)
-    (setq beg (car (notmuch-interactive-region))
-         end (cadr (notmuch-interactive-region))))
+    (setq beg (car (notmuch-interactive-region)))
+    (setq end (cadr (notmuch-interactive-region))))
   (let ((search-string (notmuch-search-find-stable-query-region
                        beg end only-matched)))
     (notmuch-tag search-string tag-changes)
@@ -654,7 +669,7 @@ of the result."
        (goto-char new-point)))))
 
 (defun notmuch-search-process-sentinel (proc msg)
-  "Add a message to let user know when \"notmuch search\" exits"
+  "Add a message to let user know when \"notmuch search\" exits."
   (let ((buffer (process-buffer proc))
        (status (process-status proc))
        (exit-status (process-exit-status proc))
@@ -662,28 +677,28 @@ of the result."
     (when (memq status '(exit signal))
       (catch 'return
        (kill-buffer (process-get proc 'parse-buf))
-       (if (buffer-live-p buffer)
-           (with-current-buffer buffer
-             (save-excursion
-               (let ((inhibit-read-only t)
-                     (atbob (bobp)))
-                 (goto-char (point-max))
-                 (if (eq status 'signal)
-                     (insert "Incomplete search results (search process was killed).\n"))
-                 (when (eq status 'exit)
-                   (insert "End of search results.\n")
-                   ;; For version mismatch, there's no point in
-                   ;; showing the search buffer
-                   (when (or (= exit-status 20) (= exit-status 21))
-                     (kill-buffer)
-                     (throw 'return nil))
-                   (if (and atbob
+       (when (buffer-live-p buffer)
+         (with-current-buffer buffer
+           (save-excursion
+             (let ((inhibit-read-only t)
+                   (atbob (bobp)))
+               (goto-char (point-max))
+               (when (eq status 'signal)
+                 (insert "Incomplete search results (search process was killed).\n"))
+               (when (eq status 'exit)
+                 (insert "End of search results.\n")
+                 ;; For version mismatch, there's no point in
+                 ;; showing the search buffer
+                 (when (or (= exit-status 20) (= exit-status 21))
+                   (kill-buffer)
+                   (throw 'return nil))
+                 (when (and atbob
                             (not (string= notmuch-search-target-thread "found")))
-                       (set 'never-found-target-thread t)))))
-             (when (and never-found-target-thread
+                   (set 'never-found-target-thread t)))))
+           (when (and never-found-target-thread
                       notmuch-search-target-line)
-                 (goto-char (point-min))
-                 (forward-line (1- notmuch-search-target-line)))))))))
+             (goto-char (point-min))
+             (forward-line (1- notmuch-search-target-line)))))))))
 
 (define-widget 'notmuch--custom-face-edit 'lazy
   "Custom face edit with a tag Edit Face"
@@ -703,7 +718,7 @@ Here is an example of how to color search results based on tags.
  (the following text would be placed in your ~/.emacs file):
 
  (setq notmuch-search-line-faces \\='((\"unread\" . (:foreground \"green\"))
-                                   (\"deleted\" . (:foreground \"red\"
+                                  (\"deleted\" . (:foreground \"red\"
                                                  :background \"blue\"))))
 
 The FACE must be a face name (a symbol or string), a property
@@ -714,7 +729,7 @@ the above settings would have a green foreground and blue
 background."
   :type '(alist :key-type (string)
                :value-type (radio (face :tag "Face name")
-                                   (notmuch--custom-face-edit)))
+                                  (notmuch--custom-face-edit)))
   :group 'notmuch-search
   :group 'notmuch-faces)
 
@@ -747,58 +762,57 @@ non-authors is found, assume that all of the authors match."
           (visible-string formatted-authors)
           (invisible-string "")
           (padding ""))
-
       ;; Truncate the author string to fit the specification.
-      (if (> (length formatted-authors)
-            (length formatted-sample))
-         (let ((visible-length (- (length formatted-sample)
-                                  (length "... "))))
-           ;; Truncate the visible string according to the width of
-           ;; the display string.
-           (setq visible-string (substring formatted-authors 0 visible-length)
-                 invisible-string (substring formatted-authors visible-length))
-           ;; If possible, truncate the visible string at a natural
-           ;; break (comma or pipe), as incremental search doesn't
-           ;; match across the visible/invisible border.
-           (when (string-match "\\(.*\\)\\([,|] \\)\\([^,|]*\\)" visible-string)
-             ;; Second clause is destructive on `visible-string', so
-             ;; order is important.
-             (setq invisible-string (concat (match-string 3 visible-string)
-                                            invisible-string)
-                   visible-string (concat (match-string 1 visible-string)
-                                          (match-string 2 visible-string))))
-           ;; `visible-string' may be shorter than the space allowed
-           ;; by `format-string'. If so we must insert some padding
-           ;; after `invisible-string'.
-           (setq padding (make-string (- (length formatted-sample)
-                                         (length visible-string)
-                                         (length "..."))
-                                      ? ))))
-
+      (when (> (length formatted-authors)
+              (length formatted-sample))
+       (let ((visible-length (- (length formatted-sample)
+                                (length "... "))))
+         ;; Truncate the visible string according to the width of
+         ;; the display string.
+         (setq visible-string (substring formatted-authors 0 visible-length))
+         (setq invisible-string (substring formatted-authors visible-length))
+         ;; If possible, truncate the visible string at a natural
+         ;; break (comma or pipe), as incremental search doesn't
+         ;; match across the visible/invisible border.
+         (when (string-match "\\(.*\\)\\([,|] \\)\\([^,|]*\\)" visible-string)
+           ;; Second clause is destructive on `visible-string', so
+           ;; order is important.
+           (setq invisible-string (concat (match-string 3 visible-string)
+                                          invisible-string))
+           (setq visible-string (concat (match-string 1 visible-string)
+                                        (match-string 2 visible-string))))
+         ;; `visible-string' may be shorter than the space allowed
+         ;; by `format-string'. If so we must insert some padding
+         ;; after `invisible-string'.
+         (setq padding (make-string (- (length formatted-sample)
+                                       (length visible-string)
+                                       (length "..."))
+                                    ? ))))
       ;; Use different faces to show matching and non-matching authors.
       (if (string-match "\\(.*\\)|\\(.*\\)" visible-string)
          ;; The visible string contains both matching and
          ;; non-matching authors.
-         (setq visible-string (notmuch-search-author-propertize visible-string)
-               ;; The invisible string must contain only non-matching
-               ;; authors, as the visible-string contains both.
-               invisible-string (propertize invisible-string
-                                            'face 'notmuch-search-non-matching-authors))
+         (progn
+           (setq visible-string (notmuch-search-author-propertize visible-string))
+           ;; The invisible string must contain only non-matching
+           ;; authors, as the visible-string contains both.
+           (setq invisible-string (propertize invisible-string
+                                              'face 'notmuch-search-non-matching-authors)))
        ;; The visible string contains only matching authors.
        (setq visible-string (propertize visible-string
-                                        'face 'notmuch-search-matching-authors)
-             ;; The invisible string may contain both matching and
-             ;; non-matching authors.
-             invisible-string (notmuch-search-author-propertize invisible-string)))
-
+                                        'face 'notmuch-search-matching-authors))
+       ;; The invisible string may contain both matching and
+       ;; non-matching authors.
+       (setq invisible-string (notmuch-search-author-propertize invisible-string)))
       ;; If there is any invisible text, add it as a tooltip to the
       ;; visible text.
-      (when (not (string= invisible-string ""))
-       (setq visible-string (propertize visible-string 'help-echo (concat "..." invisible-string))))
-
+      (unless (string= invisible-string "")
+       (setq visible-string
+             (propertize visible-string
+                         'help-echo (concat "..." invisible-string))))
       ;; Insert the visible and, if present, invisible author strings.
       (insert visible-string)
-      (when (not (string= invisible-string ""))
+      (unless (string= invisible-string "")
        (let ((start (point))
              overlay)
          (insert invisible-string)
@@ -821,11 +835,9 @@ non-authors is found, assume that all of the authors match."
     (insert (propertize (format format-string
                                (notmuch-sanitize (plist-get result :subject)))
                        'face 'notmuch-search-subject)))
-
    ((string-equal field "authors")
     (notmuch-search-insert-authors
      format-string (notmuch-sanitize (plist-get result :authors))))
-
    ((string-equal field "tags")
     (let ((tags (plist-get result :tags))
          (orig-tags (plist-get result :orig-tags)))
@@ -856,7 +868,7 @@ sets the :orig-tag property."
       (goto-char pos))))
 
 (defun notmuch-search-process-filter (proc string)
-  "Process and filter the output of \"notmuch search\""
+  "Process and filter the output of \"notmuch search\"."
   (let ((results-buf (process-buffer proc))
        (parse-buf (process-get proc 'parse-buf))
        (inhibit-read-only t)
@@ -884,12 +896,14 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
   (let* ((saved-search
          (let (longest
                (longest-length 0))
-           (loop for tuple in notmuch-saved-searches
-                 if (let ((quoted-query (regexp-quote (notmuch-saved-search-get tuple :query))))
-                      (and (string-match (concat "^" quoted-query) query)
-                           (> (length (match-string 0 query))
-                              longest-length)))
-                 do (setq longest tuple))
+           (cl-loop for tuple in notmuch-saved-searches
+                    if (let ((quoted-query
+                              (regexp-quote
+                               (notmuch-saved-search-get tuple :query))))
+                         (and (string-match (concat "^" quoted-query) query)
+                              (> (length (match-string 0 query))
+                                 longest-length)))
+                    do (setq longest tuple))
            longest))
         (saved-search-name (notmuch-saved-search-get saved-search :name))
         (saved-search-query (notmuch-saved-search-get saved-search :query)))
@@ -898,30 +912,31 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
           (concat "*notmuch-saved-search-" saved-search-name "*"))
          (saved-search
           (concat "*notmuch-search-"
-                  (replace-regexp-in-string (concat "^" (regexp-quote saved-search-query))
-                                            (concat "[ " saved-search-name " ]")
-                                            query)
+                  (replace-regexp-in-string
+                   (concat "^" (regexp-quote saved-search-query))
+                   (concat "[ " saved-search-name " ]")
+                   query)
                   "*"))
          (t
-          (concat "*notmuch-search-" query "*"))
-         )))
+          (concat "*notmuch-search-" query "*")))))
 
 (defun notmuch-read-query (prompt)
   "Read a notmuch-query from the minibuffer with completion.
 
 PROMPT is the string to prompt with."
-  (lexical-let*
+  (let*
       ((all-tags
-        (mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
-                (process-lines notmuch-command "search" "--output=tags" "*")))
+       (mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
+               (process-lines notmuch-command "search" "--output=tags" "*")))
        (completions
-        (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
-                      "subject:" "attachment:")
-                (mapcar (lambda (tag) (concat "tag:" tag)) all-tags)
-                (mapcar (lambda (tag) (concat "is:" tag)) all-tags)
-                (mapcar (lambda (mimetype) (concat "mimetype:" mimetype)) (mailcap-mime-types)))))
+       (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
+                     "subject:" "attachment:")
+               (mapcar (lambda (tag) (concat "tag:" tag)) all-tags)
+               (mapcar (lambda (tag) (concat "is:" tag)) all-tags)
+               (mapcar (lambda (mimetype) (concat "mimetype:" mimetype))
+                       (mailcap-mime-types)))))
     (let ((keymap (copy-keymap minibuffer-local-map))
-         (current-query (case major-mode
+         (current-query (cl-case major-mode
                           (notmuch-search-mode (notmuch-search-get-query))
                           (notmuch-show-mode (notmuch-show-get-query))
                           (notmuch-tree-mode (notmuch-tree-get-query))))
@@ -945,7 +960,7 @@ PROMPT is the string to prompt with."
                              'notmuch-search-history current-query nil)))))
 
 (defun notmuch-search-get-query ()
-  "Return the current query in this search buffer"
+  "Return the current query in this search buffer."
   notmuch-search-query-string)
 
 (put 'notmuch-search 'notmuch-doc "Search for messages.")
@@ -979,8 +994,12 @@ the configured default sort order."
         (buffer (get-buffer-create (notmuch-search-buffer-title query))))
     (if no-display
        (set-buffer buffer)
-      (switch-to-buffer buffer))
-    (notmuch-search-mode)
+      (pop-to-buffer-same-window buffer))
+    ;; avoid wiping out third party buffer-local variables in the case
+    ;; where we're just refreshing or changing the sort order of an
+    ;; existing search results buffer
+    (unless (eq major-mode 'notmuch-search-mode)
+      (notmuch-search-mode))
     ;; Don't track undo information for this buffer
     (set 'buffer-undo-list t)
     (set 'notmuch-search-query-string query)
@@ -990,9 +1009,8 @@ the configured default sort order."
     (notmuch-tag-clear-cache)
     (let ((proc (get-buffer-process (current-buffer)))
          (inhibit-read-only t))
-      (if proc
-         (error "notmuch search process already running for query `%s'" query)
-       )
+      (when proc
+       (error "notmuch search process already running for query `%s'" query))
       (erase-buffer)
       (goto-char (point-min))
       (save-excursion
@@ -1067,8 +1085,16 @@ current search results AND the additional query string provided."
 Runs a new search matching only messages that match both the
 current search results AND that are tagged with the given tag."
   (interactive
-   (list (notmuch-select-tag-with-completion "Filter by tag: " notmuch-search-query-string)))
-  (notmuch-search (concat notmuch-search-query-string " and tag:" tag) notmuch-search-oldest-first))
+   (list (notmuch-select-tag-with-completion "Filter by tag: "
+                                            notmuch-search-query-string)))
+  (notmuch-search (concat notmuch-search-query-string " and tag:" tag)
+                 notmuch-search-oldest-first))
+
+(defun notmuch-search-by-tag (tag)
+  "Display threads matching TAG in a notmuch-search buffer."
+  (interactive
+   (list (notmuch-select-tag-with-completion "Notmuch search tag: ")))
+  (notmuch-search (concat "tag:" tag)))
 
 ;;;###autoload
 (defun notmuch ()
@@ -1092,7 +1118,6 @@ current search results AND that are tagged with the given tag."
 If the current buffer is the only notmuch buffer, bury it. If no
 notmuch buffers exist, run `notmuch'."
   (interactive)
-
   (let (start first)
     ;; If the current buffer is a notmuch buffer, remember it and then
     ;; bury it.
@@ -1101,15 +1126,15 @@ notmuch buffers exist, run `notmuch'."
       (bury-buffer))
 
     ;; Find the first notmuch buffer.
-    (setq first (loop for buffer in (buffer-list)
-                     if (notmuch-interesting-buffer buffer)
-                     return buffer))
+    (setq first (cl-loop for buffer in (buffer-list)
+                        if (notmuch-interesting-buffer buffer)
+                        return buffer))
 
     (if first
        ;; If the first one we found is any other than the starting
        ;; buffer, switch to it.
        (unless (eq first start)
-         (switch-to-buffer first))
+         (pop-to-buffer-same-window first))
       (notmuch))))
 
 ;;;; Imenu Support
@@ -1134,9 +1159,7 @@ beginning of the line."
 (provide 'notmuch)
 
 ;; After provide to avoid loops if notmuch was require'd via notmuch-init-file.
-(if init-file-user ; don't load init file if the -q option was used.
-    (let ((init-file (locate-file notmuch-init-file '("/")
-                                 (get-load-suffixes))))
-      (if init-file (load init-file nil t t))))
+(when init-file-user ; don't load init file if the -q option was used.
+  (load notmuch-init-file t t nil t))
 
 ;;; notmuch.el ends here
index 2225aefc023a2a1776f31c53332551965952a91e..4221f142ce74e1ea6598fc4118e02d9b6439d253 100644 (file)
@@ -24,7 +24,6 @@
 ;;
 
 ;;; Commentary:
-;;
 
 ;; Rstdoc provides a facility to extract all of the docstrings defined in
 ;; an elisp source file. Usage:
 
 ;;; Code:
 
-(provide 'rstdoc)
-
 (defun rstdoc-batch-extract ()
-  "Extract docstrings to and from the files on the command line"
+  "Extract docstrings to and from the files on the command line."
   (apply #'rstdoc-extract command-line-args-left))
 
 (defun rstdoc-extract (in-file out-file)
-  "Write docstrings from IN-FILE to OUT-FILE"
+  "Write docstrings from IN-FILE to OUT-FILE."
   (load-file in-file)
   (let* ((definitions (cdr (assoc (expand-file-name in-file) load-history)))
+        (text-quoting-style 'grave)
         (doc-hash (make-hash-table :test 'eq)))
     (mapc
      (lambda (elt)
 
 (defun rstdoc--insert-docstring (symbol docstring)
   (insert (format "\n.. |docstring::%s| replace::\n" symbol))
-  (insert (replace-regexp-in-string "^" "    " (rstdoc--rst-quote-string docstring)))
+  (insert (replace-regexp-in-string "^" "    "
+                                   (rstdoc--rst-quote-string docstring)))
   (insert "\n"))
 
 (defvar rst--escape-alist
-  '( ("\\\\='" . "\\\\'")
-     ("\\([^\\]\\)'" . "\\1`")
-     ("^[[:space:]\t]*$" . "|br|")
-     ("^[[:space:]\t]" . "|indent| "))
+  '( ("\\\\='" . "\001")
+     ("`\\([^\n`']*\\)[`']" . "\002\\1\002") ;; good enough for now...
+     ("`" . "\\\\`")
+     ("\001" . "'")
+     ("\002" . "`")
+     ("^[[:space:]]*$" . "|br|")
+     ("^[[:space:]]" . "|indent| "))
     "list of (regex . replacement) pairs")
 
 (defun rstdoc--rst-quote-string (str)
@@ -82,4 +84,6 @@
        (replace-match (cdr pair))))
     (buffer-substring (point-min) (point-max))))
 
+(provide 'rstdoc)
+
 ;;; rstdoc.el ends here
index 480d93817bb627e6afb2c1ef5cc8fe7093355945..2b0676698b8ff7225b164c9db52ab5ad5b5ea314 100644 (file)
@@ -47,143 +47,143 @@ static GMimeFilterClass *parent_class = NULL;
 GType
 g_mime_filter_reply_get_type (void)
 {
-       static GType type = 0;
-
-       if (!type) {
-               static const GTypeInfo info = {
-                       .class_size = sizeof (GMimeFilterReplyClass),
-                       .base_init = NULL,
-                       .base_finalize = NULL,
-                       .class_init = (GClassInitFunc) g_mime_filter_reply_class_init,
-                       .class_finalize = NULL,
-                       .class_data = NULL,
-                       .instance_size = sizeof (GMimeFilterReply),
-                       .n_preallocs = 0,
-                       .instance_init = (GInstanceInitFunc) g_mime_filter_reply_init,
-                       .value_table = NULL,
-               };
-
-               type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterReply", &info, (GTypeFlags) 0);
-       }
-
-       return type;
+    static GType type = 0;
+
+    if (! type) {
+       static const GTypeInfo info = {
+           .class_size = sizeof (GMimeFilterReplyClass),
+           .base_init = NULL,
+           .base_finalize = NULL,
+           .class_init = (GClassInitFunc) g_mime_filter_reply_class_init,
+           .class_finalize = NULL,
+           .class_data = NULL,
+           .instance_size = sizeof (GMimeFilterReply),
+           .n_preallocs = 0,
+           .instance_init = (GInstanceInitFunc) g_mime_filter_reply_init,
+           .value_table = NULL,
+       };
+
+       type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterReply", &info, (GTypeFlags) 0);
+    }
+
+    return type;
 }
 
 
 static void
 g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass, unused (void *class_data))
 {
-       GObjectClass *object_class = G_OBJECT_CLASS (klass);
-       GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
 
-       parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER);
+    parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER);
 
-       object_class->finalize = g_mime_filter_reply_finalize;
+    object_class->finalize = g_mime_filter_reply_finalize;
 
-       filter_class->copy = filter_copy;
-       filter_class->filter = filter_filter;
-       filter_class->complete = filter_complete;
-       filter_class->reset = filter_reset;
+    filter_class->copy = filter_copy;
+    filter_class->filter = filter_filter;
+    filter_class->complete = filter_complete;
+    filter_class->reset = filter_reset;
 }
 
 static void
 g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass)
 {
-       (void) klass;
-       filter->saw_nl = true;
-       filter->saw_angle = false;
+    (void) klass;
+    filter->saw_nl = true;
+    filter->saw_angle = false;
 }
 
 static void
 g_mime_filter_reply_finalize (GObject *object)
 {
-       G_OBJECT_CLASS (parent_class)->finalize (object);
+    G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
 
 static GMimeFilter *
 filter_copy (GMimeFilter *filter)
 {
-       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+    GMimeFilterReply *reply = (GMimeFilterReply *) filter;
 
-       return g_mime_filter_reply_new (reply->encode);
+    return g_mime_filter_reply_new (reply->encode);
 }
 
 static void
 filter_filter (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
               char **outbuf, size_t *outlen, size_t *outprespace)
 {
-       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
-       const char *inptr = inbuf;
-       const char *inend = inbuf + inlen;
-       char *outptr;
-
-       (void) prespace;
-       if (reply->encode) {
-               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;
-                       }
-                       if (*inptr == '\n')
-                               reply->saw_nl = true;
-                       else
-                               reply->saw_nl = false;
-                       if (*inptr != '\r')
-                               *outptr++ = *inptr;
-                       inptr++;
-               }
-       } else {
-               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;
-                               else
-                                       *outptr++ = *inptr;
-                               reply->saw_nl = false;
-                       } else if (reply->saw_angle) {
-                               if (*inptr == ' ')
-                                       ;
-                               else
-                                       *outptr++ = *inptr;
-                               reply->saw_angle = false;
-                       } else if (*inptr != '\r') {
-                               if (*inptr == '\n')
-                                       reply->saw_nl = true;
-                               *outptr++ = *inptr;
-                       }
-
-                       inptr++;
-               }
+    GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+    const char *inptr = inbuf;
+    const char *inend = inbuf + inlen;
+    char *outptr;
+
+    (void) prespace;
+    if (reply->encode) {
+       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;
+           }
+           if (*inptr == '\n')
+               reply->saw_nl = true;
+           else
+               reply->saw_nl = false;
+           if (*inptr != '\r')
+               *outptr++ = *inptr;
+           inptr++;
+       }
+    } else {
+       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;
+               else
+                   *outptr++ = *inptr;
+               reply->saw_nl = false;
+           } else if (reply->saw_angle) {
+               if (*inptr == ' ')
+                   ;
+               else
+                   *outptr++ = *inptr;
+               reply->saw_angle = false;
+           } else if (*inptr != '\r') {
+               if (*inptr == '\n')
+                   reply->saw_nl = true;
+               *outptr++ = *inptr;
+           }
+
+           inptr++;
        }
+    }
 
-       *outlen = outptr - filter->outbuf;
-       *outprespace = filter->outpre;
-       *outbuf = filter->outbuf;
+    *outlen = outptr - filter->outbuf;
+    *outprespace = filter->outpre;
+    *outbuf = filter->outbuf;
 }
 
 static void
 filter_complete (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
                 char **outbuf, size_t *outlen, size_t *outprespace)
 {
-       if (inbuf && inlen)
-               filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace);
+    if (inbuf && inlen)
+       filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace);
 }
 
 static void
 filter_reset (GMimeFilter *filter)
 {
-       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+    GMimeFilterReply *reply = (GMimeFilterReply *) filter;
 
-       reply->saw_nl = true;
-       reply->saw_angle = false;
+    reply->saw_nl = true;
+    reply->saw_angle = false;
 }
 
 
@@ -202,11 +202,11 @@ filter_reset (GMimeFilter *filter)
 GMimeFilter *
 g_mime_filter_reply_new (gboolean encode)
 {
-       GMimeFilterReply *new_reply;
+    GMimeFilterReply *new_reply;
 
-       new_reply = (GMimeFilterReply *) g_object_new (GMIME_TYPE_FILTER_REPLY, NULL);
-       new_reply->encode = encode;
+    new_reply = (GMimeFilterReply *) g_object_new (GMIME_TYPE_FILTER_REPLY, NULL);
+    new_reply->encode = encode;
 
-       return (GMimeFilter *) new_reply;
+    return (GMimeFilter *) new_reply;
 }
 
index b7cbc6b10f742af6b7e48b5e573454f7b75013d5..5a1e606e5079a5ac7f2c2688222b9150b37ff7c4 100644 (file)
@@ -43,15 +43,15 @@ typedef struct _GMimeFilterReplyClass GMimeFilterReplyClass;
  * A filter to insert/remove reply markers (lines beginning with >)
  **/
 struct _GMimeFilterReply {
-       GMimeFilter parent_object;
+    GMimeFilter parent_object;
 
-       gboolean encode;
-       gboolean saw_nl;
-       gboolean saw_angle;
+    gboolean encode;
+    gboolean saw_nl;
+    gboolean saw_angle;
 };
 
 struct _GMimeFilterReplyClass {
-       GMimeFilterClass parent_class;
+    GMimeFilterClass parent_class;
 
 };
 
diff --git a/hooks.c b/hooks.c
index 7348d322791f8eb23ec97e318f53123ba96d0084..59c5807065fc4ee58974a7eec196c7335bcbb8e3 100644 (file)
--- a/hooks.c
+++ b/hooks.c
@@ -53,7 +53,7 @@ notmuch_run_hook (const char *db_path, const char *hook)
     /* Flush any buffered output before forking. */
     fflush (stdout);
 
-    pid = fork();
+    pid = fork ();
     if (pid == -1) {
        fprintf (stderr, "Error: %s hook fork failed: %s\n", hook,
                 strerror (errno));
@@ -78,7 +78,7 @@ notmuch_run_hook (const char *db_path, const char *hook)
        goto DONE;
     }
 
-    if (!WIFEXITED (status) || WEXITSTATUS (status)) {
+    if (! WIFEXITED (status) || WEXITSTATUS (status)) {
        if (WIFEXITED (status)) {
            fprintf (stderr, "Error: %s hook failed with status %d\n",
                     hook, WEXITSTATUS (status));
index 5dc057c090133b5c4be9647635a623188eeff6d2..a640012676d818a42490ebb93359121710f13aa0 100644 (file)
@@ -1,4 +1,4 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := lib
 
index da37032c5979cb0eeb1d67621dc9d57bff80a32f..485debad225239211e9a6f165f24be519080a0ed 100644 (file)
@@ -34,7 +34,7 @@ parse_references (void *ctx,
      * reference to the database.  We should avoid making a message
      * its own parent, thus the above check.
      */
-    return talloc_strdup(ctx, last_ref);
+    return talloc_strdup (ctx, last_ref);
 }
 
 static const char *
@@ -43,15 +43,12 @@ _notmuch_database_generate_thread_id (notmuch_database_t *notmuch)
     /* 16 bytes (+ terminator) for hexadecimal representation of
      * a 64-bit integer. */
     static char thread_id[17];
-    Xapian::WritableDatabase *db;
-
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
 
     notmuch->last_thread_id++;
 
     sprintf (thread_id, "%016" PRIx64, notmuch->last_thread_id);
 
-    db->set_metadata ("last_thread_id", thread_id);
+    notmuch->writable_xapian_db->set_metadata ("last_thread_id", thread_id);
 
     return thread_id;
 }
@@ -161,16 +158,16 @@ _resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
      * can return the thread ID stored in the metadata. Otherwise, we
      * generate a new thread ID and store it there.
      */
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    db = notmuch->writable_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()) {
+    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());
+       *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str ());
     }
 
     talloc_free (metadata_key);
@@ -190,7 +187,7 @@ _merge_threads (notmuch_database_t *notmuch,
 
     _notmuch_database_find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
 
-    for ( ; loser != loser_end; loser++) {
+    for (; loser != loser_end; loser++) {
        message = _notmuch_message_create (notmuch, notmuch,
                                           *loser, &private_status);
        if (message == NULL) {
@@ -264,7 +261,7 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
                                   last_ref_message_id);
     } else if (in_reply_to_message_id) {
        _notmuch_message_add_term (message, "replyto",
-                            in_reply_to_message_id);
+                                  in_reply_to_message_id);
     }
 
     keys = g_hash_table_get_keys (parents);
@@ -317,7 +314,7 @@ _notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
 
     _notmuch_database_find_doc_ids (notmuch, "reference", message_id, &child, &children_end);
 
-    for ( ; child != children_end; child++) {
+    for (; child != children_end; child++) {
 
        child_message = _notmuch_message_create (message, notmuch,
                                                 *child, &private_status);
@@ -370,13 +367,9 @@ _consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch,
     if (stored_id.empty ()) {
        return NULL;
     } else {
-       Xapian::WritableDatabase *db;
-
-       db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
        /* Clear the metadata for this message ID. We don't need it
         * anymore. */
-       db->set_metadata (metadata_key, "");
+       notmuch->writable_xapian_db->set_metadata (metadata_key, "");
 
        return talloc_strdup (ctx, stored_id.c_str ());
     }
@@ -461,7 +454,7 @@ _notmuch_database_link_message (notmuch_database_t *notmuch,
        _notmuch_message_add_term (message, "thread", thread_id);
     }
 
- DONE:
 DONE:
     talloc_free (local);
 
     return status;
@@ -477,7 +470,7 @@ notmuch_database_index_file (notmuch_database_t *notmuch,
     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_bool_t is_ghost = false, is_new = false;
     notmuch_indexopts_t *def_indexopts = NULL;
 
     const char *date;
@@ -525,7 +518,9 @@ notmuch_database_index_file (notmuch_database_t *notmuch,
            is_new = true;
            break;
        case NOTMUCH_PRIVATE_STATUS_SUCCESS:
-           is_ghost = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST);
+           ret = notmuch_message_get_flag_st (message, NOTMUCH_MESSAGE_FLAG_GHOST, &is_ghost);
+           if (ret)
+               goto DONE;
            is_new = false;
            break;
        default:
@@ -544,14 +539,14 @@ notmuch_database_index_file (notmuch_database_t *notmuch,
        }
 
        ret = _notmuch_database_link_message (notmuch, message,
-                                                 message_file, is_ghost);
+                                             message_file, is_ghost);
        if (ret)
            goto DONE;
 
        if (is_new || is_ghost)
            _notmuch_message_set_header_values (message, date, from, subject);
 
-       if (!indexopts) {
+       if (! indexopts) {
            def_indexopts = notmuch_database_get_default_indexopts (notmuch);
            indexopts = def_indexopts;
        }
@@ -560,13 +555,13 @@ notmuch_database_index_file (notmuch_database_t *notmuch,
        if (ret)
            goto DONE;
 
-       if (! is_new && !is_ghost)
+       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());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
        goto DONE;
index 320be6c5ed20d1e2114ae88ed1d37b92666a0b12..0c70010beca5548c012b531edb70691703df5ca6 100644 (file)
@@ -25,9 +25,9 @@ notmuch_bool_t
 notmuch_built_with (const char *name)
 {
     if (STRNCMP_LITERAL (name, "compact") == 0) {
-       return HAVE_XAPIAN_COMPACT;
+       return true;
     } else if (STRNCMP_LITERAL (name, "field_processor") == 0) {
-       return HAVE_XAPIAN_FIELD_PROCESSOR;
+       return true;
     } else if (STRNCMP_LITERAL (name, "retry_lock") == 0) {
        return HAVE_XAPIAN_DB_RETRY_LOCK;
     } else if (STRNCMP_LITERAL (name, "session_key") == 0) {
index a8bcdf831b453d93c4f8b4a43c2b7dd0d43e8cda..efab01e400708fde51470b85eee3102482f59a4e 100644 (file)
@@ -45,22 +45,20 @@ notmuch_database_set_config (notmuch_database_t *notmuch,
                             const char *value)
 {
     notmuch_status_t status;
-    Xapian::WritableDatabase *db;
 
     status = _notmuch_database_ensure_writable (notmuch);
     if (status)
        return status;
 
     try {
-       db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-       db->set_metadata (CONFIG_PREFIX + key, value);
+       notmuch->writable_xapian_db->set_metadata (CONFIG_PREFIX + key, value);
     } catch (const Xapian::Error &error) {
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
        notmuch->exception_reported = true;
        _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
-                              error.get_msg().c_str());
+                              error.get_msg ().c_str ());
     }
-    return NOTMUCH_STATUS_SUCCESS;
+    return status;
 }
 
 static notmuch_status_t
@@ -76,7 +74,7 @@ _metadata_value (notmuch_database_t *notmuch,
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
        notmuch->exception_reported = true;
        _notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n",
-                              error.get_msg().c_str());
+                              error.get_msg ().c_str ());
     }
     return status;
 }
@@ -115,7 +113,6 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch,
        goto DONE;
     }
 
-    talloc_set_destructor (list, _notmuch_config_list_destroy);
     list->notmuch = notmuch;
     list->current_key = NULL;
     list->current_val = NULL;
@@ -123,11 +120,12 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch,
     try {
 
        new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin
-                                                    (CONFIG_PREFIX + (prefix ? prefix : "")));
+                                                        (CONFIG_PREFIX + (prefix ? prefix : "")));
+       talloc_set_destructor (list, _notmuch_config_list_destroy);
 
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n",
-                              error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
index 9d1dabf109625896363b21e9c417dafd7d5d95e6..041602cdc6a8911b33531cfa8e0dbd72307b8242 100644 (file)
@@ -62,7 +62,7 @@ enum _notmuch_features {
      * unset, file names are stored in document data.
      *
      * Introduced: version 1. */
-    NOTMUCH_FEATURE_FILE_TERMS = 1 << 0,
+    NOTMUCH_FEATURE_FILE_TERMS                 = 1 << 0,
 
     /* If set, directory timestamps are stored in documents with
      * XDIRECTORY terms and relative paths.  If unset, directory
@@ -70,7 +70,7 @@ enum _notmuch_features {
      * absolute paths.
      *
      * Introduced: version 1. */
-    NOTMUCH_FEATURE_DIRECTORY_DOCS = 1 << 1,
+    NOTMUCH_FEATURE_DIRECTORY_DOCS             = 1 << 1,
 
     /* If set, the from, subject, and message-id headers are stored in
      * message document values.  If unset, message documents *may*
@@ -79,21 +79,21 @@ enum _notmuch_features {
      *
      * Introduced: optional in version 1, required as of version 3.
      */
-    NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES = 1 << 2,
+    NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES     = 1 << 2,
 
     /* If set, folder terms are boolean and path terms exist.  If
      * unset, folder terms are probabilistic and stemmed and path
      * terms do not exist.
      *
      * Introduced: version 2. */
-    NOTMUCH_FEATURE_BOOL_FOLDER = 1 << 3,
+    NOTMUCH_FEATURE_BOOL_FOLDER                        = 1 << 3,
 
     /* If set, missing messages are stored in ghost mail documents.
      * If unset, thread IDs of ghost messages are stored as database
      * metadata instead of in ghost documents.
      *
      * Introduced: version 3. */
-    NOTMUCH_FEATURE_GHOSTS = 1 << 4,
+    NOTMUCH_FEATURE_GHOSTS                     = 1 << 4,
 
 
     /* If set, then the database was created after the introduction of
@@ -101,52 +101,52 @@ enum _notmuch_features {
      * mixture of messages with indexed and non-indexed mime types.
      *
      * Introduced: version 3. */
-    NOTMUCH_FEATURE_INDEXED_MIMETYPES = 1 << 5,
+    NOTMUCH_FEATURE_INDEXED_MIMETYPES          = 1 << 5,
 
     /* If set, messages store the revision number of the last
      * modification in NOTMUCH_VALUE_LAST_MOD.
      *
      * Introduced: version 3. */
-    NOTMUCH_FEATURE_LAST_MOD = 1 << 6,
+    NOTMUCH_FEATURE_LAST_MOD                   = 1 << 6,
 
     /* If set, unprefixed terms are stored only for the message body,
      * not for headers.
      *
      * Introduced: version 3. */
-    NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY = 1 << 7,
+    NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY         = 1 << 7,
 };
 
 /* In C++, a named enum is its own type, so define bitwise operators
  * on _notmuch_features. */
 inline _notmuch_features
-operator|(_notmuch_features a, _notmuch_features b)
+operator| (_notmuch_features a, _notmuch_features b)
 {
     return static_cast<_notmuch_features>(
        static_cast<unsigned>(a) | static_cast<unsigned>(b));
 }
 
 inline _notmuch_features
-operator&(_notmuch_features a, _notmuch_features b)
+operator& (_notmuch_features a, _notmuch_features b)
 {
     return static_cast<_notmuch_features>(
        static_cast<unsigned>(a) & static_cast<unsigned>(b));
 }
 
 inline _notmuch_features
-operator~(_notmuch_features a)
+operator~ (_notmuch_features a)
 {
     return static_cast<_notmuch_features>(~static_cast<unsigned>(a));
 }
 
 inline _notmuch_features&
-operator|=(_notmuch_features &a, _notmuch_features b)
+operator|= (_notmuch_features &a, _notmuch_features b)
 {
     a = a | b;
     return a;
 }
 
 inline _notmuch_features&
-operator&=(_notmuch_features &a, _notmuch_features b)
+operator&= (_notmuch_features &a, _notmuch_features b)
 {
     a = a & b;
     return a;
@@ -155,23 +155,23 @@ operator&=(_notmuch_features &a, _notmuch_features b)
 /*
  * Configuration options for xapian database fields */
 typedef enum notmuch_field_flags {
-    NOTMUCH_FIELD_NO_FLAGS = 0,
-    NOTMUCH_FIELD_EXTERNAL = 1 << 0,
+    NOTMUCH_FIELD_NO_FLAGS     = 0,
+    NOTMUCH_FIELD_EXTERNAL     = 1 << 0,
     NOTMUCH_FIELD_PROBABILISTIC = 1 << 1,
-    NOTMUCH_FIELD_PROCESSOR = 1 << 2,
+    NOTMUCH_FIELD_PROCESSOR    = 1 << 2,
 } notmuch_field_flag_t;
 
 /*
  * define bitwise operators to hide casts */
 inline notmuch_field_flag_t
-operator|(notmuch_field_flag_t a, notmuch_field_flag_t b)
+operator| (notmuch_field_flag_t a, notmuch_field_flag_t b)
 {
     return static_cast<notmuch_field_flag_t>(
        static_cast<unsigned>(a) | static_cast<unsigned>(b));
 }
 
 inline notmuch_field_flag_t
-operator&(notmuch_field_flag_t a, notmuch_field_flag_t b)
+operator& (notmuch_field_flag_t a, notmuch_field_flag_t b)
 {
     return static_cast<notmuch_field_flag_t>(
        static_cast<unsigned>(a) & static_cast<unsigned>(b));
@@ -189,12 +189,12 @@ struct _notmuch_database {
 
     char *path;
 
-    notmuch_database_mode_t mode;
     int atomic_nesting;
     /* true if changes have been made in this atomic section */
     bool atomic_dirty;
     Xapian::Database *xapian_db;
-
+    Xapian::WritableDatabase *writable_xapian_db;
+    bool open;
     /* Bit mask of features used by this database.  This is a
      * bitwise-OR of NOTMUCH_FEATURE_* values (above). */
     enum _notmuch_features features;
@@ -218,9 +218,9 @@ struct _notmuch_database {
     unsigned long view;
     Xapian::QueryParser *query_parser;
     Xapian::TermGenerator *term_gen;
-    Xapian::ValueRangeProcessor *value_range_processor;
-    Xapian::ValueRangeProcessor *date_range_processor;
-    Xapian::ValueRangeProcessor *last_mod_range_processor;
+    Xapian::RangeProcessor *value_range_processor;
+    Xapian::RangeProcessor *date_range_processor;
+    Xapian::RangeProcessor *last_mod_range_processor;
 
     /* XXX it's slightly gross to use two parallel string->string maps
      * here, but at least they are small */
@@ -230,7 +230,7 @@ struct _notmuch_database {
 
 /* Prior to database version 3, features were implied by the database
  * version number, so hard-code them for earlier versions. */
-#define NOTMUCH_FEATURES_V0 ((enum _notmuch_features)0)
+#define NOTMUCH_FEATURES_V0 ((enum _notmuch_features) 0)
 #define NOTMUCH_FEATURES_V1 (NOTMUCH_FEATURES_V0 | NOTMUCH_FEATURE_FILE_TERMS | \
                             NOTMUCH_FEATURE_DIRECTORY_DOCS)
 #define NOTMUCH_FEATURES_V2 (NOTMUCH_FEATURES_V1 | NOTMUCH_FEATURE_BOOL_FOLDER)
index 4c4c9edc0b870241125ae8a06c2b2a76b322d3e0..7518968599f65fbeed8ac5285e15fb3cfb1cdc2e 100644 (file)
 #include <signal.h>
 #include <ftw.h>
 
-#include <glib.h> /* g_free, GPtrArray, GHashTable */
-#include <glib-object.h> /* g_type_init */
+#include <glib.h>               /* g_free, GPtrArray, GHashTable */
+#include <glib-object.h>        /* g_type_init */
 
-#include <gmime/gmime.h> /* g_mime_init */
+#include <gmime/gmime.h>        /* g_mime_init */
 
 using namespace std;
 
@@ -49,7 +49,7 @@ typedef struct {
 
 #define NOTMUCH_DATABASE_VERSION 3
 
-#define STRINGIFY(s) _SUB_STRINGIFY(s)
+#define STRINGIFY(s) _SUB_STRINGIFY (s)
 #define _SUB_STRINGIFY(s) #s
 
 #if HAVE_XAPIAN_DB_RETRY_LOCK
@@ -58,6 +58,26 @@ typedef struct {
 #define DB_ACTION Xapian::DB_CREATE_OR_OPEN
 #endif
 
+#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
+
+static void
+_log_xapian_exception (const char *where, notmuch_database_t *notmuch,  const Xapian::Error error) {
+    _notmuch_database_log (notmuch,
+                          "A Xapian exception occurred at %s: %s\n",
+                          where,
+                          error.get_msg ().c_str ());
+    notmuch->exception_reported = true;
+}
+
+notmuch_database_mode_t
+_notmuch_database_mode (notmuch_database_t *notmuch)
+{
+    if (notmuch->writable_xapian_db)
+       return NOTMUCH_DATABASE_MODE_READ_WRITE;
+    else
+       return NOTMUCH_DATABASE_MODE_READ_ONLY;
+}
+
 /* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION):
  *
  * We currently have three different types of documents (mail, ghost,
@@ -263,59 +283,57 @@ typedef struct {
 static const
 prefix_t prefix_table[] = {
     /* name                    term prefix     flags */
-    { "type",                  "T",            NOTMUCH_FIELD_NO_FLAGS },
-    { "reference",             "XREFERENCE",   NOTMUCH_FIELD_NO_FLAGS },
-    { "replyto",               "XREPLYTO",     NOTMUCH_FIELD_NO_FLAGS },
-    { "directory",             "XDIRECTORY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "file-direntry",         "XFDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "directory-direntry",    "XDDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "body",                  "",             NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC},
-    { "thread",                        "G",            NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "tag",                   "K",            NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "is",                    "K",            NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "id",                    "Q",            NOTMUCH_FIELD_EXTERNAL },
-    { "mid",                   "Q",            NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "path",                  "P",            NOTMUCH_FIELD_EXTERNAL|
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "property",              "XPROPERTY",    NOTMUCH_FIELD_EXTERNAL },
+    { "type",                   "T",            NOTMUCH_FIELD_NO_FLAGS },
+    { "reference",              "XREFERENCE",   NOTMUCH_FIELD_NO_FLAGS },
+    { "replyto",                "XREPLYTO",     NOTMUCH_FIELD_NO_FLAGS },
+    { "directory",              "XDIRECTORY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "file-direntry",          "XFDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "directory-direntry",     "XDDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "body",                   "",             NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "thread",                 "G",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "tag",                    "K",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "is",                     "K",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "id",                     "Q",            NOTMUCH_FIELD_EXTERNAL },
+    { "mid",                    "Q",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "path",                   "P",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "property",               "XPROPERTY",    NOTMUCH_FIELD_EXTERNAL },
     /*
      * Unconditionally add ':' to reduce potential ambiguity with
      * overlapping prefixes and/or terms that start with capital
      * letters. See Xapian document termprefixes.html for related
      * discussion.
      */
-    { "folder",                        "XFOLDER:",     NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-#if HAVE_XAPIAN_FIELD_PROCESSOR
-    { "date",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "query",                 NULL,           NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-#endif
-    { "from",                  "XFROM",        NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "to",                    "XTO",          NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC },
-    { "attachment",            "XATTACHMENT",  NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC },
-    { "mimetype",              "XMIMETYPE",    NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC },
-    { "subject",               "XSUBJECT",     NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC |
-                                               NOTMUCH_FIELD_PROCESSOR},
+    { "folder",                 "XFOLDER:",     NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "date",                   NULL,           NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "query",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "from",                   "XFROM",        NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "to",                     "XTO",          NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "attachment",             "XATTACHMENT",  NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "mimetype",               "XMIMETYPE",    NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "subject",                "XSUBJECT",     NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC |
+      NOTMUCH_FIELD_PROCESSOR },
 };
 
 static void
 _setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
 {
     if (prefix->prefix)
-       notmuch->query_parser->add_prefix ("",prefix->prefix);
+       notmuch->query_parser->add_prefix ("", prefix->prefix);
     if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
        notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
     else
@@ -329,9 +347,9 @@ _notmuch_database_user_headers (notmuch_database_t *notmuch)
 }
 
 const char *
-_user_prefix (void *ctx, const charname)
+_user_prefix (void *ctx, const char *name)
 {
-    return talloc_asprintf(ctx, "XU%s:", name);
+    return talloc_asprintf (ctx, "XU%s:", name);
 }
 
 static notmuch_status_t
@@ -357,7 +375,7 @@ _setup_user_query_fields (notmuch_database_t *notmuch)
        prefix_t query_field;
 
        const char *key = notmuch_config_list_key (list)
-           + sizeof (CONFIG_HEADER_PREFIX) - 1;
+                         + sizeof (CONFIG_HEADER_PREFIX) - 1;
 
        _notmuch_string_map_append (notmuch->user_prefix,
                                    key,
@@ -370,7 +388,7 @@ _setup_user_query_fields (notmuch_database_t *notmuch)
        query_field.name = talloc_strdup (notmuch, key);
        query_field.prefix = _user_prefix (notmuch, key);
        query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
-           | NOTMUCH_FIELD_EXTERNAL;
+                           | NOTMUCH_FIELD_EXTERNAL;
 
        _setup_query_field_default (&query_field, notmuch);
     }
@@ -380,7 +398,6 @@ _setup_user_query_fields (notmuch_database_t *notmuch)
     return NOTMUCH_STATUS_SUCCESS;
 }
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
 static void
 _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
 {
@@ -388,10 +405,10 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
        Xapian::FieldProcessor *fp;
 
        if (STRNCMP_LITERAL (prefix->name, "date") == 0)
-           fp = (new DateFieldProcessor())->release ();
+           fp = (new DateFieldProcessor(NOTMUCH_VALUE_TIMESTAMP))->release ();
        else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
            fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
-       else if (STRNCMP_LITERAL(prefix->name, "thread") == 0)
+       else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
            fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
        else
            fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
@@ -399,19 +416,12 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
 
        /* we treat all field-processor fields as boolean in order to get the raw input */
        if (prefix->prefix)
-           notmuch->query_parser->add_prefix ("",prefix->prefix);
+           notmuch->query_parser->add_prefix ("", prefix->prefix);
        notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
     } else {
        _setup_query_field_default (prefix, notmuch);
     }
 }
-#else
-static inline void
-_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
-    _setup_query_field_default (prefix, notmuch);
-}
-#endif
 
 const char *
 _find_prefix (const char *name)
@@ -469,18 +479,18 @@ static const struct {
     { NOTMUCH_FEATURE_BOOL_FOLDER,
       "exact folder:/path: search", "rw" },
     { NOTMUCH_FEATURE_GHOSTS,
-      "mail documents for missing messages", "w"},
+      "mail documents for missing messages", "w" },
     /* Knowledge of the index mime-types are not required for reading
      * a database because a reader will just be unable to query
      * them. */
     { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
-      "indexed MIME types", "w"},
+      "indexed MIME types", "w" },
     { NOTMUCH_FEATURE_LAST_MOD,
-      "modification tracking", "w"},
+      "modification tracking", "w" },
     /* Existing databases will work fine for all queries not involving
      * 'body:' */
     { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
-      "index body and headers separately", "w"},
+      "index body and headers separately", "w" },
 };
 
 const char *
@@ -529,8 +539,8 @@ notmuch_status_to_string (notmuch_status_t status)
 
 void
 _notmuch_database_log (notmuch_database_t *notmuch,
-                     const char *format,
-                     ...)
+                      const char *format,
+                      ...)
 {
     va_list va_args;
 
@@ -545,8 +555,8 @@ _notmuch_database_log (notmuch_database_t *notmuch,
 
 void
 _notmuch_database_log_append (notmuch_database_t *notmuch,
-                     const char *format,
-                     ...)
+                             const char *format,
+                             ...)
 {
     va_list va_args;
 
@@ -669,7 +679,7 @@ notmuch_database_find_message (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_SUCCESS;
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred finding message: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        *message_ret = NULL;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
@@ -720,7 +730,7 @@ notmuch_database_create_verbose (const char *path,
     err = stat (path, &st);
     if (err) {
        IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
-                               path, strerror (errno)));
+                                path, strerror (errno)));
        status = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
@@ -758,7 +768,7 @@ notmuch_database_create_verbose (const char *path,
 
     status = notmuch_database_upgrade (notmuch, NULL, NULL);
     if (status) {
-       notmuch_database_close(notmuch);
+       notmuch_database_close (notmuch);
        notmuch = NULL;
     }
 
@@ -782,7 +792,7 @@ notmuch_database_create_verbose (const char *path,
 notmuch_status_t
 _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
 {
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
+    if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) {
        _notmuch_database_log (notmuch, "Cannot write to a read-only database.\n");
        return NOTMUCH_STATUS_READ_ONLY_DATABASE;
     }
@@ -893,7 +903,7 @@ notmuch_database_open (const char *path,
     notmuch_status_t status;
 
     status = notmuch_database_open_verbose (path, mode, database,
-                                          &status_string);
+                                           &status_string);
 
     if (status_string) {
        fputs (status_string, stderr);
@@ -952,7 +962,7 @@ notmuch_database_open_verbose (const char *path,
     }
 
     /* Initialize the GLib type system and threads */
-#if !GLIB_CHECK_VERSION(2, 35, 1)
+#if ! GLIB_CHECK_VERSION (2, 35, 1)
     g_type_init ();
 #endif
 
@@ -967,9 +977,9 @@ notmuch_database_open_verbose (const char *path,
     notmuch->status_string = NULL;
     notmuch->path = talloc_strdup (notmuch, path);
 
-    strip_trailing(notmuch->path, '/');
+    strip_trailing (notmuch->path, '/');
 
-    notmuch->mode = mode;
+    notmuch->writable_xapian_db = NULL;
     notmuch->atomic_nesting = 0;
     notmuch->view = 1;
     try {
@@ -977,8 +987,9 @@ notmuch_database_open_verbose (const char *path,
        string last_mod;
 
        if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
-           notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
-                                                              DB_ACTION);
+           notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
+                                                                       DB_ACTION);
+           notmuch->xapian_db = notmuch->writable_xapian_db;
        } else {
            notmuch->xapian_db = new Xapian::Database (xapian_path);
        }
@@ -989,11 +1000,10 @@ notmuch_database_open_verbose (const char *path,
        version = notmuch_database_get_version (notmuch);
        if (version > NOTMUCH_DATABASE_VERSION) {
            IGNORE_RESULT (asprintf (&message,
-                     "Error: Notmuch database at %s\n"
-                     "       has a newer database format version (%u) than supported by this\n"
-                     "       version of notmuch (%u).\n",
+                                    "Error: Notmuch database at %s\n"
+                                    "       has a newer database format version (%u) than supported by this\n"
+                                    "       version of notmuch (%u).\n",
                                     notmuch_path, version, NOTMUCH_DATABASE_VERSION));
-           notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
            notmuch_database_destroy (notmuch);
            notmuch = NULL;
            status = NOTMUCH_STATUS_FILE_ERROR;
@@ -1008,11 +1018,10 @@ notmuch_database_open_verbose (const char *path,
            &incompat_features);
        if (incompat_features) {
            IGNORE_RESULT (asprintf (&message,
-               "Error: Notmuch database at %s\n"
-               "       requires features (%s)\n"
-               "       not supported by this version of notmuch.\n",
+                                    "Error: Notmuch database at %s\n"
+                                    "       requires features (%s)\n"
+                                    "       not supported by this version of notmuch.\n",
                                     notmuch_path, incompat_features));
-           notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
            notmuch_database_destroy (notmuch);
            notmuch = NULL;
            status = NOTMUCH_STATUS_FILE_ERROR;
@@ -1046,17 +1055,16 @@ notmuch_database_open_verbose (const char *path,
        notmuch->query_parser = new Xapian::QueryParser;
        notmuch->term_gen = new Xapian::TermGenerator;
        notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
-       notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
-       notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
-       notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
-
+       notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+       notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
+       notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
        notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
        notmuch->query_parser->set_database (*notmuch->xapian_db);
        notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
        notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
-       notmuch->query_parser->add_valuerangeprocessor (notmuch->value_range_processor);
-       notmuch->query_parser->add_valuerangeprocessor (notmuch->date_range_processor);
-       notmuch->query_parser->add_valuerangeprocessor (notmuch->last_mod_range_processor);
+       notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
+       notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
+       notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
 
        for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
            const prefix_t *prefix = &prefix_table[i];
@@ -1067,7 +1075,7 @@ notmuch_database_open_verbose (const char *path,
        status = _setup_user_query_fields (notmuch);
     } catch (const Xapian::Error &error) {
        IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
-                                error.get_msg().c_str()));
+                                error.get_msg ().c_str ()));
        notmuch_database_destroy (notmuch);
        notmuch = NULL;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
@@ -1087,6 +1095,10 @@ notmuch_database_open_verbose (const char *path,
        *database = notmuch;
     else
        talloc_free (notmuch);
+
+    if (notmuch)
+       notmuch->open = true;
+
     return status;
 }
 
@@ -1098,50 +1110,36 @@ notmuch_database_close (notmuch_database_t *notmuch)
     /* Many Xapian objects (and thus notmuch objects) hold references to
      * the database, so merely deleting the database may not suffice to
      * close it.  Thus, we explicitly close it here. */
-    if (notmuch->xapian_db != NULL) {
+    if (notmuch->open) {
        try {
            /* If there's an outstanding transaction, it's unclear if
             * closing the Xapian database commits everything up to
             * that transaction, or may discard committed (but
             * unflushed) transactions.  To be certain, explicitly
             * cancel any outstanding transaction before closing. */
-           if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
+           if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_WRITE &&
                notmuch->atomic_nesting)
-               (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))
-                   ->cancel_transaction ();
+               notmuch->writable_xapian_db->cancel_transaction ();
 
            /* Close the database.  This implicitly flushes
             * outstanding changes. */
-           notmuch->xapian_db->close();
+           notmuch->xapian_db->close ();
        } catch (const Xapian::Error &error) {
            status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
            if (! notmuch->exception_reported) {
                _notmuch_database_log (notmuch, "Error: A Xapian exception occurred closing database: %s\n",
-                        error.get_msg().c_str());
+                                      error.get_msg ().c_str ());
            }
        }
     }
-
-    delete notmuch->term_gen;
-    notmuch->term_gen = NULL;
-    delete notmuch->query_parser;
-    notmuch->query_parser = NULL;
-    delete notmuch->xapian_db;
-    notmuch->xapian_db = NULL;
-    delete notmuch->value_range_processor;
-    notmuch->value_range_processor = NULL;
-    delete notmuch->date_range_processor;
-    notmuch->date_range_processor = NULL;
-    delete notmuch->last_mod_range_processor;
-    notmuch->last_mod_range_processor = NULL;
-
+    notmuch->open = false;
     return status;
 }
 
 notmuch_status_t
 _notmuch_database_reopen (notmuch_database_t *notmuch)
 {
-    if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_ONLY)
+    if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_ONLY)
        return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
 
     try {
@@ -1182,7 +1180,9 @@ class NotmuchCompactor : public Xapian::Compactor
 
 public:
     NotmuchCompactor(notmuch_compact_status_cb_t cb, void *closure) :
-       status_cb (cb), status_closure (closure) { }
+       status_cb (cb), status_closure (closure)
+    {
+    }
 
     virtual void
     set_status (const std::string &table, const std::string &status)
@@ -1193,9 +1193,9 @@ public:
            return;
 
        if (status.length () == 0)
-           msg = talloc_asprintf (NULL, "compacting table %s", table.c_str());
+           msg = talloc_asprintf (NULL, "compacting table %s", table.c_str ());
        else
-           msg = talloc_asprintf (NULL, "     %s", status.c_str());
+           msg = talloc_asprintf (NULL, "     %s", status.c_str ());
 
        if (msg == NULL) {
            return;
@@ -1265,8 +1265,7 @@ notmuch_database_compact (const char *path,
            goto DONE;
        }
        keep_backup = false;
-    }
-    else {
+    } else {
        keep_backup = true;
     }
 
@@ -1277,7 +1276,7 @@ notmuch_database_compact (const char *path,
     }
     if (errno != ENOENT) {
        _notmuch_database_log (notmuch, "Unknown error while stat()ing path: %s\n",
-                strerror (errno));
+                              strerror (errno));
        ret = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
@@ -1290,27 +1289,23 @@ notmuch_database_compact (const char *path,
 
     try {
        NotmuchCompactor compactor (status_cb, closure);
-
-       compactor.set_renumber (false);
-       compactor.add_source (xapian_path);
-       compactor.set_destdir (compact_xapian_path);
-       compactor.compact ();
+       notmuch->xapian_db->compact (compact_xapian_path, Xapian::DBCOMPACT_NO_RENUMBER, 0, compactor);
     } catch (const Xapian::Error &error) {
-       _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg().c_str());
+       _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg ().c_str ());
        ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
        goto DONE;
     }
 
     if (rename (xapian_path, backup_path)) {
        _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
-                xapian_path, backup_path, strerror (errno));
+                              xapian_path, backup_path, strerror (errno));
        ret = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
 
     if (rename (compact_xapian_path, xapian_path)) {
        _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
-                compact_xapian_path, xapian_path, strerror (errno));
+                              compact_xapian_path, xapian_path, strerror (errno));
        ret = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
@@ -1318,7 +1313,7 @@ notmuch_database_compact (const char *path,
     if (! keep_backup) {
        if (rmtree (backup_path)) {
            _notmuch_database_log (notmuch, "Error removing old database %s: %s\n",
-                    backup_path, strerror (errno));
+                                  backup_path, strerror (errno));
            ret = NOTMUCH_STATUS_FILE_ERROR;
            goto DONE;
        }
@@ -1350,6 +1345,20 @@ notmuch_database_destroy (notmuch_database_t *notmuch)
     notmuch_status_t status;
 
     status = notmuch_database_close (notmuch);
+
+    delete notmuch->term_gen;
+    notmuch->term_gen = NULL;
+    delete notmuch->query_parser;
+    notmuch->query_parser = NULL;
+    delete notmuch->xapian_db;
+    notmuch->xapian_db = NULL;
+    delete notmuch->value_range_processor;
+    notmuch->value_range_processor = NULL;
+    delete notmuch->date_range_processor;
+    notmuch->date_range_processor = NULL;
+    delete notmuch->last_mod_range_processor;
+    notmuch->last_mod_range_processor = NULL;
+
     talloc_free (notmuch);
 
     return status;
@@ -1369,7 +1378,13 @@ notmuch_database_get_version (notmuch_database_t *notmuch)
     const char *str;
     char *end;
 
-    version_string = notmuch->xapian_db->get_metadata ("version");
+    try {
+       version_string = notmuch->xapian_db->get_metadata ("version");
+    } catch (const Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (notmuch, error);
+       return 0;
+    }
+
     if (version_string.empty ())
        return 0;
 
@@ -1387,9 +1402,17 @@ notmuch_database_get_version (notmuch_database_t *notmuch)
 notmuch_bool_t
 notmuch_database_needs_upgrade (notmuch_database_t *notmuch)
 {
-    return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
-       ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) ||
-        (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION));
+    unsigned int version;
+
+    if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_WRITE)
+       return FALSE;
+
+    if (NOTMUCH_FEATURES_CURRENT & ~notmuch->features)
+       return TRUE;
+
+    version = notmuch_database_get_version (notmuch);
+
+    return (version > 0 && version < NOTMUCH_DATABASE_VERSION);
 }
 
 static volatile sig_atomic_t do_progress_notify = 0;
@@ -1434,7 +1457,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
     if (status)
        return status;
 
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    db = notmuch->writable_xapian_db;
 
     target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT;
     new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features;
@@ -1510,8 +1533,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
            goto DONE;
        for (;
             notmuch_messages_valid (messages);
-            notmuch_messages_move_to_next (messages))
-       {
+            notmuch_messages_move_to_next (messages)) {
            if (do_progress_notify) {
                progress_notify (closure, (double) count / total);
                do_progress_notify = 0;
@@ -1568,8 +1590,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 
        for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP");
             t != t_end;
-            t++)
-       {
+            t++) {
            Xapian::PostingIterator p, p_end;
            std::string term = *t;
 
@@ -1577,8 +1598,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 
            for (p = notmuch->xapian_db->postlist_begin (term);
                 p != p_end;
-                p++)
-           {
+                p++) {
                Xapian::Document document;
                time_t mtime;
                notmuch_directory_t *directory;
@@ -1592,8 +1612,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
                mtime = Xapian::sortable_unserialise (
                    document.get_value (NOTMUCH_VALUE_TIMESTAMP));
 
-               directory = _notmuch_directory_create (notmuch, term.c_str() + 10,
-                                                      NOTMUCH_FIND_CREATE, &status);
+               directory = _notmuch_directory_find_or_create (notmuch, term.c_str () + 10,
+                                                              NOTMUCH_FIND_CREATE, &status);
                notmuch_directory_set_mtime (directory, mtime);
                notmuch_directory_destroy (directory);
 
@@ -1641,7 +1661,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 
            if (private_status) {
                _notmuch_database_log (notmuch,
-                        "Upgrade failed while creating ghost messages.\n");
+                                      "Upgrade failed while creating ghost messages.\n");
                status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost");
                goto DONE;
            }
@@ -1657,7 +1677,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
     db->set_metadata ("features", _print_features (local, notmuch->features));
     db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
 
- DONE:
 DONE:
     if (status == NOTMUCH_STATUS_SUCCESS)
        db->commit_transaction ();
     else
@@ -1686,7 +1706,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 notmuch_status_t
 notmuch_database_begin_atomic (notmuch_database_t *notmuch)
 {
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+    if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY ||
        notmuch->atomic_nesting > 0)
        goto DONE;
 
@@ -1694,15 +1714,15 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch)
        return NOTMUCH_STATUS_UPGRADE_REQUIRED;
 
     try {
-       (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->begin_transaction (false);
+       notmuch->writable_xapian_db->begin_transaction (false);
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
-DONE:
+  DONE:
     notmuch->atomic_nesting++;
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -1715,11 +1735,11 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
     if (notmuch->atomic_nesting == 0)
        return NOTMUCH_STATUS_UNBALANCED_ATOMIC;
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+    if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY ||
        notmuch->atomic_nesting > 1)
        goto DONE;
 
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    db = notmuch->writable_xapian_db;
     try {
        db->commit_transaction ();
 
@@ -1731,7 +1751,7 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
            db->commit ();
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
@@ -1741,14 +1761,14 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
        notmuch->atomic_dirty = false;
     }
 
-DONE:
+  DONE:
     notmuch->atomic_nesting--;
     return NOTMUCH_STATUS_SUCCESS;
 }
 
 unsigned long
 notmuch_database_get_revision (notmuch_database_t *notmuch,
-                               const char **uuid)
+                              const char **uuid)
 {
     if (uuid)
        *uuid = notmuch->uuid;
@@ -1865,8 +1885,8 @@ _notmuch_database_find_directory_id (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_SUCCESS;
     }
 
-    directory = _notmuch_directory_create (notmuch, path, flags, &status);
-    if (status || !directory) {
+    directory = _notmuch_directory_find_or_create (notmuch, path, flags, &status);
+    if (status || ! directory) {
        *directory_id = -1;
        return status;
     }
@@ -1920,7 +1940,7 @@ _notmuch_database_filename_to_direntry (void *ctx,
 
     status = _notmuch_database_find_directory_id (notmuch, directory, flags,
                                                  &directory_id);
-    if (status || directory_id == (unsigned int)-1) {
+    if (status || directory_id == (unsigned int) -1) {
        *direntry = NULL;
        return status;
     }
@@ -1950,11 +1970,10 @@ _notmuch_database_relative_path (notmuch_database_t *notmuch,
     relative = path;
 
     if (*relative == '/') {
-       while (*relative == '/' && *(relative+1) == '/')
+       while (*relative == '/' && *(relative + 1) == '/')
            relative++;
 
-       if (strncmp (relative, db_path, db_path_len) == 0)
-       {
+       if (strncmp (relative, db_path, db_path_len) == 0) {
            relative += db_path_len;
            while (*relative == '/')
                relative++;
@@ -1976,11 +1995,11 @@ notmuch_database_get_directory (notmuch_database_t *notmuch,
     *directory = NULL;
 
     try {
-       *directory = _notmuch_directory_create (notmuch, path,
-                                               NOTMUCH_FIND_LOOKUP, &status);
+       *directory = _notmuch_directory_find_or_create (notmuch, path,
+                                                       NOTMUCH_FIND_LOOKUP, &status);
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
@@ -2023,13 +2042,13 @@ notmuch_database_remove_message (notmuch_database_t *notmuch,
                                                        &message);
 
     if (status == NOTMUCH_STATUS_SUCCESS && message) {
-           status = _notmuch_message_remove_filename (message, filename);
-           if (status == NOTMUCH_STATUS_SUCCESS)
-               _notmuch_message_delete (message);
-           else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
-               _notmuch_message_sync (message);
+       status = _notmuch_message_remove_filename (message, filename);
+       if (status == NOTMUCH_STATUS_SUCCESS)
+           _notmuch_message_delete (message);
+       else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
+           _notmuch_message_sync (message);
 
-           notmuch_message_destroy (message);
+       notmuch_message_destroy (message);
     }
 
     return status;
@@ -2060,7 +2079,7 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
     try {
        status = _notmuch_database_filename_to_direntry (
            local, notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
-       if (status || !direntry)
+       if (status || ! direntry)
            goto DONE;
 
        term = talloc_asprintf (local, "%s%s", prefix, direntry);
@@ -2077,7 +2096,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());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
@@ -2122,15 +2141,15 @@ notmuch_database_get_all_tags (notmuch_database_t *db)
     notmuch_string_list_t *tags;
 
     try {
-       i = db->xapian_db->allterms_begin();
-       end = db->xapian_db->allterms_end();
+       i = db->xapian_db->allterms_begin ();
+       end = db->xapian_db->allterms_end ();
        tags = _notmuch_database_get_terms_with_prefix (db, i, end,
                                                        _find_prefix ("tag"));
        _notmuch_string_list_sort (tags);
        return _notmuch_tags_create (db, tags);
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (db, "A Xapian exception occurred getting tags: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        db->exception_reported = true;
        return NULL;
     }
index 4fcb017712f6c9c505a35326dc807f84b3e09d6d..eee8254e7a842e18cce933907ccddeac19356474 100644 (file)
@@ -32,8 +32,8 @@ _create_filenames_for_terms_with_prefix (void *ctx,
     notmuch_string_list_t *filename_list;
     Xapian::TermIterator i, end;
 
-    i = notmuch->xapian_db->allterms_begin();
-    end = notmuch->xapian_db->allterms_end();
+    i = notmuch->xapian_db->allterms_begin ();
+    end = notmuch->xapian_db->allterms_end ();
     filename_list = _notmuch_database_get_terms_with_prefix (ctx, i, end,
                                                             prefix);
     if (unlikely (filename_list == NULL))
@@ -49,6 +49,19 @@ struct _notmuch_directory {
     time_t mtime;
 };
 
+#define LOG_XAPIAN_EXCEPTION(directory, error) _log_xapian_exception (__location__, directory, error)
+
+static void
+_log_xapian_exception (const char *where, notmuch_directory_t *dir,  const Xapian::Error error) {
+    notmuch_database_t *notmuch = dir->notmuch;
+    _notmuch_database_log (notmuch,
+                          "A Xapian exception occurred at %s: %s\n",
+                          where,
+                          error.get_msg ().c_str ());
+    notmuch->exception_reported = true;
+}
+
+
 /* We end up having to call the destructor explicitly because we had
  * to use "placement new" in order to initialize C++ objects within a
  * block that we allocated with talloc. So C++ is making talloc
@@ -94,12 +107,11 @@ find_directory_document (notmuch_database_t *notmuch,
  * NOTMUCH_STATUS_SUCCESS and this returns NULL.
  */
 notmuch_directory_t *
-_notmuch_directory_create (notmuch_database_t *notmuch,
-                          const char *path,
-                          notmuch_find_flags_t flags,
-                          notmuch_status_t *status_ret)
+_notmuch_directory_find_or_create (notmuch_database_t *notmuch,
+                                  const char *path,
+                                  notmuch_find_flags_t flags,
+                                  notmuch_status_t *status_ret)
 {
-    Xapian::WritableDatabase *db;
     notmuch_directory_t *directory;
     notmuch_private_status_t private_status;
     const char *db_path;
@@ -114,7 +126,7 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
 
     path = _notmuch_database_relative_path (notmuch, path);
 
-    if (create && notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+    if (create && _notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY)
        INTERNAL_ERROR ("Failure to ensure database is writable");
 
     directory = talloc (notmuch, notmuch_directory_t);
@@ -140,7 +152,7 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
        directory->document_id = directory->doc.get_docid ();
 
        if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
-           if (!create) {
+           if (! create) {
                notmuch_directory_destroy (directory);
                directory = NULL;
                *status_ret = NOTMUCH_STATUS_SUCCESS;
@@ -176,10 +188,10 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
            directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
                                      Xapian::sortable_serialise (0));
 
-           db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
            directory->document_id = _notmuch_database_generate_doc_id (notmuch);
-           db->replace_document (directory->document_id, directory->doc);
+           directory->notmuch->
+               writable_xapian_db
+               -> replace_document (directory->document_id, directory->doc);
            talloc_free (local);
        }
 
@@ -187,8 +199,8 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
            directory->doc.get_value (NOTMUCH_VALUE_TIMESTAMP));
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch,
-                "A Xapian exception occurred creating a directory: %s.\n",
-                error.get_msg().c_str());
+                              "A Xapian exception occurred finding/creating a directory: %s.\n",
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        notmuch_directory_destroy (directory);
        directory = NULL;
@@ -213,27 +225,25 @@ notmuch_directory_set_mtime (notmuch_directory_t *directory,
                             time_t mtime)
 {
     notmuch_database_t *notmuch = directory->notmuch;
-    Xapian::WritableDatabase *db;
     notmuch_status_t status;
 
     status = _notmuch_database_ensure_writable (notmuch);
     if (status)
        return status;
 
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
     try {
        directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
-                                  Xapian::sortable_serialise (mtime));
+                                 Xapian::sortable_serialise (mtime));
 
-       db->replace_document (directory->document_id, directory->doc);
+       directory->notmuch
+           ->writable_xapian_db->replace_document (directory->document_id, directory->doc);
 
        directory->mtime = mtime;
 
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch,
-                "A Xapian exception occurred setting directory mtime: %s.\n",
-                error.get_msg().c_str());
+                              "A Xapian exception occurred setting directory mtime: %s.\n",
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
@@ -251,15 +261,19 @@ notmuch_filenames_t *
 notmuch_directory_get_child_files (notmuch_directory_t *directory)
 {
     char *term;
-    notmuch_filenames_t *child_files;
+    notmuch_filenames_t *child_files = NULL;
 
     term = talloc_asprintf (directory, "%s%u:",
                            _find_prefix ("file-direntry"),
                            directory->document_id);
 
-    child_files = _create_filenames_for_terms_with_prefix (directory,
-                                                          directory->notmuch,
-                                                          term);
+    try {
+       child_files = _create_filenames_for_terms_with_prefix (directory,
+                                                              directory->notmuch,
+                                                              term);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (directory, error);
+    }
 
     talloc_free (term);
 
@@ -270,14 +284,18 @@ notmuch_filenames_t *
 notmuch_directory_get_child_directories (notmuch_directory_t *directory)
 {
     char *term;
-    notmuch_filenames_t *child_directories;
+    notmuch_filenames_t *child_directories = NULL;
 
     term = talloc_asprintf (directory, "%s%u:",
                            _find_prefix ("directory-direntry"),
                            directory->document_id);
 
-    child_directories = _create_filenames_for_terms_with_prefix (directory,
-                                                directory->notmuch, term);
+    try {
+       child_directories = _create_filenames_for_terms_with_prefix (directory,
+                                                                    directory->notmuch, term);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (directory, error);
+    }
 
     talloc_free (term);
 
@@ -288,25 +306,24 @@ notmuch_status_t
 notmuch_directory_delete (notmuch_directory_t *directory)
 {
     notmuch_status_t status;
-    Xapian::WritableDatabase *db;
 
     status = _notmuch_database_ensure_writable (directory->notmuch);
     if (status)
        return status;
 
     try {
-       db = static_cast <Xapian::WritableDatabase *> (directory->notmuch->xapian_db);
-       db->delete_document (directory->document_id);
+       directory->notmuch->
+           writable_xapian_db->delete_document (directory->document_id);
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (directory->notmuch,
                               "A Xapian exception occurred deleting directory entry: %s.\n",
-                              error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        directory->notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
     notmuch_directory_destroy (directory);
 
-    return NOTMUCH_STATUS_SUCCESS;
+    return status;
 }
 
 void
index 1fd9e67ee96340b903773bfda59a2410b244a7a1..826aa341d678b2345962cb944becee6b8b50f202 100644 (file)
@@ -43,46 +43,46 @@ typedef struct {
  * which we discard data. */
 static const int first_uuencode_skipping_state = 11;
 static const scanner_state_t uuencode_states[] = {
-    {0,  'b',  'b',  1,  0},
-    {1,  'e',  'e',  2,  0},
-    {2,  'g',  'g',  3,  0},
-    {3,  'i',  'i',  4,  0},
-    {4,  'n',  'n',  5,  0},
-    {5,  ' ',  ' ',  6,  0},
-    {6,  '0',  '7',  7,  0},
-    {7,  '0',  '7',  8,  0},
-    {8,  '0',  '7',  9,  0},
-    {9,  ' ',  ' ',  10, 0},
-    {10, '\n', '\n', 11, 10},
-    {11, 'M',  'M',  12, 0},
-    {12, ' ',  '`',  12, 11}
+    { 0,  'b',  'b',  1,  0 },
+    { 1,  'e',  'e',  2,  0 },
+    { 2,  'g',  'g',  3,  0 },
+    { 3,  'i',  'i',  4,  0 },
+    { 4,  'n',  'n',  5,  0 },
+    { 5,  ' ',  ' ',  6,  0 },
+    { 6,  '0',  '7',  7,  0 },
+    { 7,  '0',  '7',  8,  0 },
+    { 8,  '0',  '7',  9,  0 },
+    { 9,  ' ',  ' ',  10, 0 },
+    { 10, '\n', '\n', 11, 10 },
+    { 11, 'M',  'M',  12, 0 },
+    { 12, ' ',  '`',  12, 11 }
 };
 
 /* The following table is intended to implement this DFA (in 'dot'
  format). Note that 2 and 3 are "hidden" states used to step through
  the possible out edges of state 1.
-
-digraph html_filter {
      0 -> 1  [label="<"];
      0 -> 0;
      1 -> 4 [label="'"];
      1 -> 5 [label="\""];
      1 -> 0 [label=">"];
      1 -> 1;
      4 -> 1 [label="'"];
      4 -> 4;
      5 -> 1 [label="\""];
      5 -> 5;
-}
-*/
* format). Note that 2 and 3 are "hidden" states used to step through
* the possible out edges of state 1.
+ *
+ * digraph html_filter {
*     0 -> 1  [label="<"];
*     0 -> 0;
*     1 -> 4 [label="'"];
*     1 -> 5 [label="\""];
*     1 -> 0 [label=">"];
*     1 -> 1;
*     4 -> 1 [label="'"];
*     4 -> 4;
*     5 -> 1 [label="\""];
*     5 -> 5;
+ * }
+ */
 static const int first_html_skipping_state = 1;
 static const scanner_state_t html_states[] = {
-    {0,  '<',  '<',  1,  0},
-    {1,  '\'', '\'', 4,  2},  /* scanning for quote or > */
-    {1,  '"',  '"',  5,  3},
-    {1,  '>',  '>',  0,  1},
-    {4,  '\'', '\'', 1,  4},  /* inside single quotes */
-    {5,  '"', '"',   1,  5},  /* inside double quotes */
+    { 0,  '<',  '<',  1,  0 },
+    { 1,  '\'', '\'', 4,  2 },  /* scanning for quote or > */
+    { 1,  '"',  '"',  5,  3 },
+    { 1,  '>',  '>',  0,  1 },
+    { 4,  '\'', '\'', 1,  4 },  /* inside single quotes */
+    { 5,  '"', '"',   1,  5 },  /* inside double quotes */
 };
 
 /* Oh, how I wish that gobject didn't require so much noisy boilerplate!
@@ -168,6 +168,7 @@ static GMimeFilter *
 filter_copy (GMimeFilter *gmime_filter)
 {
     NotmuchFilterDiscardNonTerm *filter = (NotmuchFilterDiscardNonTerm *) gmime_filter;
+
     return notmuch_filter_discard_non_term_new (filter->content_type);
 }
 
@@ -190,7 +191,7 @@ filter_filter (GMimeFilter *gmime_filter, char *inbuf, size_t inlen, size_t pres
 
     next = filter->state;
     while (inptr < inend) {
-        /* Each state is defined by a contiguous set of rows of the
+       /* Each state is defined by a contiguous set of rows of the
         * state table marked by a common value for '.state'. The
         * state numbers must be equal to the index of the first row
         * in a given state; thus the loop condition here looks for a
@@ -198,9 +199,9 @@ filter_filter (GMimeFilter *gmime_filter, char *inbuf, size_t inlen, size_t pres
         * in the underlying DFA.
         */
        do {
-           if (*inptr >= states[next].a && *inptr <= states[next].b)  {
+           if (*inptr >= states[next].a && *inptr <= states[next].b) {
                next = states[next].next_if_match;
-           } else  {
+           } else {
                next = states[next].next_if_not_match;
            }
 
@@ -245,7 +246,7 @@ notmuch_filter_discard_non_term_new (GMimeContentType *content_type)
     static GType type = 0;
     NotmuchFilterDiscardNonTerm *filter;
 
-    if (!type) {
+    if (! type) {
        static const GTypeInfo info = {
            .class_size = sizeof (NotmuchFilterDiscardNonTermClass),
            .base_init = NULL,
@@ -266,11 +267,11 @@ notmuch_filter_discard_non_term_new (GMimeContentType *content_type)
     filter->content_type = content_type;
     filter->state = 0;
     if (g_mime_content_type_is_type (content_type, "text", "html")) {
-      filter->states = html_states;
-      filter->first_skipping_state = first_html_skipping_state;
+       filter->states = html_states;
+       filter->first_skipping_state = first_html_skipping_state;
     } else {
-      filter->states = uuencode_states;
-      filter->first_skipping_state = first_uuencode_skipping_state;
+       filter->states = uuencode_states;
+       filter->first_skipping_state = first_uuencode_skipping_state;
     }
 
     return (GMimeFilter *) filter;
@@ -356,6 +357,7 @@ 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_get_mime_type (content_type);
        if (mime_string) {
@@ -367,9 +369,15 @@ _index_content_type (notmuch_message_t *message, GMimeObject *part)
 
 static void
 _index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts,
-                           GMimeMultipartEncrypted *part,
+                           GMimeObject *part,
                            _notmuch_message_crypto_t *msg_crypto);
 
+static void
+_index_pkcs7_part (notmuch_message_t *message,
+                  notmuch_indexopts_t *indexopts,
+                  GMimeObject *part,
+                  _notmuch_message_crypto_t *msg_crypto);
+
 /* Callback to generate terms for each mime part of a message. */
 static void
 _index_mime_part (notmuch_message_t *message,
@@ -385,11 +393,20 @@ _index_mime_part (notmuch_message_t *message,
     GMimeContentType *content_type;
     char *body;
     const char *charset;
+    GMimeObject *repaired_part = NULL;
 
     if (! part) {
        _notmuch_database_log (notmuch_message_get_database (message),
-                             "Warning: Not indexing empty mime part.\n");
-       return;
+                              "Warning: Not indexing empty mime part.\n");
+       goto DONE;
+    }
+
+    repaired_part = _notmuch_repair_mixed_up_mangled (part);
+    if (repaired_part) {
+       /* This was likely "Mixed Up" in transit!  We will instead use
+        * the more likely-to-be-correct variant. */
+       notmuch_message_add_property (message, "index.repaired", "mixedup");
+       part = repaired_part;
     }
 
     _index_content_type (message, part);
@@ -399,13 +416,12 @@ _index_mime_part (notmuch_message_t *message,
        int i;
 
        if (GMIME_IS_MULTIPART_SIGNED (multipart))
-         _notmuch_message_add_term (message, "tag", "signed");
+           _notmuch_message_add_term (message, "tag", "signed");
 
        if (GMIME_IS_MULTIPART_ENCRYPTED (multipart))
-         _notmuch_message_add_term (message, "tag", "encrypted");
+           _notmuch_message_add_term (message, "tag", "encrypted");
 
        for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
-           notmuch_status_t status;
            GMimeObject *child;
            if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
                /* Don't index the signature, but index its content type. */
@@ -422,9 +438,9 @@ _index_mime_part (notmuch_message_t *message,
                _index_content_type (message,
                                     g_mime_multipart_get_part (multipart, i));
                if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) {
-                   _index_encrypted_mime_part(message, indexopts,
-                                              GMIME_MULTIPART_ENCRYPTED (part),
-                                              msg_crypto);
+                   _index_encrypted_mime_part (message, indexopts,
+                                               part,
+                                               msg_crypto);
                } else {
                    if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) {
                        _notmuch_database_log (notmuch_message_get_database (message),
@@ -434,14 +450,16 @@ _index_mime_part (notmuch_message_t *message,
                continue;
            }
            child = g_mime_multipart_get_part (multipart, i);
-           status = _notmuch_message_crypto_potential_payload (msg_crypto, child, part, i);
-           if (status)
-               _notmuch_database_log (notmuch_message_get_database (message),
-                                      "Warning: failed to mark the potential cryptographic payload (%s).\n",
-                                      notmuch_status_to_string (status));
-           _index_mime_part (message, indexopts, child, msg_crypto);
+           GMimeObject *toindex = child;
+           if (_notmuch_message_crypto_potential_payload (msg_crypto, child, part, i) &&
+               msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
+               toindex = _notmuch_repair_crypto_payload_skip_legacy_display (child);
+               if (toindex != child)
+                   notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display");
+           }
+           _index_mime_part (message, indexopts, toindex, msg_crypto);
        }
-       return;
+       goto DONE;
     }
 
     if (GMIME_IS_MESSAGE_PART (part)) {
@@ -451,21 +469,25 @@ _index_mime_part (notmuch_message_t *message,
 
        _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message), msg_crypto);
 
-       return;
+       goto DONE;
+    }
+
+    if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) {
+       _index_pkcs7_part (message, indexopts, part, msg_crypto);
+       goto DONE;
     }
 
     if (! (GMIME_IS_PART (part))) {
        _notmuch_database_log (notmuch_message_get_database (message),
-                             "Warning: Not indexing unknown mime part: %s.\n",
-                             g_type_name (G_OBJECT_TYPE (part)));
-       return;
+                              "Warning: Not indexing unknown mime part: %s.\n",
+                              g_type_name (G_OBJECT_TYPE (part)));
+       goto DONE;
     }
 
     disposition = g_mime_object_get_content_disposition (part);
     if (disposition &&
        strcasecmp (g_mime_content_disposition_get_disposition (disposition),
-                   GMIME_DISPOSITION_ATTACHMENT) == 0)
-    {
+                   GMIME_DISPOSITION_ATTACHMENT) == 0) {
        const char *filename = g_mime_part_get_filename (GMIME_PART (part));
 
        _notmuch_message_add_term (message, "tag", "attachment");
@@ -473,7 +495,7 @@ _index_mime_part (notmuch_message_t *message,
 
        /* XXX: Would be nice to call out to something here to parse
         * the attachment into text and then index that. */
-       return;
+       goto DONE;
     }
 
     byte_array = g_byte_array_new ();
@@ -519,6 +541,9 @@ _index_mime_part (notmuch_message_t *message,
 
        free (body);
     }
+  DONE:
+    if (repaired_part)
+       g_object_unref (repaired_part);
 }
 
 /* descend (if desired) into the cleartext part of an encrypted MIME
@@ -526,15 +551,15 @@ _index_mime_part (notmuch_message_t *message,
 static void
 _index_encrypted_mime_part (notmuch_message_t *message,
                            notmuch_indexopts_t *indexopts,
-                           GMimeMultipartEncrypted *encrypted_data,
+                           GMimeObject *encrypted_data,
                            _notmuch_message_crypto_t *msg_crypto)
 {
     notmuch_status_t status;
     GError *err = NULL;
-    notmuch_database_t * notmuch = NULL;
+    notmuch_database_t *notmuch = NULL;
     GMimeObject *clear = NULL;
 
-    if (!indexopts || (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_FALSE))
+    if (! indexopts || (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_FALSE))
        return;
 
     notmuch = notmuch_message_get_database (message);
@@ -544,15 +569,15 @@ _index_encrypted_mime_part (notmuch_message_t *message,
     bool get_sk = (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_TRUE);
     clear = _notmuch_crypto_decrypt (&attempted, notmuch_indexopts_get_decrypt_policy (indexopts),
                                     message, encrypted_data, get_sk ? &decrypt_result : NULL, &err);
-    if (!attempted)
+    if (! attempted)
        return;
-    if (err || !clear) {
+    if (err || ! clear) {
        if (decrypt_result)
            g_object_unref (decrypt_result);
        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);
+           g_error_free (err);
        } else {
            _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (unknown error)\n");
        }
@@ -577,8 +602,14 @@ _index_encrypted_mime_part (notmuch_message_t *message,
        }
        g_object_unref (decrypt_result);
     }
-    status = _notmuch_message_crypto_potential_payload (msg_crypto, clear, GMIME_OBJECT (encrypted_data), GMIME_MULTIPART_ENCRYPTED_CONTENT);
-    _index_mime_part (message, indexopts, clear, msg_crypto);
+    GMimeObject *toindex = clear;
+    if (_notmuch_message_crypto_potential_payload (msg_crypto, clear, encrypted_data, GMIME_MULTIPART_ENCRYPTED_CONTENT) &&
+       msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
+       toindex = _notmuch_repair_crypto_payload_skip_legacy_display (clear);
+       if (toindex != clear)
+           notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display");
+    }
+    _index_mime_part (message, indexopts, toindex, msg_crypto);
     g_object_unref (clear);
 
     status = notmuch_message_add_property (message, "index.decryption", "success");
@@ -588,6 +619,57 @@ _index_encrypted_mime_part (notmuch_message_t *message,
 
 }
 
+static void
+_index_pkcs7_part (notmuch_message_t *message,
+                  notmuch_indexopts_t *indexopts,
+                  GMimeObject *part,
+                  _notmuch_message_crypto_t *msg_crypto)
+{
+    GMimeApplicationPkcs7Mime *pkcs7;
+    GMimeSecureMimeType p7type;
+    GMimeObject *mimeobj = NULL;
+    GMimeSignatureList *sigs = NULL;
+    GError *err = NULL;
+    notmuch_database_t *notmuch = NULL;
+
+    pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part);
+    p7type = g_mime_application_pkcs7_mime_get_smime_type (pkcs7);
+    notmuch = notmuch_message_get_database (message);
+    _index_content_type (message, part);
+
+    if (p7type == GMIME_SECURE_MIME_TYPE_SIGNED_DATA) {
+       sigs = g_mime_application_pkcs7_mime_verify (pkcs7, GMIME_VERIFY_NONE, &mimeobj, &err);
+       if (sigs == NULL) {
+           _notmuch_database_log (notmuch, "Failed to verify PKCS#7 SignedData during indexing. (%d:%d) [%s]\n",
+                                  err->domain, err->code, err->message);
+           g_error_free (err);
+           goto DONE;
+       }
+       _notmuch_message_add_term (message, "tag", "signed");
+       GMimeObject *toindex = mimeobj;
+       if (_notmuch_message_crypto_potential_payload (msg_crypto, mimeobj, part, 0) &&
+           msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
+           toindex = _notmuch_repair_crypto_payload_skip_legacy_display (mimeobj);
+           if (toindex != mimeobj)
+               notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display");
+       }
+       _index_mime_part (message, indexopts, toindex, msg_crypto);
+    } else if (p7type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) {
+       _notmuch_message_add_term (message, "tag", "encrypted");
+       _index_encrypted_mime_part (message, indexopts,
+                                   part,
+                                   msg_crypto);
+    } else {
+       _notmuch_database_log (notmuch, "Cannot currently handle PKCS#7 smime-type '%s'\n",
+                              g_mime_object_get_content_type_parameter (part, "smime-type"));
+    }
+ DONE:
+    if (mimeobj)
+       g_object_unref (mimeobj);
+    if (sigs)
+       g_object_unref (sigs);
+}
+
 static notmuch_status_t
 _notmuch_message_index_user_headers (notmuch_message_t *message, GMimeMessage *mime_message)
 {
index b78a57b6840996d524e1cdcc9fade0609876f5d2..82a0026f0d46b47d46e94407d2271df978bd90db 100644 (file)
@@ -24,25 +24,26 @@ notmuch_indexopts_t *
 notmuch_database_get_default_indexopts (notmuch_database_t *db)
 {
     notmuch_indexopts_t *ret = talloc_zero (db, notmuch_indexopts_t);
-    if (!ret)
+
+    if (! ret)
        return ret;
     ret->crypto.decrypt = NOTMUCH_DECRYPT_AUTO;
 
-    char * decrypt_policy;
+    char *decrypt_policy;
     notmuch_status_t err = notmuch_database_get_config (db, "index.decrypt", &decrypt_policy);
     if (err)
-       return ret;
+       return NULL;
 
     if (decrypt_policy) {
-       if ((!(strcasecmp(decrypt_policy, "true"))) ||
-           (!(strcasecmp(decrypt_policy, "yes"))) ||
-           (!(strcasecmp(decrypt_policy, "1"))))
+       if ((! (strcasecmp (decrypt_policy, "true"))) ||
+           (! (strcasecmp (decrypt_policy, "yes"))) ||
+           (! (strcasecmp (decrypt_policy, "1"))))
            notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_TRUE);
-       else if ((!(strcasecmp(decrypt_policy, "false"))) ||
-                (!(strcasecmp(decrypt_policy, "no"))) ||
-                (!(strcasecmp(decrypt_policy, "0"))))
+       else if ((! (strcasecmp (decrypt_policy, "false"))) ||
+                (! (strcasecmp (decrypt_policy, "no"))) ||
+                (! (strcasecmp (decrypt_policy, "0"))))
            notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_FALSE);
-       else if (!strcasecmp(decrypt_policy, "nostash"))
+       else if (! strcasecmp (decrypt_policy, "nostash"))
            notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_NOSTASH);
     }
 
@@ -54,7 +55,7 @@ notmuch_status_t
 notmuch_indexopts_set_decrypt_policy (notmuch_indexopts_t *indexopts,
                                      notmuch_decryption_policy_t decrypt_policy)
 {
-    if (!indexopts)
+    if (! indexopts)
        return NOTMUCH_STATUS_NULL_POINTER;
     indexopts->crypto.decrypt = decrypt_policy;
     return NOTMUCH_STATUS_SUCCESS;
@@ -63,7 +64,7 @@ notmuch_indexopts_set_decrypt_policy (notmuch_indexopts_t *indexopts,
 notmuch_decryption_policy_t
 notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts)
 {
-    if (!indexopts)
+    if (! indexopts)
        return false;
     return indexopts->crypto.decrypt;
 }
index 24c5fda43b1338fb6f5c1c45a2d79e3c51005a98..311bd478b0cfff13620ba653795ccb9cd62246bf 100644 (file)
@@ -64,21 +64,37 @@ _notmuch_message_file_open_ctx (notmuch_database_t *notmuch,
     if (unlikely (message == NULL))
        return NULL;
 
-    message->filename = talloc_strdup (message, filename);
+    const char *prefix = notmuch_database_get_path (notmuch);
+    if (prefix == NULL)
+       goto FAIL;
+
+    if (*filename == '/') {
+       if (strncmp (filename, prefix, strlen(prefix)) != 0) {
+           _notmuch_database_log (notmuch, "Error opening %s: path outside mail root\n",
+                                  filename);
+           errno = 0;
+           goto FAIL;
+       }
+       message->filename = talloc_strdup (message, filename);
+    } else {
+       message->filename = talloc_asprintf(message, "%s/%s", prefix, filename);
+    }
+
     if (message->filename == NULL)
        goto FAIL;
 
     talloc_set_destructor (message, _notmuch_message_file_destructor);
 
-    message->stream = g_mime_stream_gzfile_open (filename);
+    message->stream = g_mime_stream_gzfile_open (message->filename);
     if (message->stream == NULL)
        goto FAIL;
 
     return message;
 
   FAIL:
-    _notmuch_database_log (notmuch, "Error opening %s: %s\n",
-                         filename, strerror (errno));
+    if (errno)
+       _notmuch_database_log (notmuch, "Error opening %s: %s\n",
+                              filename, strerror (errno));
     _notmuch_message_file_close (message);
 
     return NULL;
@@ -110,7 +126,7 @@ _is_mbox (GMimeStream *stream)
     bool ret = false;
 
     /* Is this mbox? */
-    if (g_mime_stream_read (stream, from_buf, sizeof (from_buf)) == sizeof(from_buf) &&
+    if (g_mime_stream_read (stream, from_buf, sizeof (from_buf)) == sizeof (from_buf) &&
        strncmp (from_buf, "From ", 5) == 0)
        ret = true;
 
@@ -201,7 +217,8 @@ _notmuch_message_file_get_mime_message (notmuch_message_file_t *message,
  */
 
 static char *
-_extend_header (char *combined, const char *value) {
+_extend_header (char *combined, const char *value)
+{
     char *decoded;
 
     decoded = g_mime_utils_header_decode_text (NULL, value);
@@ -226,7 +243,7 @@ _extend_header (char *combined, const char *value) {
     } else {
        combined = decoded;
     }
- DONE:
 DONE:
     return combined;
 }
 
@@ -242,7 +259,7 @@ _notmuch_message_file_get_combined_header (notmuch_message_file_t *message,
        return NULL;
 
 
-    for (int i=0; i < g_mime_header_list_get_count (headers); i++) {
+    for (int i = 0; i < g_mime_header_list_get_count (headers); i++) {
        const char *value;
        GMimeHeader *g_header = g_mime_header_list_get_header_at (headers, i);
 
@@ -264,7 +281,7 @@ _notmuch_message_file_get_combined_header (notmuch_message_file_t *message,
 
 const char *
 _notmuch_message_file_get_header (notmuch_message_file_t *message,
-                                const char *header)
+                                 const char *header)
 {
     const char *value;
     char *decoded;
@@ -366,7 +383,7 @@ _notmuch_message_file_get_headers (notmuch_message_file_t *message_file,
        message_id = talloc_asprintf (message_file, "notmuch-sha1-%s", sha1);
        free (sha1);
     }
- DONE:
 DONE:
     if (ret == NOTMUCH_STATUS_SUCCESS) {
        if (from_out)
            *from_out = from;
index e71ce9f4674465c13f06eee32c4266d8821b3a26..540123548248010eb329c96fa34b3db1e94ae477 100644 (file)
@@ -27,7 +27,7 @@ skip_space_and_comments (const char **str)
                } else if (*s == ')') {
                    nesting--;
                } else if (*s == '\\') {
-                   if (*(s+1))
+                   if (*(s + 1))
                        s++;
                }
                s++;
@@ -90,7 +90,7 @@ _notmuch_message_id_parse (void *ctx, const char *message_id, const char **next)
 
        for (r = result, len = strlen (r); *r; r++, len--)
            if (*r == ' ' || *r == '\t')
-               memmove (r, r+1, len);
+               memmove (r, r + 1, len);
     }
 
     return result;
index 710ba0460665c6e506a2efe24b2a12c242321980..ecf7e140108d08b07e43afeb78c636d6085de46f 100644 (file)
@@ -115,7 +115,7 @@ notmuch_status_t
 _notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key, bool prefix)
 {
     notmuch_status_t status;
-    const char * term_prefix;
+    const char *term_prefix;
 
     status = _notmuch_database_ensure_writable (notmuch_message_get_database (message));
     if (status)
@@ -150,6 +150,7 @@ notmuch_message_properties_t *
 notmuch_message_get_properties (notmuch_message_t *message, const char *key, notmuch_bool_t exact)
 {
     notmuch_string_map_t *map;
+
     map = _notmuch_message_property_map (message);
     return _notmuch_string_map_iterator_create (map, key, exact);
 }
index 9e1005a3058c8330334525943d03e8e7bee896a5..fca99082a8483cba035fba37f9a1bc1ebaab0b4e 100644 (file)
@@ -69,10 +69,10 @@ struct maildir_flag_tag {
 
 /* 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},
+    { 'D', "draft",   false },
+    { 'F', "flagged", false },
+    { 'P', "passed",  false },
+    { 'R', "replied", false },
     { 'S', "unread",  true }
 };
 
@@ -90,6 +90,18 @@ _notmuch_message_destructor (notmuch_message_t *message)
     return 0;
 }
 
+#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
+
+static void
+_log_xapian_exception (const char *where, notmuch_message_t *message,  const Xapian::Error error) {
+    notmuch_database_t *notmuch = notmuch_message_get_database (message);
+    _notmuch_database_log (notmuch,
+                          "A Xapian exception occurred at %s: %s\n",
+                          where,
+                          error.get_msg ().c_str ());
+    notmuch->exception_reported = true;
+}
+
 static notmuch_message_t *
 _notmuch_message_create_for_document (const void *talloc_owner,
                                      notmuch_database_t *notmuch,
@@ -263,7 +275,7 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
        return NULL;
     }
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+    if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY)
        INTERNAL_ERROR ("Failure to ensure database is writable.");
 
     try {
@@ -274,8 +286,8 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
 
        doc_id = _notmuch_database_generate_doc_id (notmuch);
     } catch (const Xapian::Error &error) {
-       _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n",
-                error.get_msg().c_str());
+       _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n",
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
        return NULL;
@@ -306,10 +318,10 @@ _notmuch_message_get_term (notmuch_message_t *message,
        return NULL;
 
     const std::string &term = *i;
-    if (strncmp (term.c_str(), prefix, prefix_len))
+    if (strncmp (term.c_str (), prefix, prefix_len))
        return NULL;
 
-    value = talloc_strdup (message, term.c_str() + prefix_len);
+    value = talloc_strdup (message, term.c_str () + prefix_len);
 
 #if DEBUG_DATABASE_SANITY
     i++;
@@ -350,32 +362,32 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
        return;
 
     const char *thread_prefix = _find_prefix ("thread"),
-       *tag_prefix = _find_prefix ("tag"),
-       *id_prefix = _find_prefix ("id"),
-       *type_prefix = _find_prefix ("type"),
-       *filename_prefix = _find_prefix ("file-direntry"),
-       *property_prefix = _find_prefix ("property"),
-       *reference_prefix = _find_prefix ("reference"),
-       *replyto_prefix = _find_prefix ("replyto");
+              *tag_prefix = _find_prefix ("tag"),
+              *id_prefix = _find_prefix ("id"),
+              *type_prefix = _find_prefix ("type"),
+              *filename_prefix = _find_prefix ("file-direntry"),
+              *property_prefix = _find_prefix ("property"),
+              *reference_prefix = _find_prefix ("reference"),
+              *replyto_prefix = _find_prefix ("replyto");
 
     /* We do this all in a single pass because Xapian decompresses the
      * term list every time you iterate over it.  Thus, while this is
      * slightly more costly than looking up individual fields if only
      * one field of the message object is actually used, it's a huge
      * win as more fields are used. */
-    for (int count=0; count < 3; count++) {
+    for (int count = 0; count < 3; count++) {
        try {
            i = message->doc.termlist_begin ();
            end = message->doc.termlist_end ();
 
            /* Get thread */
-           if (!message->thread_id)
+           if (! message->thread_id)
                message->thread_id =
                    _notmuch_message_get_term (message, i, end, thread_prefix);
 
            /* Get tags */
            assert (strcmp (thread_prefix, tag_prefix) < 0);
-           if (!message->tag_list) {
+           if (! message->tag_list) {
                message->tag_list =
                    _notmuch_database_get_terms_with_prefix (message, i, end,
                                                             tag_prefix);
@@ -384,7 +396,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
 
            /* Get id */
            assert (strcmp (tag_prefix, id_prefix) < 0);
-           if (!message->message_id)
+           if (! message->message_id)
                message->message_id =
                    _notmuch_message_get_term (message, i, end, id_prefix);
 
@@ -407,7 +419,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
             * expand them to full file names when needed in
             * _notmuch_message_ensure_filename_list. */
            assert (strcmp (type_prefix, filename_prefix) < 0);
-           if (!message->filename_term_list && !message->filename_list)
+           if (! message->filename_term_list && ! message->filename_list)
                message->filename_term_list =
                    _notmuch_database_get_terms_with_prefix (message, i, end,
                                                             filename_prefix);
@@ -415,14 +427,14 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
 
            /* Get property terms. Mimic the setup with filenames above */
            assert (strcmp (filename_prefix, property_prefix) < 0);
-           if (!message->property_map && !message->property_term_list)
+           if (! message->property_map && ! message->property_term_list)
                message->property_term_list =
                    _notmuch_database_get_terms_with_prefix (message, i, end,
-                                                        property_prefix);
+                                                            property_prefix);
 
            /* get references */
            assert (strcmp (property_prefix, reference_prefix) < 0);
-           if (!message->reference_list) {
+           if (! message->reference_list) {
                message->reference_list =
                    _notmuch_database_get_terms_with_prefix (message, i, end,
                                                             reference_prefix);
@@ -430,14 +442,14 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
 
            /* Get reply to */
            assert (strcmp (property_prefix, replyto_prefix) < 0);
-           if (!message->in_reply_to)
+           if (! message->in_reply_to)
                message->in_reply_to =
                    _notmuch_message_get_term (message, i, end, replyto_prefix);
 
 
            /* It's perfectly valid for a message to have no In-Reply-To
             * header. For these cases, we return an empty string. */
-           if (!message->in_reply_to)
+           if (! message->in_reply_to)
                message->in_reply_to = talloc_strdup (message, "");
 
            /* all the way without an exception */
@@ -447,9 +459,6 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
            if (status != NOTMUCH_STATUS_SUCCESS)
                INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n",
                                notmuch_status_to_string (status));
-       } catch (const Xapian::Error &error) {
-           INTERNAL_ERROR ("A Xapian exception occurred fetching message metadata: %s\n",
-                           error.get_msg().c_str());
        }
     }
     message->last_view = message->notmuch->view;
@@ -507,8 +516,14 @@ _notmuch_message_get_doc_id (notmuch_message_t *message)
 const char *
 notmuch_message_get_message_id (notmuch_message_t *message)
 {
-    _notmuch_message_ensure_metadata (message, message->message_id);
-    if (!message->message_id)
+    try {
+       _notmuch_message_ensure_metadata (message, message->message_id);
+    } catch (const Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NULL;
+    }
+
+    if (! message->message_id)
        INTERNAL_ERROR ("Message with document ID of %u has no message ID.\n",
                        message->doc_id);
     return message->message_id;
@@ -553,13 +568,11 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
             * it could just mean we didn't record the header. */
            if ((message->notmuch->features &
                 NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES) ||
-               ! value.empty())
+               ! value.empty ())
                return talloc_strdup (message, value.c_str ());
 
        } catch (Xapian::Error &error) {
-           _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred when reading header: %s\n",
-                    error.get_msg().c_str());
-           message->notmuch->exception_reported = true;
+           LOG_XAPIAN_EXCEPTION (message, error);
            return NULL;
        }
     }
@@ -589,8 +602,13 @@ _notmuch_message_get_in_reply_to (notmuch_message_t *message)
 const char *
 notmuch_message_get_thread_id (notmuch_message_t *message)
 {
-    _notmuch_message_ensure_metadata (message, message->thread_id);
-    if (!message->thread_id)
+    try {
+       _notmuch_message_ensure_metadata (message, message->thread_id);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NULL;
+    }
+    if (! message->thread_id)
        INTERNAL_ERROR ("Message with document ID of %u has no thread ID.\n",
                        message->doc_id);
     return message->thread_id;
@@ -604,7 +622,8 @@ _notmuch_message_add_reply (notmuch_message_t *message,
 }
 
 size_t
-_notmuch_message_get_thread_depth (notmuch_message_t *message) {
+_notmuch_message_get_thread_depth (notmuch_message_t *message)
+{
     return message->thread_depth;
 }
 
@@ -618,7 +637,7 @@ _notmuch_message_label_depths (notmuch_message_t *message,
         notmuch_messages_valid (messages);
         notmuch_messages_move_to_next (messages)) {
        notmuch_message_t *child = notmuch_messages_get (messages);
-       _notmuch_message_label_depths (child, depth+1);
+       _notmuch_message_label_depths (child, depth + 1);
     }
 }
 
@@ -730,7 +749,7 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message)
        type_prefix = _find_prefix ("type");
 
     /* Make sure we have the data to restore to Xapian*/
-    _notmuch_message_ensure_metadata (message,NULL);
+    _notmuch_message_ensure_metadata (message, NULL);
 
     /* Empirically, it turns out to be faster to remove all the terms,
      * and add back the ones we want. */
@@ -750,11 +769,11 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message)
 
        const char *tag = notmuch_tags_get (tags);
 
-       if (STRNCMP_LITERAL (tag, "encrypted") != 0 &&
-           STRNCMP_LITERAL (tag, "signed") != 0 &&
-           STRNCMP_LITERAL (tag, "attachment") != 0) {
+       if (strcmp (tag, "encrypted") != 0 &&
+           strcmp (tag, "signed") != 0 &&
+           strcmp (tag, "attachment") != 0) {
            std::string term = tag_prefix + tag;
-           message->doc.add_term(term);
+           message->doc.add_term (term);
        }
     }
 
@@ -764,10 +783,10 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message)
     for (list = notmuch_message_get_properties (message, "", false);
         notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
        std::string term = property_prefix +
-           notmuch_message_properties_key(list) + "=" +
-           notmuch_message_properties_value(list);
+                          notmuch_message_properties_key (list) + "=" +
+                          notmuch_message_properties_value (list);
 
-       message->doc.add_term(term);
+       message->doc.add_term (term);
     }
 
     notmuch_message_properties_destroy (list);
@@ -777,7 +796,8 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message)
 
 
 /* Return true if p points at "new" or "cur". */
-static bool is_maildir (const char *p)
+static bool
+is_maildir (const char *p)
 {
     return strcmp (p, "cur") == 0 || strcmp (p, "new") == 0;
 }
@@ -972,7 +992,7 @@ _notmuch_message_remove_filename (notmuch_message_t *message,
 
     status = _notmuch_database_filename_to_direntry (
        local, message->notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
-    if (status || !direntry)
+    if (status || ! direntry)
        return status;
 
     /* Unlink this file from its parent directory. */
@@ -1041,7 +1061,7 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
     message->filename_list = _notmuch_string_list_create (message);
     node = message->filename_term_list->head;
 
-    if (!node) {
+    if (! node) {
        /* A message document created by an old version of notmuch
         * (prior to rename support) will have the filename in the
         * data of the document rather than as a file-direntry term.
@@ -1102,14 +1122,18 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
 const char *
 notmuch_message_get_filename (notmuch_message_t *message)
 {
-    _notmuch_message_ensure_filename_list (message);
+    try {
+       _notmuch_message_ensure_filename_list (message);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NULL;
+    }
 
     if (message->filename_list == NULL)
        return NULL;
 
     if (message->filename_list->head == NULL ||
-       message->filename_list->head->string == NULL)
-    {
+       message->filename_list->head->string == NULL) {
        INTERNAL_ERROR ("message with no filename");
     }
 
@@ -1119,7 +1143,12 @@ notmuch_message_get_filename (notmuch_message_t *message)
 notmuch_filenames_t *
 notmuch_message_get_filenames (notmuch_message_t *message)
 {
-    _notmuch_message_ensure_filename_list (message);
+    try {
+       _notmuch_message_ensure_filename_list (message);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NULL;
+    }
 
     return _notmuch_filenames_create (message, message->filename_list);
 }
@@ -1127,20 +1156,50 @@ notmuch_message_get_filenames (notmuch_message_t *message)
 int
 notmuch_message_count_files (notmuch_message_t *message)
 {
-    _notmuch_message_ensure_filename_list (message);
+    try {
+       _notmuch_message_ensure_filename_list (message);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return -1;
+    }
 
     return _notmuch_string_list_length (message->filename_list);
 }
 
+notmuch_status_t
+notmuch_message_get_flag_st (notmuch_message_t *message,
+                            notmuch_message_flag_t flag,
+                            notmuch_bool_t *is_set)
+{
+    if (! is_set)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    try {
+       if (flag == NOTMUCH_MESSAGE_FLAG_GHOST &&
+           ! NOTMUCH_TEST_BIT (message->lazy_flags, flag))
+           _notmuch_message_ensure_metadata (message, NULL);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    *is_set = NOTMUCH_TEST_BIT (message->flags, flag);
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
 notmuch_bool_t
 notmuch_message_get_flag (notmuch_message_t *message,
                          notmuch_message_flag_t flag)
 {
-    if (flag == NOTMUCH_MESSAGE_FLAG_GHOST &&
-       ! NOTMUCH_TEST_BIT (message->lazy_flags, flag))
-       _notmuch_message_ensure_metadata (message, NULL);
+    notmuch_bool_t is_set;
+    notmuch_status_t status;
+
+    status = notmuch_message_get_flag_st (message, flag, &is_set);
 
-    return NOTMUCH_TEST_BIT (message->flags, flag);
+    if (status)
+       return FALSE;
+    else
+       return is_set;
 }
 
 void
@@ -1162,9 +1221,7 @@ notmuch_message_get_date (notmuch_message_t *message)
     try {
        value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP);
     } catch (Xapian::Error &error) {
-       _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred when reading date: %s\n",
-                error.get_msg().c_str());
-       message->notmuch->exception_reported = true;
+       LOG_XAPIAN_EXCEPTION (message, error);
        return 0;
     }
 
@@ -1179,7 +1236,12 @@ notmuch_message_get_tags (notmuch_message_t *message)
 {
     notmuch_tags_t *tags;
 
-    _notmuch_message_ensure_metadata (message, message->tag_list);
+    try {
+       _notmuch_message_ensure_metadata (message, message->tag_list);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NULL;
+    }
 
     tags = _notmuch_tags_create (message, message->tag_list);
     /* _notmuch_tags_create steals the reference to the tag_list, but
@@ -1188,7 +1250,7 @@ notmuch_message_get_tags (notmuch_message_t *message)
      * possible to modify the message tags (which talloc_unlink's the
      * current list from the message) while still iterating because
      * the iterator will keep the current list alive. */
-    if (!talloc_reference (message, message->tag_list))
+    if (! talloc_reference (message, message->tag_list))
        return NULL;
 
     return tags;
@@ -1202,11 +1264,11 @@ _notmuch_message_get_author (notmuch_message_t *message)
 
 void
 _notmuch_message_set_author (notmuch_message_t *message,
-                           const char *author)
+                            const char *author)
 {
     if (message->author)
-       talloc_free(message->author);
-    message->author = talloc_strdup(message, author);
+       talloc_free (message->author);
+    message->author = talloc_strdup (message, author);
     return;
 }
 
@@ -1260,9 +1322,7 @@ _notmuch_message_upgrade_last_mod (notmuch_message_t *message)
 void
 _notmuch_message_sync (notmuch_message_t *message)
 {
-    Xapian::WritableDatabase *db;
-
-    if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+    if (_notmuch_database_mode (message->notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY)
        return;
 
     if (! message->modified)
@@ -1280,8 +1340,8 @@ _notmuch_message_sync (notmuch_message_t *message)
                                    _notmuch_database_new_revision (
                                        message->notmuch)));
 
-    db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
-    db->replace_document (message->doc_id, message->doc);
+    message->notmuch->writable_xapian_db->
+       replace_document (message->doc_id, message->doc);
     message->modified = false;
 }
 
@@ -1291,7 +1351,6 @@ notmuch_status_t
 _notmuch_message_delete (notmuch_message_t *message)
 {
     notmuch_status_t status;
-    Xapian::WritableDatabase *db;
     const char *mid, *tid, *query_string;
     notmuch_message_t *ghost;
     notmuch_private_status_t private_status;
@@ -1308,8 +1367,7 @@ _notmuch_message_delete (notmuch_message_t *message)
     if (status)
        return status;
 
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-    db->delete_document (message->doc_id);
+    message->notmuch->writable_xapian_db->delete_document (message->doc_id);
 
     /* if this was a ghost to begin with, we are done */
     private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
@@ -1339,8 +1397,8 @@ _notmuch_message_delete (notmuch_message_t *message)
                _notmuch_message_sync (ghost);
        } else if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
            /* this is deeply weird, and we should not have gotten
-              into this state.  is there a better error message to
-              return here? */
+            * into this state.  is there a better error message to
+            * return here? */
            status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
        }
 
@@ -1358,8 +1416,8 @@ _notmuch_message_delete (notmuch_message_t *message)
                message = notmuch_messages_get (messages);
                status = _notmuch_message_delete (message);
                if (status) /* we'll report the last failure we see;
-                            * if there is more than one failure, we
-                            * forget about previous ones */
+                                        * if there is more than one failure, we
+                                        * forget about previous ones */
                    last_error = status;
                notmuch_message_destroy (message);
                notmuch_messages_move_to_next (messages);
@@ -1535,7 +1593,7 @@ _notmuch_message_has_term (notmuch_message_t *message,
        Xapian::TermIterator i = message->doc.termlist_begin ();
        i.skip_to (term);
        if (i != message->doc.termlist_end () &&
-           !strcmp ((*i).c_str (), term))
+           ! strcmp ((*i).c_str (), term))
            out = true;
     } catch (Xapian::Error &error) {
        status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
@@ -1552,24 +1610,31 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
     notmuch_private_status_t private_status;
     notmuch_status_t status;
 
-    status = _notmuch_database_ensure_writable (message->notmuch);
-    if (status)
-       return status;
+    try {
+       status = _notmuch_database_ensure_writable (message->notmuch);
+       if (status)
+           return status;
 
-    if (tag == NULL)
-       return NOTMUCH_STATUS_NULL_POINTER;
+       if (tag == NULL)
+           return NOTMUCH_STATUS_NULL_POINTER;
 
-    if (strlen (tag) > NOTMUCH_TAG_MAX)
-       return NOTMUCH_STATUS_TAG_TOO_LONG;
+       if (strlen (tag) > NOTMUCH_TAG_MAX)
+           return NOTMUCH_STATUS_TAG_TOO_LONG;
 
-    private_status = _notmuch_message_add_term (message, "tag", tag);
-    if (private_status) {
-       INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n",
-                       private_status);
-    }
+       private_status = _notmuch_message_add_term (message, "tag", tag);
+       if (private_status) {
+           return COERCE_STATUS (private_status,
+                                 "_notmuch_message_remove_term return unexpected value: %d\n",
+                                 private_status);
+       }
 
-    if (! message->frozen)
-       _notmuch_message_sync (message);
+       if (! message->frozen)
+           _notmuch_message_sync (message);
+
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -1580,24 +1645,30 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
     notmuch_private_status_t private_status;
     notmuch_status_t status;
 
-    status = _notmuch_database_ensure_writable (message->notmuch);
-    if (status)
-       return status;
+    try {
+       status = _notmuch_database_ensure_writable (message->notmuch);
+       if (status)
+           return status;
 
-    if (tag == NULL)
-       return NOTMUCH_STATUS_NULL_POINTER;
+       if (tag == NULL)
+           return NOTMUCH_STATUS_NULL_POINTER;
 
-    if (strlen (tag) > NOTMUCH_TAG_MAX)
-       return NOTMUCH_STATUS_TAG_TOO_LONG;
+       if (strlen (tag) > NOTMUCH_TAG_MAX)
+           return NOTMUCH_STATUS_TAG_TOO_LONG;
 
-    private_status = _notmuch_message_remove_term (message, "tag", tag);
-    if (private_status) {
-       INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-                       private_status);
-    }
+       private_status = _notmuch_message_remove_term (message, "tag", tag);
+       if (private_status) {
+           return COERCE_STATUS (private_status,
+                                 "_notmuch_message_remove_term return unexpected value: %d\n",
+                                 private_status);
+       }
 
-    if (! message->frozen)
-       _notmuch_message_sync (message);
+       if (! message->frozen)
+           _notmuch_message_sync (message);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -1635,15 +1706,14 @@ _filename_is_in_maildir (const char *filename)
     dir = slash + 1;
 
     if (STRNCMP_LITERAL (dir, "cur/") == 0 ||
-       STRNCMP_LITERAL (dir, "new/") == 0)
-    {
+       STRNCMP_LITERAL (dir, "new/") == 0) {
        return dir;
     }
 
     return NULL;
 }
 
-static void
+static notmuch_status_t
 _ensure_maildir_flags (notmuch_message_t *message, bool force)
 {
     const char *flags;
@@ -1658,11 +1728,12 @@ _ensure_maildir_flags (notmuch_message_t *message, bool force)
            message->maildir_flags = NULL;
        }
     }
-
-    for (filenames = notmuch_message_get_filenames (message);
+    filenames = notmuch_message_get_filenames (message);
+    if (! filenames)
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    for (;
         notmuch_filenames_valid (filenames);
-        notmuch_filenames_move_to_next (filenames))
-    {
+        notmuch_filenames_move_to_next (filenames)) {
        filename = notmuch_filenames_get (filenames);
        dir = _filename_is_in_maildir (filename);
 
@@ -1686,13 +1757,37 @@ _ensure_maildir_flags (notmuch_message_t *message, bool force)
     }
     if (seen_maildir_info)
        message->maildir_flags = combined_flags;
+    return NOTMUCH_STATUS_SUCCESS;
 }
 
 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 status;
+    notmuch_bool_t ret;
+    status = notmuch_message_has_maildir_flag_st (message, flag, &ret);
+    if (status)
+       return FALSE;
+
+    return ret;
+}
+
+notmuch_status_t
+notmuch_message_has_maildir_flag_st (notmuch_message_t *message,
+                                    char flag,
+                                    notmuch_bool_t *is_set)
+{
+    notmuch_status_t status;
+    
+    if (! is_set)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    status = _ensure_maildir_flags (message, false);
+    if (status)
+       return status;
+    
+    *is_set =  message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL);
+    return NOTMUCH_STATUS_SUCCESS;
 }
 
 notmuch_status_t
@@ -1701,7 +1796,9 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
     notmuch_status_t status;
     unsigned i;
 
-    _ensure_maildir_flags (message, true);
+    status = _ensure_maildir_flags (message, true);
+    if (status)
+       return status;
     /* 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. */
@@ -1712,11 +1809,10 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
     if (status)
        return status;
 
-    for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
+    for (i = 0; i < ARRAY_SIZE (flag2tag); i++) {
        if ((strchr (message->maildir_flags, flag2tag[i].flag) != NULL)
            ^
-           flag2tag[i].inverse)
-       {
+           flag2tag[i].inverse) {
            status = notmuch_message_add_tag (message, flag2tag[i].tag);
        } else {
            status = notmuch_message_remove_tag (message, flag2tag[i].tag);
@@ -1751,8 +1847,7 @@ _get_maildir_flag_actions (notmuch_message_t *message,
     /* First, find flags for all set tags. */
     for (tags = notmuch_message_get_tags (message);
         notmuch_tags_valid (tags);
-        notmuch_tags_move_to_next (tags))
-    {
+        notmuch_tags_move_to_next (tags)) {
        tag = notmuch_tags_get (tags);
 
        for (i = 0; i < ARRAY_SIZE (flag2tag); i++) {
@@ -1802,7 +1897,7 @@ _get_maildir_flag_actions (notmuch_message_t *message,
  * non-ASCII ordering of flags), this function will return NULL
  * (meaning that renaming would not be safe and should not occur).
  */
-static char*
+static char *
 _new_maildir_filename (void *ctx,
                       const char *filename,
                       const char *flags_to_set,
@@ -1822,13 +1917,12 @@ _new_maildir_filename (void *ctx,
     info = strstr (filename, ":2,");
 
     if (info == NULL) {
-       info = filename + strlen(filename);
+       info = filename + strlen (filename);
     } else {
        /* Loop through existing flags in filename. */
        for (flags = info + 3, last_flag = 0;
             *flags;
-            last_flag = flag, flags++)
-       {
+            last_flag = flag, flags++) {
            flag = *flags;
 
            /* Original flags not in ASCII order. Abort. */
@@ -1836,7 +1930,7 @@ _new_maildir_filename (void *ctx,
                return NULL;
 
            /* Non-ASCII flag. Abort. */
-           if (flag > sizeof(flag_map) - 1)
+           if (flag > sizeof (flag_map) - 1)
                return NULL;
 
            /* Repeated flag value. Abort. */
@@ -1870,7 +1964,7 @@ _new_maildir_filename (void *ctx,
     /* Messages in new/ without maildir info can be kept in new/ if no
      * flags have changed. */
     dir = (char *) _filename_is_in_maildir (filename);
-    if (dir && STRNCMP_LITERAL (dir, "new/") == 0 && !*info && !flags_changed)
+    if (dir && STRNCMP_LITERAL (dir, "new/") == 0 && ! *info && ! flags_changed)
        return talloc_strdup (ctx, filename);
 
     filename_new = (char *) talloc_size (ctx,
@@ -1885,8 +1979,7 @@ _new_maildir_filename (void *ctx,
     strcat (filename_new, ":2,");
 
     s = filename_new + strlen (filename_new);
-    for (i = 0; i < sizeof (flag_map); i++)
-    {
+    for (i = 0; i < sizeof (flag_map); i++) {
        if (flag_map[i]) {
            *s = i;
            s++;
@@ -1915,8 +2008,7 @@ notmuch_message_tags_to_maildir_flags (notmuch_message_t *message)
 
     for (filenames = notmuch_message_get_filenames (message);
         notmuch_filenames_valid (filenames);
-        notmuch_filenames_move_to_next (filenames))
-    {
+        notmuch_filenames_move_to_next (filenames)) {
        filename = notmuch_filenames_get (filenames);
 
        if (! _filename_is_in_maildir (filename))
@@ -1975,17 +2067,20 @@ notmuch_message_remove_all_tags (notmuch_message_t *message)
     status = _notmuch_database_ensure_writable (message->notmuch);
     if (status)
        return status;
+    tags = notmuch_message_get_tags (message);
+    if (! tags)
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
 
-    for (tags = notmuch_message_get_tags (message);
+    for (;
         notmuch_tags_valid (tags);
-        notmuch_tags_move_to_next (tags))
-    {
+        notmuch_tags_move_to_next (tags)) {
        tag = notmuch_tags_get (tags);
 
        private_status = _notmuch_message_remove_term (message, "tag", tag);
        if (private_status) {
-           INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-                           private_status);
+           return COERCE_STATUS (private_status,
+                                  "_notmuch_message_remove_term return unexpected value: %d\n",
+                                  private_status);
        }
     }
 
@@ -2057,8 +2152,8 @@ _notmuch_message_ensure_property_map (notmuch_message_t *message)
        const char *key;
        char *value;
 
-       value = strchr(node->string, '=');
-       if (!value)
+       value = strchr (node->string, '=');
+       if (! value)
            INTERNAL_ERROR ("malformed property term");
 
        *value = '\0';
@@ -2105,9 +2200,11 @@ notmuch_message_reindex (notmuch_message_t *message,
 
     /* 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");
+    if (! orig_thread_id) {
+       /* the following is correct as long as there is only one reason
+          n_m_get_thread_id returns NULL
+       */
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
     /* strdup it because the metadata may be invalidated */
@@ -2123,7 +2220,7 @@ notmuch_message_reindex (notmuch_message_t *message,
 
     private_status = _notmuch_message_remove_indexed_terms (message);
     if (private_status) {
-       ret = COERCE_STATUS(private_status, "error removing terms");
+       ret = COERCE_STATUS (private_status, "error removing terms");
        goto DONE;
     }
 
@@ -2194,7 +2291,7 @@ notmuch_message_reindex (notmuch_message_t *message,
        _notmuch_message_sync (message);
     }
 
- DONE:
 DONE:
     if (message_file)
        _notmuch_message_file_close (message_file);
 
index 04fa19f8ec16e4c140e506e1f39f084ba47e09dc..eec0a1622a3fc4fb2961fbddaff8ed222a3c4331 100644 (file)
@@ -117,7 +117,7 @@ _notmuch_messages_has_next (notmuch_messages_t *messages)
        return false;
 
     if (! messages->is_of_list_type)
-       INTERNAL_ERROR("_notmuch_messages_has_next not implimented for msets");
+       INTERNAL_ERROR ("_notmuch_messages_has_next not implemented for msets");
 
     return (messages->iterator->next != NULL);
 }
@@ -183,7 +183,7 @@ notmuch_messages_collect_tags (notmuch_messages_t *messages)
 
     keys = g_hash_table_get_keys (htable);
     for (l = keys; l; l = l->next) {
-       _notmuch_string_list_append (tags, (char *)l->data);
+       _notmuch_string_list_append (tags, (char *) l->data);
     }
 
     g_list_free (keys);
index 6fc5b366c539712c6ddd2b2fd3778080b0b8958e..57ec7f72eae530b8aa82efb6598065382947064f 100644 (file)
@@ -53,6 +53,7 @@ NOTMUCH_BEGIN_DECLS
 #include "error_util.h"
 #include "string-util.h"
 #include "crypto.h"
+#include "repair.h"
 
 #ifdef DEBUG
 # define DEBUG_DATABASE_SANITY 1
@@ -60,7 +61,7 @@ NOTMUCH_BEGIN_DECLS
 # define DEBUG_QUERY 1
 #endif
 
-#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - 2*!(pred)]))
+#define COMPILE_TIME_ASSERT(pred) ((void) sizeof (char[1 - 2 * ! (pred)]))
 
 #define STRNCMP_LITERAL(var, literal) \
     strncmp ((var), (literal), sizeof (literal) - 1)
@@ -69,11 +70,11 @@ NOTMUCH_BEGIN_DECLS
 #define _NOTMUCH_VALID_BIT(bit) \
     ((bit) >= 0 && ((unsigned long) bit) < CHAR_BIT * sizeof (unsigned long long))
 #define NOTMUCH_TEST_BIT(val, bit) \
-    (_NOTMUCH_VALID_BIT(bit) ? !!((val) & (1ull << (bit))) : 0)
+    (_NOTMUCH_VALID_BIT (bit) ? ! ! ((val) & (1ull << (bit))) : 0)
 #define NOTMUCH_SET_BIT(valp, bit) \
-    (_NOTMUCH_VALID_BIT(bit) ? (*(valp) |= (1ull << (bit))) : *(valp))
+    (_NOTMUCH_VALID_BIT (bit) ? (*(valp) |= (1ull << (bit))) : *(valp))
 #define NOTMUCH_CLEAR_BIT(valp,  bit) \
-    (_NOTMUCH_VALID_BIT(bit) ? (*(valp) &= ~(1ull << (bit))) : *(valp))
+    (_NOTMUCH_VALID_BIT (bit) ? (*(valp) &= ~(1ull << (bit))) : *(valp))
 
 #define unused(x) x __attribute__ ((unused))
 
@@ -83,12 +84,12 @@ NOTMUCH_BEGIN_DECLS
 /* these macros gain us a few percent of speed on gcc */
 #if (__GNUC__ >= 3)
 /* the strange !! is to ensure that __builtin_expect() takes either 0 or 1
  as its first argument */
* as its first argument */
 #ifndef likely
-#define likely(x)   __builtin_expect(!!(x), 1)
+#define likely(x)   __builtin_expect (! ! (x), 1)
 #endif
 #ifndef unlikely
-#define unlikely(x) __builtin_expect(!!(x), 0)
+#define unlikely(x) __builtin_expect (! ! (x), 0)
 #endif
 #else
 #ifndef likely
@@ -124,17 +125,17 @@ typedef enum {
 
 typedef enum _notmuch_private_status {
     /* First, copy all the public status values. */
-    NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS,
-    NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY,
-    NOTMUCH_PRIVATE_STATUS_READ_ONLY_DATABASE = NOTMUCH_STATUS_READ_ONLY_DATABASE,
-    NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION = NOTMUCH_STATUS_XAPIAN_EXCEPTION,
-    NOTMUCH_PRIVATE_STATUS_FILE_NOT_EMAIL = NOTMUCH_STATUS_FILE_NOT_EMAIL,
-    NOTMUCH_PRIVATE_STATUS_NULL_POINTER = NOTMUCH_STATUS_NULL_POINTER,
-    NOTMUCH_PRIVATE_STATUS_TAG_TOO_LONG = NOTMUCH_STATUS_TAG_TOO_LONG,
-    NOTMUCH_PRIVATE_STATUS_UNBALANCED_FREEZE_THAW = NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
+    NOTMUCH_PRIVATE_STATUS_SUCCESS                     = NOTMUCH_STATUS_SUCCESS,
+    NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY               = NOTMUCH_STATUS_OUT_OF_MEMORY,
+    NOTMUCH_PRIVATE_STATUS_READ_ONLY_DATABASE          = NOTMUCH_STATUS_READ_ONLY_DATABASE,
+    NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION            = NOTMUCH_STATUS_XAPIAN_EXCEPTION,
+    NOTMUCH_PRIVATE_STATUS_FILE_NOT_EMAIL              = NOTMUCH_STATUS_FILE_NOT_EMAIL,
+    NOTMUCH_PRIVATE_STATUS_NULL_POINTER                        = NOTMUCH_STATUS_NULL_POINTER,
+    NOTMUCH_PRIVATE_STATUS_TAG_TOO_LONG                        = NOTMUCH_STATUS_TAG_TOO_LONG,
+    NOTMUCH_PRIVATE_STATUS_UNBALANCED_FREEZE_THAW      = NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
 
     /* Then add our own private values. */
-    NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG = NOTMUCH_STATUS_LAST_STATUS,
+    NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG               = NOTMUCH_STATUS_LAST_STATUS,
     NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND,
     NOTMUCH_PRIVATE_STATUS_BAD_PREFIX,
 
@@ -150,14 +151,14 @@ typedef enum _notmuch_private_status {
  * Note that the function _internal_error does not return. Evaluating
  * to NOTMUCH_STATUS_SUCCESS is done purely to appease the compiler.
  */
-#define COERCE_STATUS(private_status, format, ...)                     \
-    ((private_status >= (notmuch_private_status_t) NOTMUCH_STATUS_LAST_STATUS)\
-     ?                                                                 \
-     _internal_error (format " (%s).\n",                               \
-                     ##__VA_ARGS__,                                    \
-                     __location__),                                    \
-     (notmuch_status_t) NOTMUCH_PRIVATE_STATUS_SUCCESS                 \
-     :                                                                 \
+#define COERCE_STATUS(private_status, format, ...)                      \
+    ((private_status >= (notmuch_private_status_t) NOTMUCH_STATUS_LAST_STATUS) \
+     ?                                                                  \
+     _internal_error (format " (%s).\n",                                \
+                     ##__VA_ARGS__,                                    \
+                     __location__),                                    \
+     (notmuch_status_t) NOTMUCH_PRIVATE_STATUS_SUCCESS                  \
+     :                                                                  \
      (notmuch_status_t) private_status)
 
 /* Flags shared by various lookup functions. */
@@ -167,7 +168,7 @@ typedef enum _notmuch_find_flags {
     NOTMUCH_FIND_LOOKUP = 0,
     /* If set, create the necessary document (or documents) if they
      * are missing.  Requires a read/write database. */
-    NOTMUCH_FIND_CREATE = 1<<0,
+    NOTMUCH_FIND_CREATE = 1 << 0,
 } notmuch_find_flags_t;
 
 typedef struct _notmuch_doc_id_set notmuch_doc_id_set_t;
@@ -185,7 +186,7 @@ _find_prefix (const char *name);
 /* Lookup a prefix value by name, including possibly user defined prefixes
  */
 const char *
-_notmuch_database_prefix (notmuch_database_t  *notmuch, const char *name);
+_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name);
 
 char *
 _notmuch_message_id_compressed (void *ctx, const char *message_id);
@@ -250,14 +251,17 @@ _notmuch_database_filename_to_direntry (void *ctx,
 /* directory.cc */
 
 notmuch_directory_t *
-_notmuch_directory_create (notmuch_database_t *notmuch,
-                          const char *path,
-                          notmuch_find_flags_t flags,
-                          notmuch_status_t *status_ret);
+_notmuch_directory_find_or_create (notmuch_database_t *notmuch,
+                                  const char *path,
+                                  notmuch_find_flags_t flags,
+                                  notmuch_status_t *status_ret);
 
 unsigned int
 _notmuch_directory_get_document_id (notmuch_directory_t *directory);
 
+notmuch_database_mode_t
+_notmuch_database_mode (notmuch_database_t *notmuch);
+
 /* message.cc */
 
 notmuch_message_t *
@@ -436,7 +440,7 @@ _notmuch_message_file_get_mime_message (notmuch_message_file_t *message,
  */
 const char *
 _notmuch_message_file_get_header (notmuch_message_file_t *message,
-                                const char *header);
+                                 const char *header);
 
 notmuch_status_t
 _notmuch_message_file_get_headers (notmuch_message_file_t *message_file,
@@ -568,7 +572,7 @@ void
 _notmuch_message_remove_unprefixed_terms (notmuch_message_t *message);
 
 const char *
-_notmuch_message_get_thread_id_only(notmuch_message_t *message);
+_notmuch_message_get_thread_id_only (notmuch_message_t *message);
 
 size_t _notmuch_message_get_thread_depth (notmuch_message_t *message);
 
@@ -621,10 +625,10 @@ void
 _notmuch_string_list_sort (notmuch_string_list_t *list);
 
 const notmuch_string_list_t *
-_notmuch_message_get_references(notmuch_message_t *message);
+_notmuch_message_get_references (notmuch_message_t *message);
 
 /* string-map.c */
-typedef struct _notmuch_string_map  notmuch_string_map_t;
+typedef struct _notmuch_string_map notmuch_string_map_t;
 typedef struct _notmuch_string_map_iterator notmuch_string_map_iterator_t;
 notmuch_string_map_t *
 _notmuch_string_map_create (const void *ctx);
@@ -704,7 +708,7 @@ NOTMUCH_END_DECLS
  * template function for this to maintain type safety, and redefine
  * talloc_steal to use it.
  */
-#if !(__GNUC__ >= 3)
+#if ! (__GNUC__ >= 3)
 template <class T> T *
 _notmuch_talloc_steal (const void *new_ctx, const T *ptr)
 {
index 24708f3c5947c6819570f493bb249284e40407cc..c66e78b1941226617285537338efe4ef7d90f293 100644 (file)
@@ -57,18 +57,18 @@ NOTMUCH_BEGIN_DECLS
  * The library version number.  This must agree with the soname
  * version in Makefile.local.
  */
-#define LIBNOTMUCH_MAJOR_VERSION       5
-#define LIBNOTMUCH_MINOR_VERSION       2
-#define LIBNOTMUCH_MICRO_VERSION       0
+#define LIBNOTMUCH_MAJOR_VERSION        5
+#define LIBNOTMUCH_MINOR_VERSION        3
+#define LIBNOTMUCH_MICRO_VERSION        0
 
 
 #if defined (__clang_major__) && __clang_major__ >= 3 \
     || defined (__GNUC__) && __GNUC__ >= 5 \
     || defined (__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 5
-#define NOTMUCH_DEPRECATED(major,minor) \
+#define NOTMUCH_DEPRECATED(major, minor) \
     __attribute__ ((deprecated ("function deprecated as of libnotmuch " #major "." #minor)))
 #else
-#define NOTMUCH_DEPRECATED(major,minor) __attribute__ ((deprecated))
+#define NOTMUCH_DEPRECATED(major, minor) __attribute__ ((deprecated))
 #endif
 
 
@@ -95,8 +95,8 @@ NOTMUCH_BEGIN_DECLS
  * #endif
  * @endcode
  */
-#define LIBNOTMUCH_CHECK_VERSION(major, minor, micro)                  \
-    (LIBNOTMUCH_MAJOR_VERSION > (major) ||                                     \
+#define LIBNOTMUCH_CHECK_VERSION(major, minor, micro)                   \
+    (LIBNOTMUCH_MAJOR_VERSION > (major) ||                                      \
      (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION > (minor)) || \
      (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION == (minor) && \
       LIBNOTMUCH_MICRO_VERSION >= (micro)))
@@ -405,8 +405,8 @@ typedef void (*notmuch_compact_status_cb_t)(const char *message, void *closure);
  * 'closure' is passed verbatim to any callback invoked.
  */
 notmuch_status_t
-notmuch_database_compact (const charpath,
-                         const charbackup_path,
+notmuch_database_compact (const char *path,
+                         const char *backup_path,
                          notmuch_compact_status_cb_t status_cb,
                          void *closure);
 
@@ -431,6 +431,8 @@ notmuch_database_get_path (notmuch_database_t *database);
 
 /**
  * Return the database format version of the given database.
+ *
+ * @retval 0 on error
  */
 unsigned int
 notmuch_database_get_version (notmuch_database_t *database);
@@ -444,6 +446,9 @@ notmuch_database_get_version (notmuch_database_t *database);
  * fail with NOTMUCH_STATUS_UPGRADE_REQUIRED.  This always returns
  * FALSE for a read-only database because there's no way to upgrade a
  * read-only database.
+ *
+ * Also returns FALSE if an error occurs accessing the database.
+ *
  */
 notmuch_bool_t
 notmuch_database_needs_upgrade (notmuch_database_t *database);
@@ -467,8 +472,8 @@ notmuch_database_needs_upgrade (notmuch_database_t *database);
  */
 notmuch_status_t
 notmuch_database_upgrade (notmuch_database_t *database,
-                         void (*progress_notify) (void *closure,
-                                                  double progress),
+                         void (*progress_notify)(void *closure,
+                                                 double progress),
                          void *closure);
 
 /**
@@ -525,7 +530,7 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch);
  */
 unsigned long
 notmuch_database_get_revision (notmuch_database_t *notmuch,
-                               const char **uuid);
+                              const char **uuid);
 
 /**
  * Retrieve a directory object from the database for 'path'.
@@ -551,7 +556,7 @@ notmuch_database_get_revision (notmuch_database_t *notmuch,
  *     directory not retrieved.
  *
  * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
- *     database to use this function.
+ *      database to use this function.
  */
 notmuch_status_t
 notmuch_database_get_directory (notmuch_database_t *database,
@@ -614,7 +619,7 @@ notmuch_database_get_directory (notmuch_database_t *database,
  *     mode so no message can be added.
  *
  * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
- *     database to use this function.
+ *      database to use this function.
  *
  * @since libnotmuch 5.1 (notmuch 0.26)
  */
@@ -632,7 +637,7 @@ notmuch_database_index_file (notmuch_database_t *database,
  * use notmuch_database_index_file instead.
  *
  */
-NOTMUCH_DEPRECATED(5,1)
+NOTMUCH_DEPRECATED (5, 1)
 notmuch_status_t
 notmuch_database_add_message (notmuch_database_t *database,
                              const char *filename,
@@ -664,7 +669,7 @@ notmuch_database_add_message (notmuch_database_t *database,
  *     mode so no message can be removed.
  *
  * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
- *     database to use this function.
+ *      database to use this function.
  */
 notmuch_status_t
 notmuch_database_remove_message (notmuch_database_t *database,
@@ -722,7 +727,7 @@ notmuch_database_find_message (notmuch_database_t *database,
  * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred
  *
  * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
- *     database to use this function.
+ *      database to use this function.
  */
 notmuch_status_t
 notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
@@ -930,7 +935,7 @@ notmuch_query_search_threads (notmuch_query_t *query,
  * use notmuch_query_search_threads instead.
  *
  */
-NOTMUCH_DEPRECATED(5,0)
+NOTMUCH_DEPRECATED (5, 0)
 notmuch_status_t
 notmuch_query_search_threads_st (notmuch_query_t *query, notmuch_threads_t **out);
 
@@ -986,7 +991,7 @@ notmuch_query_search_messages (notmuch_query_t *query,
  *
  */
 
-NOTMUCH_DEPRECATED(5,0)
+NOTMUCH_DEPRECATED (5, 0)
 notmuch_status_t
 notmuch_query_search_messages_st (notmuch_query_t *query,
                                  notmuch_messages_t **out);
@@ -1082,7 +1087,7 @@ notmuch_query_count_messages (notmuch_query_t *query, unsigned int *count);
  * @deprecated Deprecated since libnotmuch 5.0 (notmuch 0.25). Please
  * use notmuch_query_count_messages instead.
  */
-NOTMUCH_DEPRECATED(5,0)
+NOTMUCH_DEPRECATED (5, 0)
 notmuch_status_t
 notmuch_query_count_messages_st (notmuch_query_t *query, unsigned int *count);
 
@@ -1100,7 +1105,7 @@ notmuch_query_count_messages_st (notmuch_query_t *query, unsigned int *count);
  *
  * NOTMUCH_STATUS_OUT_OF_MEMORY: Memory allocation failed. The value
  *      of *count is not defined
-
+ *
  * NOTMUCH_STATUS_SUCCESS: query completed successfully.
  *
  * NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occurred. The
@@ -1117,7 +1122,7 @@ notmuch_query_count_threads (notmuch_query_t *query, unsigned *count);
  * @deprecated Deprecated as of libnotmuch 5.0 (notmuch 0.25). Please
  * use notmuch_query_count_threads_st instead.
  */
-NOTMUCH_DEPRECATED(5,0)
+NOTMUCH_DEPRECATED (5, 0)
 notmuch_status_t
 notmuch_query_count_threads_st (notmuch_query_t *query, unsigned *count);
 
@@ -1363,9 +1368,8 @@ notmuch_message_get_database (const notmuch_message_t *message);
  * message is valid, (which is until the query from which it derived
  * is destroyed).
  *
- * This function will not return NULL since Notmuch ensures that every
- * message has a unique message ID, (Notmuch will generate an ID for a
- * message if the original file does not contain one).
+ * This function will return NULL if triggers an unhandled Xapian
+ * exception.
  */
 const char *
 notmuch_message_get_message_id (notmuch_message_t *message);
@@ -1379,8 +1383,8 @@ notmuch_message_get_message_id (notmuch_message_t *message);
  * notmuch_message_destroy on 'message' or until a query from which it
  * derived is destroyed).
  *
- * This function will not return NULL since Notmuch ensures that every
- * message belongs to a single thread.
+ * This function will return NULL if triggers an unhandled Xapian
+ * exception.
  */
 const char *
 notmuch_message_get_thread_id (notmuch_message_t *message);
@@ -1403,14 +1407,18 @@ notmuch_message_get_thread_id (notmuch_message_t *message);
  * NULL. (Note that notmuch_messages_valid will accept that NULL
  * value as legitimate, and simply return FALSE for it.)
  *
- * The returned list will be destroyed when the thread is destroyed.
+ * This function also returns NULL if it triggers a Xapian exception.
+ *
+ * The returned list will be destroyed when the thread is
+ * destroyed.
  */
 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
+ * @returns Non-negative integer for file count.
+ * @returns Negative integer for error.
  * @since libnotmuch 5.0 (notmuch 0.25)
  */
 int
@@ -1431,6 +1439,8 @@ notmuch_message_count_files (notmuch_message_t *message);
  * this function will arbitrarily return a single one of those
  * filenames. See notmuch_message_get_filenames for returning the
  * complete list of filenames.
+ *
+ * This function returns NULL if it triggers a Xapian exception.
  */
 const char *
 notmuch_message_get_filename (notmuch_message_t *message);
@@ -1444,6 +1454,8 @@ notmuch_message_get_filename (notmuch_message_t *message);
  *
  * Each filename in the iterator is an absolute filename, (the initial
  * component will match notmuch_database_get_path() ).
+ *
+ * This function returns NULL if it triggers a Xapian exception.
  */
 notmuch_filenames_t *
 notmuch_message_get_filenames (notmuch_message_t *message);
@@ -1479,11 +1491,36 @@ typedef enum _notmuch_message_flag {
 
 /**
  * Get a value of a flag for the email corresponding to 'message'.
+ *
+ * returns FALSE in case of errors.
+ *
+ * @deprecated Deprecated as of libnotmuch 5.3 (notmuch 0.31). Please
+ * use notmuch_message_get_flag_st instead.
  */
+NOTMUCH_DEPRECATED(5,3)
 notmuch_bool_t
 notmuch_message_get_flag (notmuch_message_t *message,
                          notmuch_message_flag_t flag);
 
+/**
+ * Get a value of a flag for the email corresponding to 'message'.
+ *
+ * @param message a message object
+ * @param flag flag to check
+ * @param is_set pointer to boolean to store flag value.
+ *
+ * @retval #NOTMUCH_STATUS_SUCCESS
+ * @retval #NOTMUCH_STATUS_NULL_POINTER is_set is NULL
+ * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION Accessing the database
+ * triggered an exception.
+ *
+ * @since libnotmuch 5.3 (notmuch 0.31)
+ */
+notmuch_status_t
+notmuch_message_get_flag_st (notmuch_message_t *message,
+                            notmuch_message_flag_t flag,
+                            notmuch_bool_t *is_set);
+
 /**
  * Set a value of a flag for the email corresponding to 'message'.
  */
@@ -1497,9 +1534,11 @@ notmuch_message_set_flag (notmuch_message_t *message,
  * For the original textual representation of the Date header from the
  * message call notmuch_message_get_header() with a header value of
  * "date".
+ *
+ * Returns 0 in case of error.
  */
 time_t
-notmuch_message_get_date  (notmuch_message_t *message);
+notmuch_message_get_date (notmuch_message_t *message);
 
 /**
  * Get the value of the specified header from 'message' as a UTF-8 string.
@@ -1601,8 +1640,10 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag);
  * See notmuch_message_freeze for an example showing how to safely
  * replace tag values.
  *
- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
- *     mode so message cannot be modified.
+ * @retval #NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in
+ *     read-only mode so message cannot be modified.
+ * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION: an exception was thrown
+ *      accessing the database.
  */
 notmuch_status_t
 notmuch_message_remove_all_tags (notmuch_message_t *message);
@@ -1646,10 +1687,32 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message);
  * return TRUE if any filename of 'message' has maildir flag 'flag',
  * FALSE otherwise.
  *
+ * Deprecated wrapper for notmuch_message_has_maildir_flag_st
+ *
+ * @returns FALSE in case of error
+ * @deprecated libnotmuch 5.3 (notmuch 0.31)
  */
+NOTMUCH_DEPRECATED(5, 3)
 notmuch_bool_t
 notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag);
 
+/**
+ * check message for maildir flag
+ *
+ * @param [in,out]     message message to check
+ * @param [in] flag    flag to check for
+ * @param [out] is_set  pointer to boolean
+ *
+ * @retval #NOTMUCH_STATUS_SUCCESS
+ * @retval #NOTMUCH_STATUS_NULL_POINTER is_set is NULL
+ * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION Accessing the database
+ * triggered an exception.
+ */
+notmuch_status_t
+notmuch_message_has_maildir_flag_st (notmuch_message_t *message,
+                                    char flag,
+                                    notmuch_bool_t *is_set);
+
 /**
  * Rename message filename(s) to encode tags as maildir flags.
  *
@@ -1811,7 +1874,7 @@ notmuch_message_add_property (notmuch_message_t *message, const char *key, const
 /**
  * Remove a (key,value) pair from a message.
  *
- * It is not an error to remove a non-existant (key,value) pair
+ * It is not an error to remove a non-existent (key,value) pair
  *
  * @returns
  * - NOTMUCH_STATUS_ILLEGAL_ARGUMENT: *key* may not contain an '=' character.
@@ -2085,6 +2148,8 @@ notmuch_directory_get_mtime (notmuch_directory_t *directory);
  *
  * The returned filenames will be the basename-entries only (not
  * complete paths).
+ *
+ * Returns NULL if it triggers a Xapian exception
  */
 notmuch_filenames_t *
 notmuch_directory_get_child_files (notmuch_directory_t *directory);
@@ -2095,6 +2160,8 @@ notmuch_directory_get_child_files (notmuch_directory_t *directory);
  *
  * The returned filenames will be the basename-entries only (not
  * complete paths).
+ *
+ * Returns NULL if it triggers a Xapian exception
  */
 notmuch_filenames_t *
 notmuch_directory_get_child_directories (notmuch_directory_t *directory);
@@ -2224,6 +2291,7 @@ notmuch_config_list_key (notmuch_config_list_t *config_list);
  * next call to notmuch_config_list_value or notmuch config_list_destroy
  *
  * @since libnotmuch 4.4 (notmuch 0.23)
+ * @retval NULL for errors
  */
 const char *
 notmuch_config_list_value (notmuch_config_list_t *config_list);
@@ -2257,6 +2325,7 @@ notmuch_config_list_destroy (notmuch_config_list_t *config_list);
  * added to the index.  At the moment it is a featureless stub.
  *
  * @since libnotmuch 5.1 (notmuch 0.26)
+ * @retval NULL in case of error
  */
 notmuch_indexopts_t *
 notmuch_database_get_default_indexopts (notmuch_database_t *db);
@@ -2312,7 +2381,7 @@ notmuch_indexopts_destroy (notmuch_indexopts_t *options);
  */
 notmuch_bool_t
 notmuch_built_with (const char *name);
-/* @} */
+/**@}*/
 
 #pragma GCC visibility pop
 
index dd69149423072f396cbc096cfd10ab53c1e79087..10809aa37453306ef9fc117c3d24f34fa52af581 100644 (file)
 #include "parse-time-vrp.h"
 #include "parse-time-string.h"
 
-#define PREFIX "date:"
-
-/* See *ValueRangeProcessor in xapian-core/api/valuerangeproc.cc */
-Xapian::valueno
-ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
+Xapian::Query
+ParseTimeRangeProcessor::operator() (const std::string &begin, const std::string &end)
 {
-    time_t t, now;
-    std::string b;
-
-    /* Require date: prefix in start of the range... */
-    if (STRNCMP_LITERAL (begin.c_str (), PREFIX))
-       return Xapian::BAD_VALUENO;
-
-    /* ...and remove it. */
-    begin.erase (0, sizeof (PREFIX) - 1);
-    b = begin;
+    double from = DBL_MIN, to = DBL_MAX;
+    time_t parsed_time, now;
+    std::string str;
 
     /* Use the same 'now' for begin and end. */
     if (time (&now) == (time_t) -1)
-       return Xapian::BAD_VALUENO;
+       throw Xapian::QueryParserError ("unable to get current time");
 
     if (!begin.empty ()) {
-       if (parse_time_string (begin.c_str (), &t, &now, PARSE_TIME_ROUND_DOWN))
-           return Xapian::BAD_VALUENO;
-
-       begin.assign (Xapian::sortable_serialise ((double) t));
+       if (parse_time_string (begin.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN))
+           throw Xapian::QueryParserError ("Didn't understand date specification '" + begin + "'");
+       else
+           from = (double) parsed_time;
     }
 
     if (!end.empty ()) {
-       if (end == "!" && ! b.empty ())
-           end = b;
-
-       if (parse_time_string (end.c_str (), &t, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
-           return Xapian::BAD_VALUENO;
-
-       end.assign (Xapian::sortable_serialise ((double) t));
+       if (end == "!" && ! begin.empty ())
+           str = begin;
+       else
+           str = end;
+
+       if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
+           throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'");
+       else
+           to = (double) parsed_time;
     }
 
-    return valno;
+    return Xapian::Query (Xapian::Query::OP_VALUE_RANGE, slot,
+                         Xapian::sortable_serialise (from),
+                         Xapian::sortable_serialise (to));
 }
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
 /* XXX TODO: is throwing an exception the right thing to do here? */
-Xapian::Query DateFieldProcessor::operator()(const std::string & str) {
-    time_t from, to, now;
+Xapian::Query
+DateFieldProcessor::operator() (const std::string & str)
+{
+    double from = DBL_MIN, to = DBL_MAX;
+    time_t parsed_time, now;
 
     /* Use the same 'now' for begin and end. */
     if (time (&now) == (time_t) -1)
-       throw Xapian::QueryParserError("Unable to get current time");
+       throw Xapian::QueryParserError ("Unable to get current time");
 
-    if (parse_time_string (str.c_str (), &from, &now, PARSE_TIME_ROUND_DOWN))
+    if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN))
        throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'");
+    else
+       from = (double) parsed_time;
 
-    if (parse_time_string (str.c_str (), &to, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
+    if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
        throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'");
+    else
+       to = (double) parsed_time;
 
-    return Xapian::Query(Xapian::Query::OP_AND,
-                        Xapian::Query(Xapian::Query::OP_VALUE_GE, 0, Xapian::sortable_serialise ((double) from)),
-                        Xapian::Query(Xapian::Query::OP_VALUE_LE, 0, Xapian::sortable_serialise ((double) to)));
+    return Xapian::Query (Xapian::Query::OP_VALUE_RANGE, slot,
+                         Xapian::sortable_serialise (from),
+                         Xapian::sortable_serialise (to));
 }
-#endif
index c024dba2fed59035b4adcf57b0a5f04435efb3d7..f495e7161a9a6f3cf8b108f17c3bb54a57976229 100644 (file)
 #include <xapian.h>
 
 /* see *ValueRangeProcessor in xapian-core/include/xapian/queryparser.h */
-class ParseTimeValueRangeProcessor : public Xapian::ValueRangeProcessor {
-protected:
-    Xapian::valueno valno;
+class ParseTimeRangeProcessor : public Xapian::RangeProcessor {
 
 public:
-    ParseTimeValueRangeProcessor (Xapian::valueno slot_)
-       : valno(slot_) { }
+    ParseTimeRangeProcessor (Xapian::valueno slot_, const std::string prefix_)
+       :  Xapian::RangeProcessor(slot_, prefix_, 0) { }
 
-    Xapian::valueno operator() (std::string &begin, std::string &end);
+    Xapian::Query operator() (const std::string &begin, const std::string &end);
 };
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
 class DateFieldProcessor : public Xapian::FieldProcessor {
+private:
+    Xapian::valueno slot;
+public:
+    DateFieldProcessor(Xapian::valueno slot_) : slot(slot_) { };
     Xapian::Query operator()(const std::string & str);
 };
-#endif
+
 #endif /* NOTMUCH_PARSE_TIME_VRP_H */
index c39f59156d1f9e32f53bfd09bac21340328d2480..b980b7f06669402c2f592ed0159a8c034ca1a157 100644 (file)
@@ -24,8 +24,6 @@
 #include "query-fp.h"
 #include <iostream>
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
-
 Xapian::Query
 QueryFieldProcessor::operator() (const std::string & name)
 {
@@ -40,4 +38,3 @@ QueryFieldProcessor::operator() (const std::string & name)
 
     return parser.parse_query (expansion, NOTMUCH_QUERY_PARSER_FLAGS);
 }
-#endif
index d6e4b31394f4f8eef3d3eebd9ee6b704be26284f..beaaf4054cd921b508b25acaa5a719f072033ab3 100644 (file)
 #include <xapian.h>
 #include "notmuch.h"
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
 class QueryFieldProcessor : public Xapian::FieldProcessor {
- protected:
+protected:
     Xapian::QueryParser &parser;
     notmuch_database_t *notmuch;
 
- public:
+public:
     QueryFieldProcessor (Xapian::QueryParser &parser_, notmuch_database_t *notmuch_)
-       : parser(parser_), notmuch(notmuch_) { };
+       : parser (parser_), notmuch (notmuch_)
+    {
+    };
 
-    Xapian::Query operator()(const std::string & str);
+    Xapian::Query operator() (const std::string & str);
 };
-#endif
+
 #endif /* NOTMUCH_QUERY_FP_H */
index 7fdf992de7162a758d6ea869515ad07e15aaa4a6..792aba219fa48d53e3b761089eac1d4349b5c0fb 100644 (file)
@@ -71,12 +71,14 @@ static bool
 _debug_query (void)
 {
     char *env = getenv ("NOTMUCH_DEBUG_QUERY");
+
     return (env && strcmp (env, "") != 0);
 }
 
 /* Explicit destructor call for placement new */
 static int
-_notmuch_query_destructor (notmuch_query_t *query) {
+_notmuch_query_destructor (notmuch_query_t *query)
+{
     query->xapian_query.~Query();
     query->terms.~set<std::string>();
     return 0;
@@ -123,12 +125,12 @@ _notmuch_query_ensure_parsed (notmuch_query_t *query)
     try {
        query->xapian_query =
            query->notmuch->query_parser->
-               parse_query (query->query_string, NOTMUCH_QUERY_PARSER_FLAGS);
+           parse_query (query->query_string, NOTMUCH_QUERY_PARSER_FLAGS);
 
-       /* Xapian doesn't support skip_to on terms from a query since
-       *  they are unordered, so cache a copy of all terms in
-       *  something searchable.
-       */
+       /* Xapian doesn't support skip_to on terms from a query since
+        *  they are unordered, so cache a copy of all terms in
+        *  something searchable.
+        */
 
        for (Xapian::TermIterator t = query->xapian_query.get_terms_begin ();
             t != query->xapian_query.get_terms_end (); ++t)
@@ -137,7 +139,7 @@ _notmuch_query_ensure_parsed (notmuch_query_t *query)
        query->parsed = true;
 
     } catch (const Xapian::Error &error) {
-       if (!query->notmuch->exception_reported) {
+       if (! query->notmuch->exception_reported) {
            _notmuch_database_log (query->notmuch,
                                   "A Xapian exception occurred parsing query: %s\n",
                                   error.get_msg ().c_str ());
@@ -188,7 +190,7 @@ notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag)
        return status;
 
     term = talloc_asprintf (query, "%s%s", _find_prefix ("tag"), tag);
-    if (query->terms.count(term) != 0)
+    if (query->terms.count (term) != 0)
        return NOTMUCH_STATUS_IGNORED;
 
     _notmuch_string_list_append (query->exclude_terms, term);
@@ -236,7 +238,7 @@ notmuch_query_search_messages_st (notmuch_query_t *query,
 
 notmuch_status_t
 notmuch_query_search_messages (notmuch_query_t *query,
-                                 notmuch_messages_t **out)
+                              notmuch_messages_t **out)
 {
     return _notmuch_query_search_documents (query, "mail", out);
 }
@@ -278,8 +280,7 @@ _notmuch_query_search_documents (notmuch_query_t *query,
        Xapian::MSetIterator iterator;
 
        if (strcmp (query_string, "") == 0 ||
-           strcmp (query_string, "*") == 0)
-       {
+           strcmp (query_string, "*") == 0) {
            final_query = mail_query;
        } else {
            final_query = Xapian::Query (Xapian::Query::OP_AND,
@@ -291,15 +292,14 @@ _notmuch_query_search_documents (notmuch_query_t *query,
            exclude_query = _notmuch_exclude_tags (query);
 
            if (query->omit_excluded == NOTMUCH_EXCLUDE_TRUE ||
-               query->omit_excluded == NOTMUCH_EXCLUDE_ALL)
-           {
+               query->omit_excluded == NOTMUCH_EXCLUDE_ALL) {
                final_query = Xapian::Query (Xapian::Query::OP_AND_NOT,
                                             final_query, exclude_query);
            } else { /* NOTMUCH_EXCLUDE_FLAG */
                exclude_query = Xapian::Query (Xapian::Query::OP_AND,
-                                          exclude_query, final_query);
+                                              exclude_query, final_query);
 
-               enquire.set_weighting_scheme (Xapian::BoolWeight());
+               enquire.set_weighting_scheme (Xapian::BoolWeight ());
                enquire.set_query (exclude_query);
 
                mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
@@ -318,7 +318,7 @@ _notmuch_query_search_documents (notmuch_query_t *query,
        }
 
 
-       enquire.set_weighting_scheme (Xapian::BoolWeight());
+       enquire.set_weighting_scheme (Xapian::BoolWeight ());
 
        switch (query->sort) {
        case NOTMUCH_SORT_OLDEST_FIRST:
@@ -354,10 +354,10 @@ _notmuch_query_search_documents (notmuch_query_t *query,
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch,
                               "A Xapian exception occurred performing query: %s\n",
-                              error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        _notmuch_database_log_append (notmuch,
-                              "Query string was: %s\n",
-                              query->query_string);
+                                     "Query string was: %s\n",
+                                     query->query_string);
 
        notmuch->exception_reported = true;
        talloc_free (messages);
@@ -408,8 +408,7 @@ _notmuch_mset_messages_get (notmuch_messages_t *messages)
                                       &status);
 
     if (message == NULL &&
-       status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
-    {
+       status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
        INTERNAL_ERROR ("a messages iterator contains a non-existent document ID.\n");
     }
 
@@ -439,8 +438,8 @@ _notmuch_doc_id_set_init (void *ctx,
     unsigned char *bitmap;
 
     for (unsigned int i = 0; i < arr->len; i++)
-       max = MAX(max, g_array_index (arr, unsigned int, i));
-    bitmap = talloc_zero_array (ctx, unsigned char, DOCIDSET_WORD(max) + 1);
+       max = MAX (max, g_array_index (arr, unsigned int, i));
+    bitmap = talloc_zero_array (ctx, unsigned char, DOCIDSET_WORD (max) + 1);
 
     if (bitmap == NULL)
        return false;
@@ -450,7 +449,7 @@ _notmuch_doc_id_set_init (void *ctx,
 
     for (unsigned int i = 0; i < arr->len; i++) {
        unsigned int doc_id = g_array_index (arr, unsigned int, i);
-       bitmap[DOCIDSET_WORD(doc_id)] |= 1 << DOCIDSET_BIT(doc_id);
+       bitmap[DOCIDSET_WORD (doc_id)] |= 1 << DOCIDSET_BIT (doc_id);
     }
 
     return true;
@@ -462,7 +461,7 @@ _notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
 {
     if (doc_id >= doc_ids->bound)
        return false;
-    return doc_ids->bitmap[DOCIDSET_WORD(doc_id)] & (1 << DOCIDSET_BIT(doc_id));
+    return doc_ids->bitmap[DOCIDSET_WORD (doc_id)] & (1 << DOCIDSET_BIT (doc_id));
 }
 
 void
@@ -470,7 +469,7 @@ _notmuch_doc_id_set_remove (notmuch_doc_id_set_t *doc_ids,
                            unsigned int doc_id)
 {
     if (doc_id < doc_ids->bound)
-       doc_ids->bitmap[DOCIDSET_WORD(doc_id)] &= ~(1 << DOCIDSET_BIT(doc_id));
+       doc_ids->bitmap[DOCIDSET_WORD (doc_id)] &= ~(1 << DOCIDSET_BIT (doc_id));
 }
 
 /* Glib objects force use to use a talloc destructor as well, (but not
@@ -489,7 +488,7 @@ _notmuch_threads_destructor (notmuch_threads_t *threads)
 notmuch_status_t
 notmuch_query_search_threads_st (notmuch_query_t *query, notmuch_threads_t **out)
 {
-    return notmuch_query_search_threads(query, out);
+    return notmuch_query_search_threads (query, out);
 }
 
 notmuch_status_t
@@ -624,8 +623,7 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign
        Xapian::MSet mset;
 
        if (strcmp (query_string, "") == 0 ||
-           strcmp (query_string, "*") == 0)
-       {
+           strcmp (query_string, "*") == 0) {
            final_query = mail_query;
        } else {
            final_query = Xapian::Query (Xapian::Query::OP_AND,
@@ -635,10 +633,10 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign
        exclude_query = _notmuch_exclude_tags (query);
 
        final_query = Xapian::Query (Xapian::Query::OP_AND_NOT,
-                                        final_query, exclude_query);
+                                    final_query, exclude_query);
 
-       enquire.set_weighting_scheme(Xapian::BoolWeight());
-       enquire.set_docid_order(Xapian::Enquire::ASCENDING);
+       enquire.set_weighting_scheme (Xapian::BoolWeight ());
+       enquire.set_docid_order (Xapian::Enquire::ASCENDING);
 
        if (_debug_query ()) {
            fprintf (stderr, "Exclude query is:\n%s\n",
@@ -657,12 +655,12 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign
        mset = enquire.get_mset (0, 1,
                                 notmuch->xapian_db->get_doccount ());
 
-       count = mset.get_matches_estimated();
+       count = mset.get_matches_estimated ();
 
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch,
                               "A Xapian exception occurred performing query: %s\n",
-                              error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        _notmuch_database_log_append (notmuch,
                                      "Query string was: %s\n",
                                      query->query_string);
index 5d4cf80aeea6f145630002c45ac31c4bda5f526c..0feb50e586ba84f1c4ef84cb62e78ea679b472c3 100644 (file)
@@ -26,7 +26,6 @@
 #include "notmuch-private.h"
 #include "database-private.h"
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
 static void
 compile_regex (regex_t &regexp, const char *str)
 {
@@ -124,12 +123,13 @@ bool
 RegexpPostingSource::check (Xapian::docid did, unused (double min_wt))
 {
     started_ = true;
-    if (!it_.check (did) || at_end ())
+    if (! it_.check (did) || at_end ())
        return false;
     return (regexec (&regexp_, (*it_).c_str (), 0, NULL, 0) == 0);
 }
 
-static inline Xapian::valueno _find_slot (std::string prefix)
+static inline Xapian::valueno
+_find_slot (std::string prefix)
 {
     if (prefix == "from")
        return NOTMUCH_VALUE_FROM;
@@ -145,11 +145,11 @@ RegexpFieldProcessor::RegexpFieldProcessor (std::string prefix,
                                            notmuch_field_flag_t options_,
                                            Xapian::QueryParser &parser_,
                                            notmuch_database_t *notmuch_)
-       : slot (_find_slot (prefix)),
-         term_prefix (_find_prefix (prefix.c_str ())),
-         options (options_),
-         parser (parser_),
-         notmuch (notmuch_)
+    : slot (_find_slot (prefix)),
+    term_prefix (_find_prefix (prefix.c_str ())),
+    options (options_),
+    parser (parser_),
+    notmuch (notmuch_)
 {
 };
 
@@ -158,17 +158,17 @@ RegexpFieldProcessor::operator() (const std::string & str)
 {
     if (str.empty ()) {
        if (options & NOTMUCH_FIELD_PROBABILISTIC) {
-           return Xapian::Query(Xapian::Query::OP_AND_NOT,
-                            Xapian::Query::MatchAll,
-                            Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix));
+           return Xapian::Query (Xapian::Query::OP_AND_NOT,
+                                 Xapian::Query::MatchAll,
+                                 Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix));
        } else {
            return Xapian::Query (term_prefix);
        }
     }
 
     if (str.at (0) == '/') {
-       if (str.length() > 1 && str.at (str.size () - 1) == '/'){
-           std::string regexp_str = str.substr(1,str.size () - 2);
+       if (str.length () > 1 && str.at (str.size () - 1) == '/') {
+           std::string regexp_str = str.substr (1, str.size () - 2);
            if (slot != Xapian::BAD_VALUENO) {
                RegexpPostingSource *postings = new RegexpPostingSource (slot, regexp_str);
                return Xapian::Query (postings->release ());
@@ -176,14 +176,14 @@ RegexpFieldProcessor::operator() (const std::string & str)
                std::vector<std::string> terms;
                regex_t regexp;
 
-               compile_regex(regexp, regexp_str.c_str ());
+               compile_regex (regexp, regexp_str.c_str ());
                for (Xapian::TermIterator it = notmuch->xapian_db->allterms_begin (term_prefix);
                     it != notmuch->xapian_db->allterms_end (); ++it) {
-                   if (regexec (&regexp, (*it).c_str () + term_prefix.size(),
+                   if (regexec (&regexp, (*it).c_str () + term_prefix.size (),
                                 0, NULL, 0) == 0)
-                       terms.push_back(*it);
+                       terms.push_back (*it);
                }
-               return Xapian::Query (Xapian::Query::OP_OR, terms.begin(), terms.end());
+               return Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
            }
        } else {
            throw Xapian::QueryParserError ("unmatched regex delimiter in '" + str + "'");
@@ -207,4 +207,3 @@ RegexpFieldProcessor::operator() (const std::string & str)
        }
     }
 }
-#endif
index d5f93445400d875585f7c1e788ae03ab338d3374..a8cca2431d737d9284c66ba311f9f9f26f4d0268 100644 (file)
@@ -24,7 +24,7 @@
 
 #ifndef NOTMUCH_REGEXP_FIELDS_H
 #define NOTMUCH_REGEXP_FIELDS_H
-#if HAVE_XAPIAN_FIELD_PROCESSOR
+
 #include <sys/types.h>
 #include <regex.h>
 #include "database-private.h"
@@ -35,7 +35,7 @@
  */
 class RegexpPostingSource : public Xapian::PostingSource
 {
- protected:
+protected:
     const Xapian::valueno slot_;
     regex_t regexp_;
     Xapian::Database db_;
@@ -46,7 +46,7 @@ class RegexpPostingSource : public Xapian::PostingSource
     RegexpPostingSource (const RegexpPostingSource &);
     RegexpPostingSource &operator= (const RegexpPostingSource &);
 
- public:
+public:
     RegexpPostingSource (Xapian::valueno slot, const std::string &regexp);
     ~RegexpPostingSource ();
     void init (const Xapian::Database &db);
@@ -62,20 +62,22 @@ class RegexpPostingSource : public Xapian::PostingSource
 
 
 class RegexpFieldProcessor : public Xapian::FieldProcessor {
- protected:
+protected:
     Xapian::valueno slot;
     std::string term_prefix;
     notmuch_field_flag_t options;
     Xapian::QueryParser &parser;
     notmuch_database_t *notmuch;
 
- public:
+public:
     RegexpFieldProcessor (std::string prefix, notmuch_field_flag_t options,
                          Xapian::QueryParser &parser_, notmuch_database_t *notmuch_);
 
-    ~RegexpFieldProcessor () { };
+    ~RegexpFieldProcessor ()
+    {
+    };
 
-    Xapian::Query operator()(const std::string & str);
+    Xapian::Query operator() (const std::string & str);
 };
-#endif
+
 #endif /* NOTMUCH_REGEXP_FIELDS_H */
index cb55b49a4f11a324744ccccc3e7876c945d297ca..d1a76ee6de061fffa83c7a924596c03c5c1d0acb 100644 (file)
@@ -55,6 +55,7 @@ char *
 _notmuch_sha1_of_file (const char *filename)
 {
     FILE *file;
+
 #define BLOCK_SIZE 4096
     unsigned char block[BLOCK_SIZE];
     size_t bytes_read;
index 9c3ae7ef2a30eb6127cb3b180f2385ee5da0d5ea..f3dac6755b2957694d8cd04aa04ebd785395e53f 100644 (file)
@@ -67,8 +67,8 @@ _notmuch_string_list_append (notmuch_string_list_t *list,
 static int
 cmpnode (const void *pa, const void *pb)
 {
-    notmuch_string_node_t *a = *(notmuch_string_node_t * const *)pa;
-    notmuch_string_node_t *b = *(notmuch_string_node_t * const *)pb;
+    notmuch_string_node_t *a = *(notmuch_string_node_t *const *) pa;
+    notmuch_string_node_t *b = *(notmuch_string_node_t *const *) pb;
 
     return strcmp (a->string, b->string);
 }
@@ -92,7 +92,7 @@ _notmuch_string_list_sort (notmuch_string_list_t *list)
     qsort (nodes, list->length, sizeof (*nodes), cmpnode);
 
     for (i = 0; i < list->length - 1; ++i)
-       nodes[i]->next = nodes[i+1];
+       nodes[i]->next = nodes[i + 1];
     nodes[i]->next = NULL;
     list->head = nodes[0];
     list->tail = &nodes[i]->next;
index 7327700653bcab8ead048b55257617dfe9f52bc3..97a65211992654b629269a74bdde0d6bbf82504f 100644 (file)
@@ -24,8 +24,6 @@
 #include "thread-fp.h"
 #include <iostream>
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
-
 Xapian::Query
 ThreadFieldProcessor::operator() (const std::string & str)
 {
@@ -64,4 +62,3 @@ ThreadFieldProcessor::operator() (const std::string & str)
     }
 
 }
-#endif
index 47c066c1067967e2d8f9939c3ae0bc69fea2b8b0..00bf1aa286f78cfd6acaa6268fc291e72984e796 100644 (file)
 #include <xapian.h>
 #include "notmuch.h"
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
 class ThreadFieldProcessor : public Xapian::FieldProcessor {
- protected:
+protected:
     Xapian::QueryParser &parser;
     notmuch_database_t *notmuch;
 
- public:
+public:
     ThreadFieldProcessor (Xapian::QueryParser &parser_, notmuch_database_t *notmuch_)
-       : parser(parser_), notmuch(notmuch_) { };
+       : parser (parser_), notmuch (notmuch_)
+    {
+    };
 
-    Xapian::Query operator()(const std::string & str);
+    Xapian::Query operator() (const std::string & str);
 };
-#endif
+
 #endif /* NOTMUCH_THREAD_FP_H */
index fd0e1393be51f17011ca3b96b190f8a8159ce606..173460084e2ce31cb8e31fc878c0377b1676f9e2 100644 (file)
 #include "database-private.h"
 
 #include <gmime/gmime.h>
-#include <glib.h> /* GHashTable */
+#include <glib.h>                                       /* GHashTable */
 
 #ifdef DEBUG_THREADING
-#define THREAD_DEBUG(format, ...) fprintf(stderr, format " (%s).\n", ##__VA_ARGS__, __location__)
+#define THREAD_DEBUG(format, ...) fprintf (stderr, format " (%s).\n", ##__VA_ARGS__, __location__)
 #else
-#define THREAD_DEBUG(format, ...) do {} while (0) /* ignored */
+#define THREAD_DEBUG(format, ...) do {} while (0)       /* ignored */
 #endif
 
 struct _notmuch_thread {
@@ -165,8 +165,8 @@ _resolve_thread_authors_string (notmuch_thread_t *thread)
     g_ptr_array_free (thread->matched_authors_array, true);
     thread->matched_authors_array = NULL;
 
-    if (!thread->authors)
-       thread->authors = talloc_strdup(thread, "");
+    if (! thread->authors)
+       thread->authors = talloc_strdup (thread, "");
 }
 
 /* clean up the ugly "Lastname, Firstname" format that some mail systems
@@ -180,14 +180,14 @@ static char *
 _thread_cleanup_author (notmuch_thread_t *thread,
                        const char *author, const char *from)
 {
-    char *clean_author,*test_author;
+    char *clean_author, *test_author;
     const char *comma;
     char *blank;
-    int fname,lname;
+    int fname, lname;
 
     if (author == NULL)
        return NULL;
-    clean_author = talloc_strdup(thread, author);
+    clean_author = talloc_strdup (thread, author);
     if (clean_author == NULL)
        return NULL;
     /* check if there's a comma in the name and that there's a
@@ -196,34 +196,34 @@ _thread_cleanup_author (notmuch_thread_t *thread,
      * one character long ",\0").
      * Otherwise just return the copy of the original author name that
      * we just made*/
-    comma = strchr(author,',');
-    if (comma && strlen(comma) > 1) {
+    comma = strchr (author, ',');
+    if (comma && strlen (comma) > 1) {
        /* let's assemble what we think is the correct name */
        lname = comma - author;
 
        /* Skip all the spaces after the comma */
-       fname = strlen(author) - lname - 1;
+       fname = strlen (author) - lname - 1;
        comma += 1;
        while (*comma == ' ') {
            fname -= 1;
            comma += 1;
        }
-       strncpy(clean_author, comma, fname);
+       strncpy (clean_author, comma, fname);
 
-       *(clean_author+fname) = ' ';
-       strncpy(clean_author + fname + 1, author, lname);
-       *(clean_author+fname+1+lname) = '\0';
+       *(clean_author + fname) = ' ';
+       strncpy (clean_author + fname + 1, author, lname);
+       *(clean_author + fname + 1 + lname) = '\0';
        /* make a temporary copy and see if it matches the email */
-       test_author = talloc_strdup(thread,clean_author);
+       test_author = talloc_strdup (thread, clean_author);
 
-       blank=strchr(test_author,' ');
+       blank = strchr (test_author, ' ');
        while (blank != NULL) {
            *blank = '.';
-           blank=strchr(test_author,' ');
+           blank = strchr (test_author, ' ');
        }
-       if (strcasestr(from, test_author) == NULL)
+       if (strcasestr (from, test_author) == NULL)
            /* we didn't identify this as part of the email address
-           * so let's punt and return the original author */
+            * so let's punt and return the original author */
            strcpy (clean_author, author);
     }
     return clean_author;
@@ -251,16 +251,14 @@ _thread_add_message (notmuch_thread_t *thread,
     if (omit_exclude != NOTMUCH_EXCLUDE_FALSE) {
        for (tags = notmuch_message_get_tags (message);
             notmuch_tags_valid (tags);
-            notmuch_tags_move_to_next (tags))
-       {
+            notmuch_tags_move_to_next (tags)) {
            tag = notmuch_tags_get (tags);
            /* Is message excluded? */
            for (notmuch_string_node_t *term = exclude_terms->head;
                 term != NULL;
-                term = term->next)
-           {
+                term = term->next) {
                /* Check for an empty string, and then ignore initial 'K'. */
-               if (*(term->string) && strcmp(tag, (term->string + 1)) == 0) {
+               if (*(term->string) && strcmp (tag, (term->string + 1)) == 0) {
                    message_excluded = true;
                    break;
                }
@@ -309,8 +307,7 @@ _thread_add_message (notmuch_thread_t *thread,
 
     for (tags = notmuch_message_get_tags (message);
         notmuch_tags_valid (tags);
-        notmuch_tags_move_to_next (tags))
-    {
+        notmuch_tags_move_to_next (tags)) {
        tag = notmuch_tags_get (tags);
        g_hash_table_insert (thread->tags, xstrdup (tag), NULL);
     }
@@ -338,12 +335,12 @@ _thread_set_subject_from_message (notmuch_thread_t *thread,
 
        cleaned_subject = talloc_strndup (thread,
                                          subject + 4,
-                                         strlen(subject) - 4);
+                                         strlen (subject) - 4);
     } else {
        cleaned_subject = talloc_strdup (thread, subject);
     }
 
-    if (! EMPTY_STRING(cleaned_subject)) {
+    if (! EMPTY_STRING (cleaned_subject)) {
        if (thread->subject)
            talloc_free (thread->subject);
 
@@ -354,14 +351,16 @@ _thread_set_subject_from_message (notmuch_thread_t *thread,
 /* Add a message to this thread which is known to match the original
  * search specification. The 'sort' parameter controls whether the
  * oldest or newest matching subject is applied to the thread as a
- * whole. */
-static void
+ * whole. Returns 0 on success.
+ */
+static int
 _thread_add_matched_message (notmuch_thread_t *thread,
                             notmuch_message_t *message,
                             notmuch_sort_t sort)
 {
     time_t date;
     notmuch_message_t *hashed_message;
+    notmuch_bool_t is_set;
 
     date = notmuch_message_get_date (message);
 
@@ -373,34 +372,38 @@ _thread_add_matched_message (notmuch_thread_t *thread,
 
     if (date > thread->newest || ! thread->matched_messages) {
        thread->newest = date;
-       const char *cur_subject = notmuch_thread_get_subject(thread);
-       if (sort != NOTMUCH_SORT_OLDEST_FIRST || EMPTY_STRING(cur_subject))
+       const char *cur_subject = notmuch_thread_get_subject (thread);
+       if (sort != NOTMUCH_SORT_OLDEST_FIRST || EMPTY_STRING (cur_subject))
            _thread_set_subject_from_message (thread, message);
     }
 
-    if (!notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED))
+    if (notmuch_message_get_flag_st (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, &is_set))
+       return -1;
+    if (! is_set)
        thread->matched_messages++;
 
     if (g_hash_table_lookup_extended (thread->message_hash,
-                           notmuch_message_get_message_id (message), NULL,
-                           (void **) &hashed_message)) {
+                                     notmuch_message_get_message_id (message), NULL,
+                                     (void **) &hashed_message)) {
        notmuch_message_set_flag (hashed_message,
                                  NOTMUCH_MESSAGE_FLAG_MATCH, 1);
     }
 
     _thread_add_matched_author (thread, _notmuch_message_get_author (hashed_message));
+    return 0;
 }
 
 static bool
-_parent_via_in_reply_to (notmuch_thread_t *thread, notmuch_message_t *message) {
+_parent_via_in_reply_to (notmuch_thread_t *thread, notmuch_message_t *message)
+{
     notmuch_message_t *parent;
     const char *in_reply_to;
 
     in_reply_to = _notmuch_message_get_in_reply_to (message);
-    THREAD_DEBUG("checking message = %s in_reply_to=%s\n",
-                notmuch_message_get_message_id (message), in_reply_to);
+    THREAD_DEBUG ("checking message = %s in_reply_to=%s\n",
+                 notmuch_message_get_message_id (message), in_reply_to);
 
-    if (in_reply_to && (! EMPTY_STRING(in_reply_to)) &&
+    if (in_reply_to && (! EMPTY_STRING (in_reply_to)) &&
        g_hash_table_lookup_extended (thread->message_hash,
                                      in_reply_to, NULL,
                                      (void **) &parent)) {
@@ -420,32 +423,32 @@ _parent_or_toplevel (notmuch_thread_t *thread, notmuch_message_t *message)
     const notmuch_string_list_t *references =
        _notmuch_message_get_references (message);
 
-    THREAD_DEBUG("trying to reparent via references: %s\n",
-                    notmuch_message_get_message_id (message));
+    THREAD_DEBUG ("trying to reparent via references: %s\n",
+                 notmuch_message_get_message_id (message));
 
     for (notmuch_string_node_t *ref_node = references->head;
         ref_node; ref_node = ref_node->next) {
-       THREAD_DEBUG("checking reference=%s\n", ref_node->string);
+       THREAD_DEBUG ("checking reference=%s\n", ref_node->string);
        if ((g_hash_table_lookup_extended (thread->message_hash,
                                           ref_node->string, NULL,
                                           (void **) &new_parent))) {
            size_t new_depth = _notmuch_message_get_thread_depth (new_parent);
-           THREAD_DEBUG("got depth %lu\n", new_depth);
-           if (new_depth > max_depth || !parent) {
-               THREAD_DEBUG("adding at depth %lu parent=%s\n", new_depth, ref_node->string);
+           THREAD_DEBUG ("got depth %lu\n", new_depth);
+           if (new_depth > max_depth || ! parent) {
+               THREAD_DEBUG ("adding at depth %lu parent=%s\n", new_depth, ref_node->string);
                max_depth = new_depth;
                parent = new_parent;
            }
        }
     }
     if (parent) {
-       THREAD_DEBUG("adding reply %s to parent=%s\n",
-                notmuch_message_get_message_id (message),
-                notmuch_message_get_message_id (parent));
+       THREAD_DEBUG ("adding reply %s to parent=%s\n",
+                     notmuch_message_get_message_id (message),
+                     notmuch_message_get_message_id (parent));
        _notmuch_message_add_reply (parent, message);
     } else {
-       THREAD_DEBUG("adding as toplevel %s\n",
-                notmuch_message_get_message_id (message));
+       THREAD_DEBUG ("adding as toplevel %s\n",
+                     notmuch_message_get_message_id (message));
        _notmuch_message_list_add_message (thread->toplevel_list, message);
     }
 }
@@ -479,22 +482,21 @@ _resolve_thread_relationships (notmuch_thread_t *thread)
      */
     if (first_node) {
        message = first_node->message;
-       THREAD_DEBUG("checking first message  %s\n",
-                    notmuch_message_get_message_id (message));
+       THREAD_DEBUG ("checking first message  %s\n",
+                     notmuch_message_get_message_id (message));
 
-        if (_notmuch_message_list_empty (maybe_toplevel_list) ||
+       if (_notmuch_message_list_empty (maybe_toplevel_list) ||
            ! _parent_via_in_reply_to (thread, message)) {
 
-           THREAD_DEBUG("adding first message as toplevel = %s\n",
-                        notmuch_message_get_message_id (message));
+           THREAD_DEBUG ("adding first message as toplevel = %s\n",
+                         notmuch_message_get_message_id (message));
            _notmuch_message_list_add_message (maybe_toplevel_list, message);
        }
     }
 
     for (notmuch_messages_t *messages = _notmuch_messages_create (maybe_toplevel_list);
         notmuch_messages_valid (messages);
-        notmuch_messages_move_to_next (messages))
-    {
+        notmuch_messages_move_to_next (messages)) {
        notmuch_message_t *message = notmuch_messages_get (messages);
        _notmuch_message_label_depths (message, 0);
     }
@@ -616,8 +618,7 @@ _notmuch_thread_create (void *ctx,
 
     for (;
         notmuch_messages_valid (messages);
-        notmuch_messages_move_to_next (messages))
-    {
+        notmuch_messages_move_to_next (messages)) {
        unsigned int doc_id;
 
        message = notmuch_messages_get (messages);
@@ -629,7 +630,10 @@ _notmuch_thread_create (void *ctx,
 
        if ( _notmuch_doc_id_set_contains (match_set, doc_id)) {
            _notmuch_doc_id_set_remove (match_set, doc_id);
-           _thread_add_matched_message (thread, message, sort);
+           if (_thread_add_matched_message (thread, message, sort)) {
+               thread = NULL;
+               goto DONE;
+           }
        }
 
        _notmuch_message_close (message);
index a93cbb31daed9d54f0ef8fdfe97414952ebba85c..f552e03a780f56e0b6926550e52c649bc3f1b792 100644 (file)
@@ -36,6 +36,9 @@ typedef struct mime_node_context {
     GMimeMessage *mime_message;
     _notmuch_message_crypto_t *msg_crypto;
 
+    /* repaired/unmangled parts that will need to be cleaned up */
+    GSList *repaired_parts;
+
     /* Context provided by the caller. */
     _notmuch_crypto_t *crypto;
 } mime_node_context_t;
@@ -52,10 +55,22 @@ _mime_node_context_free (mime_node_context_t *res)
     if (res->stream)
        g_object_unref (res->stream);
 
+    if (res->repaired_parts)
+       g_slist_free_full (res->repaired_parts, g_object_unref);
+
     return 0;
 }
 
-const _notmuch_message_crypto_t*
+/* keep track of objects that need to be destroyed when the mime node
+ * context goes away. */
+static void
+_mime_node_context_track_repaired_part (mime_node_context_t *ctx, GMimeObject *part)
+{
+    if (part)
+       ctx->repaired_parts = g_slist_prepend (ctx->repaired_parts, part);
+}
+
+const _notmuch_message_crypto_t *
 mime_node_get_message_crypto_status (mime_node_t *node)
 {
     return node->ctx->msg_crypto;
@@ -97,8 +112,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
        notmuch_filenames_t *filenames;
        for (filenames = notmuch_message_get_filenames (message);
             notmuch_filenames_valid (filenames);
-            notmuch_filenames_move_to_next (filenames))
-       {
+            notmuch_filenames_move_to_next (filenames)) {
            filename = notmuch_filenames_get (filenames);
            fd = open (filename, O_RDONLY);
            if (fd != -1)
@@ -109,27 +123,27 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
        if (fd == -1) {
            /* Give up */
            fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
-               status = NOTMUCH_STATUS_FILE_ERROR;
-               goto DONE;
-           }
+           status = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
        }
+    }
 
     mctx->stream = g_mime_stream_gzfile_new (fd);
-    if (!mctx->stream) {
+    if (! mctx->stream) {
        fprintf (stderr, "Out of memory.\n");
        status = NOTMUCH_STATUS_OUT_OF_MEMORY;
        goto DONE;
     }
 
     mctx->parser = g_mime_parser_new_with_stream (mctx->stream);
-    if (!mctx->parser) {
+    if (! mctx->parser) {
        fprintf (stderr, "Out of memory.\n");
        status = NOTMUCH_STATUS_OUT_OF_MEMORY;
        goto DONE;
     }
 
     mctx->mime_message = g_mime_parser_construct_message (mctx->parser, NULL);
-    if (!mctx->mime_message) {
+    if (! mctx->mime_message) {
        fprintf (stderr, "Failed to parse %s\n", filename);
        status = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
@@ -153,7 +167,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
     *root_out = root;
     return NOTMUCH_STATUS_SUCCESS;
 
-DONE:
+  DONE:
     talloc_free (root);
     return status;
 }
@@ -171,12 +185,33 @@ static void
 set_signature_list_destructor (mime_node_t *node)
 {
     GMimeSignatureList **proxy = talloc (node, GMimeSignatureList *);
+
     if (proxy) {
        *proxy = node->sig_list;
        talloc_set_destructor (proxy, _signature_list_free);
     }
 }
 
+/* Unwrapped MIME part destructor */
+static int
+_unwrapped_child_free (GMimeObject **proxy)
+{
+    g_object_unref (*proxy);
+    return 0;
+}
+
+/* Set up unwrapped MIME part destructor */
+static void
+set_unwrapped_child_destructor (mime_node_t *node)
+{
+    GMimeObject **proxy = talloc (node, GMimeObject *);
+
+    if (proxy) {
+       *proxy = node->unwrapped_child;
+       talloc_set_destructor (proxy, _unwrapped_child_free);
+    }
+}
+
 /* Verify a signed mime node */
 static void
 node_verify (mime_node_t *node, GMimeObject *part)
@@ -185,8 +220,17 @@ node_verify (mime_node_t *node, GMimeObject *part)
     notmuch_status_t status;
 
     node->verify_attempted = true;
-    node->sig_list = g_mime_multipart_signed_verify
-       (GMIME_MULTIPART_SIGNED (part), GMIME_ENCRYPT_NONE, &err);
+    if (GMIME_IS_APPLICATION_PKCS7_MIME (part))
+       node->sig_list = g_mime_application_pkcs7_mime_verify (
+           GMIME_APPLICATION_PKCS7_MIME (part), GMIME_VERIFY_NONE, &node->unwrapped_child, &err);
+    else
+       node->sig_list = g_mime_multipart_signed_verify (
+           GMIME_MULTIPART_SIGNED (part), GMIME_VERIFY_NONE, &err);
+
+    if (node->unwrapped_child) {
+       node->nchildren = 1;
+       set_unwrapped_child_destructor (node);
+    }
 
     if (node->sig_list)
        set_signature_list_destructor (node);
@@ -209,22 +253,23 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part)
     GError *err = NULL;
     GMimeDecryptResult *decrypt_result = NULL;
     notmuch_status_t status;
-    GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part);
     notmuch_message_t *message = NULL;
 
-    if (! node->decrypted_child) {
+    if (! node->unwrapped_child) {
        for (mime_node_t *parent = node; parent; parent = parent->parent)
            if (parent->envelope_file) {
                message = parent->envelope_file;
                break;
            }
 
-       node->decrypted_child = _notmuch_crypto_decrypt (&node->decrypt_attempted,
+       node->unwrapped_child = _notmuch_crypto_decrypt (&node->decrypt_attempted,
                                                         node->ctx->crypto->decrypt,
                                                         message,
-                                                        encrypteddata, &decrypt_result, &err);
+                                                        part, &decrypt_result, &err);
+       if (node->unwrapped_child)
+           set_unwrapped_child_destructor (node);
     }
-    if (! node->decrypted_child) {
+    if (! node->unwrapped_child) {
        fprintf (stderr, "Failed to decrypt part: %s\n",
                 err ? err->message : "no error explanation given");
        goto DONE;
@@ -259,21 +304,22 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part)
        g_object_unref (decrypt_result);
     }
 
- DONE:
 DONE:
     if (err)
        g_error_free (err);
 }
 
+static bool
+_mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild);
+
 static mime_node_t *
 _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild)
 {
     mime_node_t *node = talloc_zero (parent, mime_node_t);
-    notmuch_status_t status;
 
     /* Set basic node properties */
-    node->part = part;
     node->ctx = parent->ctx;
-    if (!talloc_reference (node, node->ctx)) {
+    if (! talloc_reference (node, node->ctx)) {
        fprintf (stderr, "Out of memory.\n");
        talloc_free (node);
        return NULL;
@@ -282,10 +328,29 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild)
     node->part_num = node->next_part_num = -1;
     node->next_child = 0;
 
+    if (_mime_node_set_up_part (node, part, numchild))
+       return node;
+    talloc_free (node);
+    return NULL;
+}
+
+/* associate a MIME part with a node. */
+static bool
+_mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild)
+{
     /* Deal with the different types of parts */
     if (GMIME_IS_PART (part)) {
+       node->part = part;
        node->nchildren = 0;
     } else if (GMIME_IS_MULTIPART (part)) {
+       GMimeObject *repaired_part = _notmuch_repair_mixed_up_mangled (part);
+       if (repaired_part) {
+           /* This was likely "Mixed Up" in transit!  We replace it
+            * with the more likely-to-be-correct variant. */
+           _mime_node_context_track_repaired_part (node->ctx, repaired_part);
+           part = repaired_part;
+       }
+       node->part = part;
        node->nchildren = g_mime_multipart_get_count (GMIME_MULTIPART (part));
     } else if (GMIME_IS_MESSAGE_PART (part)) {
        /* Promote part to an envelope and open it */
@@ -297,11 +362,10 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild)
     } else {
        fprintf (stderr, "Warning: Unknown mime part type: %s.\n",
                 g_type_name (G_OBJECT_TYPE (part)));
-       talloc_free (node);
-       return NULL;
+       return false;
     }
 
-    /* Handle PGP/MIME parts */
+    /* Handle PGP/MIME parts (by definition not cryptographic payload parts) */
     if (GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) {
        if (node->nchildren != 2) {
            /* this violates RFC 3156 section 4, so we won't bother with it. */
@@ -320,13 +384,32 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild)
        } else {
            node_verify (node, part);
        }
+    } else if (GMIME_IS_APPLICATION_PKCS7_MIME (part) &&
+              GMIME_SECURE_MIME_TYPE_SIGNED_DATA == g_mime_application_pkcs7_mime_get_smime_type (GMIME_APPLICATION_PKCS7_MIME (part))) {
+       /* If node->ctx->crypto->verify is false, it would be better
+        * to just unwrap (instead of verifying), but
+        * https://github.com/jstedfast/gmime/issues/67 */
+       node_verify (node, part);
+    } else if (GMIME_IS_APPLICATION_PKCS7_MIME (part) &&
+              GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA == g_mime_application_pkcs7_mime_get_smime_type (GMIME_APPLICATION_PKCS7_MIME (part)) &&
+              (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) {
+       node_decrypt_and_verify (node, part);
+       if (node->unwrapped_child && node->nchildren == 0)
+           node->nchildren = 1;
     } else {
-       status = _notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, parent ? parent->part : NULL, numchild);
-       if (status)
-           fprintf (stderr, "Warning: failed to record potential crypto payload (%s).\n", notmuch_status_to_string (status));
+       if (_notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild) &&
+           node->ctx->msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
+           GMimeObject *clean_payload = _notmuch_repair_crypto_payload_skip_legacy_display (part);
+           if (clean_payload != part) {
+               /* only one layer of recursion is possible here
+                * because there can be only a single cryptographic
+                * payload: */
+               return _mime_node_set_up_part (node, clean_payload, numchild);
+           }
+       }
     }
 
-    return node;
+    return true;
 }
 
 mime_node_t *
@@ -335,19 +418,23 @@ mime_node_child (mime_node_t *parent, int child)
     GMimeObject *sub;
     mime_node_t *node;
 
-    if (!parent || !parent->part || child < 0 || child >= parent->nchildren)
+    if (! parent || ! parent->part || child < 0 || child >= parent->nchildren)
        return NULL;
 
     if (GMIME_IS_MULTIPART (parent->part)) {
-       if (child == GMIME_MULTIPART_ENCRYPTED_CONTENT && parent->decrypted_child)
-           sub = parent->decrypted_child;
+       if (child == GMIME_MULTIPART_ENCRYPTED_CONTENT && parent->unwrapped_child)
+           sub = parent->unwrapped_child;
        else
-           sub = g_mime_multipart_get_part
-               (GMIME_MULTIPART (parent->part), child);
+           sub = g_mime_multipart_get_part (
+               GMIME_MULTIPART (parent->part), child);
     } else if (GMIME_IS_MESSAGE (parent->part)) {
        sub = g_mime_message_get_mime_part (GMIME_MESSAGE (parent->part));
+    } else if (GMIME_IS_APPLICATION_PKCS7_MIME (parent->part) &&
+              parent->unwrapped_child &&
+              child == 0) {
+       sub = parent->unwrapped_child;
     } else {
-       /* This should have been caught by message_part_create */
+       /* This should have been caught by _mime_node_set_up_part */
        INTERNAL_ERROR ("Unexpected GMimeObject type: %s",
                        g_type_name (G_OBJECT_TYPE (parent->part)));
     }
index 39e26f2ec92aa3a2e4ef32516396491cc3184e9d..ebd43e8d296ec775808f527b757b73c137528235 100644 (file)
 #include <errno.h>
 #include <signal.h>
 #include <ctype.h>
+#include <zlib.h>
 
 #include "talloc-extra.h"
 #include "crypto.h"
+#include "repair.h"
 
-#define unused(x) x __attribute__ ((unused))
+#define unused(x) x ## _unused __attribute__ ((unused))
 
-#define STRINGIFY(s) STRINGIFY_(s)
+#define STRINGIFY(s) STRINGIFY_ (s)
 #define STRINGIFY_(s) #s
 
 typedef struct mime_node mime_node_t;
@@ -63,10 +65,10 @@ struct sprinter;
 struct notmuch_show_params;
 
 typedef struct notmuch_show_format {
-    struct sprinter *(*new_sprinter) (const void *ctx, FILE *stream);
-    notmuch_status_t (*part) (const void *ctx, struct sprinter *sprinter,
-                             struct mime_node *node, int indent,
-                             const struct notmuch_show_params *params);
+    struct sprinter *(*new_sprinter)(const void *ctx, FILE *stream);
+    notmuch_status_t (*part)(const void *ctx, struct sprinter *sprinter,
+                            struct mime_node *node, int indent,
+                            const struct notmuch_show_params *params);
 } notmuch_show_format_t;
 
 typedef struct notmuch_show_params {
@@ -85,12 +87,12 @@ typedef struct notmuch_show_params {
  *
  * Note that __location__ comes from talloc.h.
  */
-#define INTERNAL_ERROR(format, ...)                    \
-    do {                                               \
-       fprintf(stderr,                                 \
-               "Internal error: " format " (%s)\n",    \
-               ##__VA_ARGS__, __location__);           \
-       exit (1);                                       \
+#define INTERNAL_ERROR(format, ...)                     \
+    do {                                                \
+       fprintf (stderr,                                 \
+                "Internal error: " format " (%s)\n",    \
+                ##__VA_ARGS__, __location__);           \
+       exit (1);                                       \
     } while (0)
 
 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
@@ -101,8 +103,8 @@ typedef struct notmuch_show_params {
 static inline void
 chomp_newline (char *str)
 {
-    if (str && str[strlen(str)-1] == '\n')
-       str[strlen(str)-1] = '\0';
+    if (str && str[strlen (str) - 1] == '\n')
+       str[strlen (str) - 1] = '\0';
 }
 
 /* Exit status code indicating temporary failure; user is invited to
@@ -251,8 +253,8 @@ json_quote_str (const void *ctx, const char *str);
 /* notmuch-config.c */
 
 typedef enum {
-    NOTMUCH_CONFIG_OPEN        = 1 << 0,
-    NOTMUCH_CONFIG_CREATE = 1 << 1,
+    NOTMUCH_CONFIG_OPEN                = 1 << 0,
+    NOTMUCH_CONFIG_CREATE      = 1 << 1,
 } notmuch_config_mode_t;
 
 notmuch_config_t *
@@ -328,8 +330,8 @@ notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length
 
 void
 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
-                                     const char *list[],
-                                     size_t length);
+                                       const char *list[],
+                                       size_t length);
 
 int
 notmuch_run_hook (const char *db_path, const char *hook);
@@ -388,14 +390,16 @@ struct mime_node {
 
     /* The list of signatures for signed or encrypted containers. If
      * there are no signatures, this will be NULL. */
-    GMimeSignatureListsig_list;
+    GMimeSignatureList *sig_list;
 
     /* Internal: Context inherited from the root iterator. */
     struct mime_node_context *ctx;
 
     /* Internal: For successfully decrypted multipart parts, the
-     * decrypted part to substitute for the second child. */
-    GMimeObject *decrypted_child;
+     * decrypted part to substitute for the second child; or, for
+     * PKCS#7 parts, the part returned after removing/processing the
+     * PKCS#7 transformation */
+    GMimeObject *unwrapped_child;
 
     /* Internal: The next child for depth-first traversal and the part
      * number to assign it (or -1 if unknown). */
@@ -435,11 +439,11 @@ mime_node_t *
 mime_node_child (mime_node_t *parent, int child);
 
 /* Return the nth child of node in a depth-first traversal.  If n is
- * 0, returns node itself.  Returns NULL if there is no such part. */
+* 0, returns node itself.  Returns NULL if there is no such part. */
 mime_node_t *
 mime_node_seek_dfs (mime_node_t *node, int n);
 
-const _notmuch_message_crypto_t*
+const _notmuch_message_crypto_t *
 mime_node_get_message_crypto_status (mime_node_t *node);
 
 typedef enum dump_formats {
@@ -449,9 +453,9 @@ typedef enum dump_formats {
 } dump_format_t;
 
 typedef enum dump_includes {
-    DUMP_INCLUDE_TAGS = 1,
-    DUMP_INCLUDE_CONFIG = 2,
-    DUMP_INCLUDE_PROPERTIES = 4
+    DUMP_INCLUDE_TAGS          = 1,
+    DUMP_INCLUDE_CONFIG                = 2,
+    DUMP_INCLUDE_PROPERTIES    = 4
 } dump_include_t;
 
 #define DUMP_INCLUDE_DEFAULT (DUMP_INCLUDE_TAGS | DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_PROPERTIES)
@@ -466,9 +470,9 @@ notmuch_database_dump (notmuch_database_t *notmuch,
                       dump_include_t include,
                       bool gzip_output);
 
-/* If status is non-zero (i.e. error) print appropriate
  messages to stderr.
-*/
+/* If status indicates error print appropriate
* messages to stderr.
+ */
 
 notmuch_status_t
 print_status_query (const char *loc,
@@ -488,14 +492,24 @@ print_status_database (const char *loc,
 int
 status_to_exit (notmuch_status_t status);
 
+notmuch_status_t
+print_status_gzbytes (const char *loc,
+                     gzFile file,
+                     int bytes);
+
+/* the __location__ macro is defined in talloc.h */
+#define ASSERT_GZBYTES(file, bytes) ((print_status_gzbytes (__location__, file, bytes)) ? exit (1) : 0)
+#define GZPRINTF(file, fmt, ...) ASSERT_GZBYTES (file, gzprintf (file, fmt, ##__VA_ARGS__));
+#define GZPUTS(file, str) ASSERT_GZBYTES(file, gzputs (file, str));
+
 #include "command-line-arguments.h"
 
 extern const char *notmuch_requested_db_uuid;
-extern const notmuch_opt_desc_t  notmuch_shared_options [];
+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 charsubcommand_name);
-int notmuch_minimal_options (const charsubcommand_name,
+void notmuch_process_shared_options (const char *subcommand_name);
+int notmuch_minimal_options (const char *subcommand_name,
                             int argc, char **argv);
 
 
@@ -504,10 +518,10 @@ int notmuch_minimal_options (const char* subcommand_name,
 struct _notmuch_client_indexing_cli_choices {
     int decrypt_policy;
     bool decrypt_policy_set;
-    notmuch_indexopts_t * opts;
+    notmuch_indexopts_t *opts;
 };
 extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices;
-extern const notmuch_opt_desc_t  notmuch_shared_indexing_options [];
+extern const notmuch_opt_desc_t notmuch_shared_indexing_options [];
 notmuch_status_t
 notmuch_process_shared_indexing_options (notmuch_database_t *notmuch);
 
index b7f0784f1d99b6bc967dc9958dca622443a1012f..19c2ddb36a292d1687c61d98d7072e10753029fb 100644 (file)
@@ -119,7 +119,6 @@ struct _notmuch_config {
     bool is_new;
 
     char *database_path;
-    char *crypto_gpg_path;
     char *user_name;
     char *user_primary_email;
     const char **user_other_email;
@@ -151,14 +150,14 @@ get_name_from_passwd_file (void *ctx)
     char *name;
     int e;
 
-    pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+    pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
     if (pw_buf_size == -1) pw_buf_size = 64;
     pw_buf = talloc_size (ctx, pw_buf_size);
 
     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
-                            pw_buf_size, &ignored)) == ERANGE) {
-        pw_buf_size = pw_buf_size * 2;
-        pw_buf = talloc_zero_size(ctx, pw_buf_size);
+                           pw_buf_size, &ignored)) == ERANGE) {
+       pw_buf_size = pw_buf_size * 2;
+       pw_buf = talloc_zero_size (ctx, pw_buf_size);
     }
 
     if (e == 0) {
@@ -186,14 +185,14 @@ get_username_from_passwd_file (void *ctx)
     char *name;
     int e;
 
-    pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+    pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
     if (pw_buf_size == -1) pw_buf_size = 64;
     pw_buf = talloc_zero_size (ctx, pw_buf_size);
 
     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
-                            pw_buf_size, &ignored)) == ERANGE) {
-        pw_buf_size = pw_buf_size * 2;
-        pw_buf = talloc_zero_size(ctx, pw_buf_size);
+                           pw_buf_size, &ignored)) == ERANGE) {
+       pw_buf_size = pw_buf_size * 2;
+       pw_buf = talloc_zero_size (ctx, pw_buf_size);
     }
 
     if (e == 0)
@@ -217,7 +216,7 @@ get_config_from_file (notmuch_config_t *config, bool create_new)
     GError *error = NULL;
     bool ret = false;
 
-    FILE *fp = fopen(config->filename, "r");
+    FILE *fp = fopen (config->filename, "r");
     if (fp == NULL) {
        if (errno == ENOENT) {
            /* If create_new is true, then the caller is prepared for a
@@ -233,7 +232,7 @@ get_config_from_file (notmuch_config_t *config, bool create_new)
            }
        } else {
            fprintf (stderr, "Error opening config file '%s': %s\n",
-                    config->filename, strerror(errno));
+                    config->filename, strerror (errno));
        }
        goto out;
     }
@@ -274,12 +273,12 @@ get_config_from_file (notmuch_config_t *config, bool create_new)
 
     g_error_free (error);
 
-out:
+  out:
     if (fp)
-       fclose(fp);
+       fclose (fp);
 
     if (config_str)
-       talloc_free(config_str);
+       talloc_free (config_str);
 
     return ret;
 }
@@ -300,20 +299,20 @@ out:
  *
  *     If is_new_ret is NULL, then a "file not found" message will be
  *     printed to stderr and NULL will be returned.
-
+ *
  *     If is_new_ret is non-NULL then a default configuration will be
  *     returned and *is_new_ret will be set to 1 on return so that
  *     the caller can recognize this case.
  *
- *     These default configuration settings are determined as
- *     follows:
+ *     These default configuration settings are determined as
+ *     follows:
  *
  *             database_path:          $MAILDIR, otherwise $HOME/mail
  *
  *             user_name:              $NAME variable if set, otherwise
  *                                     read from /etc/passwd
  *
- *             user_primary_mail:      $EMAIL variable if set, otherwise
+ *             user_primary_mail:      $EMAIL variable if set, otherwise
  *                                     constructed from the username and
  *                                     hostname of the current machine.
  *
@@ -338,11 +337,12 @@ notmuch_config_open (void *ctx,
     int file_had_crypto_group;
 
     notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
+
     if (config == NULL) {
        fprintf (stderr, "Out of memory.\n");
        return NULL;
     }
-    
+
     talloc_set_destructor (config, notmuch_config_destructor);
 
     /* non-zero defaults */
@@ -438,7 +438,7 @@ notmuch_config_open (void *ctx,
     }
 
     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
-        const char *tags[] = { "unread", "inbox" };
+       const char *tags[] = { "unread", "inbox" };
        notmuch_config_set_new_tags (config, tags, 2);
     }
 
@@ -499,11 +499,11 @@ notmuch_config_open (void *ctx,
 }
 
 /* Close the given notmuch_config_t object, freeing all resources.
- * 
+ *
  * Note: Any changes made to the configuration are *not* saved by this
  * function. To save changes, call notmuch_config_save before
  * notmuch_config_close.
-*/
+ */
 void
 notmuch_config_close (notmuch_config_t *config)
 {
@@ -605,13 +605,13 @@ _config_get_list (notmuch_config_t *config,
                  const char *section, const char *key,
                  const char ***outlist, size_t *list_length, size_t *ret_length)
 {
-    assert(outlist);
+    assert (outlist);
 
     /* read from config file and cache value, if not cached already */
     if (*outlist == NULL) {
 
        char **inlist = g_key_file_get_string_list (config->key_file,
-                                            section, key, list_length, NULL);
+                                                   section, key, list_length, NULL);
        if (inlist) {
            unsigned int i;
 
@@ -648,7 +648,7 @@ _config_set_list (notmuch_config_t *config,
 const char *
 notmuch_config_get_database_path (notmuch_config_t *config)
 {
-    char *db_path = (char *)_config_get (config, &config->database_path, "database", "path");
+    char *db_path = (char *) _config_get (config, &config->database_path, "database", "path");
 
     if (db_path && *db_path != '/') {
        /* If the path in the configuration file begins with any
@@ -726,16 +726,16 @@ notmuch_config_set_user_other_email (notmuch_config_t *config,
                                     size_t length)
 {
     _config_set_list (config, "user", "other_email", list, length,
-                    &(config->user_other_email));
+                     &(config->user_other_email));
 }
 
 void
 notmuch_config_set_new_tags (notmuch_config_t *config,
-                                    const char *list[],
-                                    size_t length)
+                            const char *list[],
+                            size_t length)
 {
     _config_set_list (config, "new", "tags", list, length,
-                    &(config->new_tags));
+                     &(config->new_tags));
 }
 
 void
@@ -744,7 +744,7 @@ notmuch_config_set_new_ignore (notmuch_config_t *config,
                               size_t length)
 {
     _config_set_list (config, "new", "ignore", list, length,
-                    &(config->new_ignore));
+                     &(config->new_ignore));
 }
 
 const char **
@@ -757,8 +757,8 @@ notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length
 
 void
 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
-                                     const char *list[],
-                                     size_t length)
+                                       const char *list[],
+                                       size_t length)
 {
     _config_set_list (config, "search", "exclude_tags", list, length,
                      &(config->search_exclude_tags));
@@ -779,7 +779,7 @@ _item_split (char *item, char **group, char **key)
     *group = item;
 
     period = strchr (item, '.');
-    if (period == NULL || *(period+1) == '\0') {
+    if (period == NULL || *(period + 1) == '\0') {
        fprintf (stderr,
                 "Invalid configuration name: %s\n"
                 "(Should be of the form <section>.<item>)\n", item);
@@ -793,7 +793,7 @@ _item_split (char *item, char **group, char **key)
 }
 
 /* These are more properly called Xapian fields, but the user facing
  docs call them prefixes, so make the error message match */
* docs call them prefixes, so make the error message match */
 static bool
 validate_field_name (const char *str)
 {
@@ -839,10 +839,10 @@ typedef struct config_key {
 } config_key_info_t;
 
 static struct config_key
-config_key_table[] = {
-    {"index.decrypt",  true,   false,  NULL},
-    {"index.header.",  true,   true,   validate_field_name},
-    {"query.",         true,   true,   NULL},
+    config_key_table[] = {
+    { "index.decrypt",   true,   false,  NULL },
+    { "index.header.",   true,   true,   validate_field_name },
+    { "query.",          true,   true,   NULL },
 };
 
 static config_key_info_t *
@@ -851,10 +851,10 @@ _config_key_info (const char *item)
     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
        if (config_key_table[i].prefix &&
            strncmp (item, config_key_table[i].name,
-                    strlen(config_key_table[i].name)) == 0)
-           return config_key_table+i;
+                    strlen (config_key_table[i].name)) == 0)
+           return config_key_table + i;
        if (strcmp (item, config_key_table[i].name) == 0)
-           return config_key_table+i;
+           return config_key_table + i;
     }
     return NULL;
 }
@@ -863,13 +863,14 @@ static bool
 _stored_in_db (const char *item)
 {
     config_key_info_t *info;
+
     info = _config_key_info (item);
 
     return (info && info->in_db);
 }
 
 static int
-_print_db_config(notmuch_config_t *config, const char *name)
+_print_db_config (notmuch_config_t *config, const char *name)
 {
     notmuch_database_t *notmuch;
     char *val;
@@ -884,7 +885,7 @@ _print_db_config(notmuch_config_t *config, const char *name)
                               notmuch_database_get_config (notmuch, name, &val)))
        return EXIT_FAILURE;
 
-     puts (val);
+    puts (val);
 
     return EXIT_SUCCESS;
 }
@@ -892,20 +893,20 @@ _print_db_config(notmuch_config_t *config, const char *name)
 static int
 notmuch_config_command_get (notmuch_config_t *config, char *item)
 {
-    if (strcmp(item, "database.path") == 0) {
+    if (strcmp (item, "database.path") == 0) {
        printf ("%s\n", notmuch_config_get_database_path (config));
-    } else if (strcmp(item, "user.name") == 0) {
+    } else if (strcmp (item, "user.name") == 0) {
        printf ("%s\n", notmuch_config_get_user_name (config));
-    } else if (strcmp(item, "user.primary_email") == 0) {
+    } else if (strcmp (item, "user.primary_email") == 0) {
        printf ("%s\n", notmuch_config_get_user_primary_email (config));
-    } else if (strcmp(item, "user.other_email") == 0) {
+    } else if (strcmp (item, "user.other_email") == 0) {
        const char **other_email;
        size_t i, length;
-       
+
        other_email = notmuch_config_get_user_other_email (config, &length);
        for (i = 0; i < length; i++)
            printf ("%s\n", other_email[i]);
-    } else if (strcmp(item, "new.tags") == 0) {
+    } else if (strcmp (item, "new.tags") == 0) {
        const char **tags;
        size_t i, length;
 
@@ -944,7 +945,7 @@ notmuch_config_command_get (notmuch_config_t *config, char *item)
 }
 
 static int
-_set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
+_set_db_config (notmuch_config_t *config, const char *key, int argc, char **argv)
 {
     notmuch_database_t *notmuch;
     const char *val = "";
@@ -1025,15 +1026,15 @@ static
 void
 _notmuch_config_list_built_with ()
 {
-    printf("%scompact=%s\n",
-          BUILT_WITH_PREFIX,
-          notmuch_built_with ("compact") ? "true" : "false");
-    printf("%sfield_processor=%s\n",
-          BUILT_WITH_PREFIX,
-          notmuch_built_with ("field_processor") ? "true" : "false");
-    printf("%sretry_lock=%s\n",
-          BUILT_WITH_PREFIX,
-          notmuch_built_with ("retry_lock") ? "true" : "false");
+    printf ("%scompact=%s\n",
+           BUILT_WITH_PREFIX,
+           notmuch_built_with ("compact") ? "true" : "false");
+    printf ("%sfield_processor=%s\n",
+           BUILT_WITH_PREFIX,
+           notmuch_built_with ("field_processor") ? "true" : "false");
+    printf ("%sretry_lock=%s\n",
+           BUILT_WITH_PREFIX,
+           notmuch_built_with ("retry_lock") ? "true" : "false");
 }
 
 static int
@@ -1054,11 +1055,11 @@ _list_db_config (notmuch_config_t *config)
        return EXIT_FAILURE;
 
     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
-       printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+       printf ("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value (list));
     }
     notmuch_config_list_destroy (list);
 
-   return EXIT_SUCCESS;
+    return EXIT_SUCCESS;
 }
 
 static int
@@ -1115,8 +1116,8 @@ notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
                 notmuch_requested_db_uuid);
 
     /* skip at least subcommand argument */
-    argc-= opt_index;
-    argv+= opt_index;
+    argc -= opt_index;
+    argv += opt_index;
 
     if (argc < 1) {
        fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
index ca05c9793b70efcc2932bc8a046af5fa4d2c5622..d8ad7d6d57286285ef630424d86d999bfa2e9b46 100644 (file)
@@ -165,10 +165,10 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_opt_desc_t options[] = {
        { .opt_keyword = &output, .name = "output", .keywords =
-         (notmuch_keyword_t []){ { "threads", OUTPUT_THREADS },
-                                 { "messages", OUTPUT_MESSAGES },
-                                 { "files", OUTPUT_FILES },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "threads", OUTPUT_THREADS },
+                                     { "messages", OUTPUT_MESSAGES },
+                                     { "files", OUTPUT_FILES },
+                                     { 0, 0 } } },
        { .opt_bool = &exclude, .name = "exclude" },
        { .opt_bool = &print_lastmod, .name = "lastmod" },
        { .opt_bool = &batch, .name = "batch" },
@@ -214,7 +214,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
 
     if (exclude) {
        search_exclude_tags = notmuch_config_get_search_exclude_tags
-           (config, &search_exclude_tags_length);
+                                 (config, &search_exclude_tags_length);
     }
 
     if (batch)
index 70cdb0c21718f8f0738760a0ed8d3e5a0486f44f..887ef7f0179ab872ab7280e1ade03e15d6e1124a 100644 (file)
@@ -21,7 +21,7 @@
 #include "notmuch-client.h"
 #include "hex-escape.h"
 #include "string-util.h"
-#include <zlib.h>
+#include "zlib-extra.h"
 
 static int
 database_dump_config (notmuch_database_t *notmuch, gzFile output)
@@ -42,7 +42,7 @@ database_dump_config (notmuch_database_t *notmuch, gzFile output)
                     notmuch_config_list_key (list));
            goto DONE;
        }
-       gzprintf (output, "#@ %s", buffer);
+       GZPRINTF (output, "#@ %s", buffer);
 
        if (hex_encode (notmuch, notmuch_config_list_value (list),
                        &buffer, &buffer_size) != HEX_SUCCESS) {
@@ -51,12 +51,14 @@ database_dump_config (notmuch_database_t *notmuch, gzFile output)
            goto DONE;
        }
 
-       gzprintf (output, " %s\n", buffer);
+       GZPUTS (output, " ");
+       GZPUTS (output, buffer);
+       GZPUTS (output, "\n");
     }
 
     ret = EXIT_SUCCESS;
 
- DONE:
 DONE:
     if (list)
        notmuch_config_list_destroy (list);
 
@@ -71,22 +73,22 @@ print_dump_header (gzFile output, int output_format, int include)
 {
     const char *sep = "";
 
-    gzprintf (output, "#notmuch-dump %s:%d ",
+    GZPRINTF (output, "#notmuch-dump %s:%d ",
              (output_format == DUMP_FORMAT_SUP) ? "sup" : "batch-tag",
              NOTMUCH_DUMP_VERSION);
 
     if (include & DUMP_INCLUDE_CONFIG) {
-       gzputs (output, "config");
+       GZPUTS (output, "config");
        sep = ",";
     }
     if (include & DUMP_INCLUDE_PROPERTIES) {
-       gzprintf (output, "%sproperties", sep);
+       GZPRINTF (output, "%sproperties", sep);
        sep = ",";
     }
     if (include & DUMP_INCLUDE_TAGS) {
-       gzprintf (output, "%stags", sep);
+       GZPRINTF (output, "%stags", sep);
     }
-    gzputs (output, "\n");
+    GZPUTS (output, "\n");
 }
 
 static int
@@ -115,7 +117,7 @@ dump_properties_message (void *ctx,
                fprintf (stderr, "Error: failed to hex-encode message-id %s\n", message_id);
                return 1;
            }
-           gzprintf (output, "#= %s", *buffer_p);
+           GZPRINTF (output, "#= %s", *buffer_p);
            first = false;
        }
 
@@ -126,18 +128,18 @@ dump_properties_message (void *ctx,
            fprintf (stderr, "Error: failed to hex-encode key %s\n", key);
            return 1;
        }
-       gzprintf (output, " %s", *buffer_p);
+       GZPRINTF (output, " %s", *buffer_p);
 
        if (hex_encode (ctx, val, buffer_p, size_p) != HEX_SUCCESS) {
            fprintf (stderr, "Error: failed to hex-encode value %s\n", val);
            return 1;
        }
-       gzprintf (output, "=%s", *buffer_p);
+       GZPRINTF (output, "=%s", *buffer_p);
     }
     notmuch_message_properties_destroy (list);
 
     if (! first)
-       gzprintf (output, "\n", *buffer_p);
+       GZPRINTF (output, "\n", *buffer_p);
 
     return 0;
 }
@@ -165,7 +167,7 @@ dump_tags_message (void *ctx,
     }
 
     if (output_format == DUMP_FORMAT_SUP) {
-       gzprintf (output, "%s (", message_id);
+       GZPRINTF (output, "%s (", message_id);
     }
 
     for (notmuch_tags_t *tags = notmuch_message_get_tags (message);
@@ -174,12 +176,12 @@ dump_tags_message (void *ctx,
        const char *tag_str = notmuch_tags_get (tags);
 
        if (! first)
-           gzputs (output, " ");
+           GZPUTS (output, " ");
 
        first = 0;
 
        if (output_format == DUMP_FORMAT_SUP) {
-           gzputs (output, tag_str);
+           GZPUTS (output, tag_str);
        } else {
            if (hex_encode (ctx, tag_str,
                            buffer_p, size_p) != HEX_SUCCESS) {
@@ -187,12 +189,12 @@ dump_tags_message (void *ctx,
                         tag_str);
                return EXIT_FAILURE;
            }
-           gzprintf (output, "+%s", *buffer_p);
+           GZPRINTF (output, "+%s", *buffer_p);
        }
     }
 
     if (output_format == DUMP_FORMAT_SUP) {
-       gzputs (output, ")\n");
+       GZPUTS (output, ")\n");
     } else {
        if (make_boolean_term (ctx, "id", message_id,
                               buffer_p, size_p)) {
@@ -200,7 +202,7 @@ dump_tags_message (void *ctx,
                     message_id, strerror (errno));
            return EXIT_FAILURE;
        }
-       gzprintf (output, " -- %s\n", *buffer_p);
+       GZPRINTF (output, " -- %s\n", *buffer_p);
     }
     return EXIT_SUCCESS;
 }
@@ -220,7 +222,7 @@ database_dump_file (notmuch_database_t *notmuch, gzFile output,
 
     if (include & DUMP_INCLUDE_CONFIG) {
        if (print_status_database ("notmuch dump", notmuch,
-                                  database_dump_config(notmuch,output)))
+                                  database_dump_config (notmuch, output)))
            return EXIT_FAILURE;
     }
 
@@ -307,7 +309,7 @@ notmuch_database_dump (notmuch_database_t *notmuch,
                 name_for_error, strerror (errno));
        if (close (outfd))
            fprintf (stderr, "Error closing %s during shutdown: %s\n",
-                name_for_error, strerror (errno));
+                    name_for_error, strerror (errno));
        goto DONE;
     }
 
@@ -316,7 +318,7 @@ notmuch_database_dump (notmuch_database_t *notmuch,
 
     ret = gzflush (output, Z_FINISH);
     if (ret) {
-       fprintf (stderr, "Error flushing output: %s\n", gzerror (output, NULL));
+       fprintf (stderr, "Error flushing output: %s\n", gzerror_str (output));
        goto DONE;
     }
 
@@ -332,7 +334,7 @@ notmuch_database_dump (notmuch_database_t *notmuch,
     ret = gzclose_w (output);
     if (ret) {
        fprintf (stderr, "Error closing %s: %s\n", name_for_error,
-                gzerror (output, NULL));
+                gzerror_str (output));
        ret = EXIT_FAILURE;
        output = NULL;
        goto DONE;
@@ -348,7 +350,7 @@ notmuch_database_dump (notmuch_database_t *notmuch,
        }
 
     }
- DONE:
 DONE:
     if (ret != EXIT_SUCCESS && output)
        (void) gzclose_w (output);
 
@@ -380,13 +382,13 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_opt_desc_t options[] = {
        { .opt_keyword = &output_format, .name = "format", .keywords =
-         (notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP },
-                                 { "batch-tag", DUMP_FORMAT_BATCH_TAG },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP },
+                                     { "batch-tag", DUMP_FORMAT_BATCH_TAG },
+                                     { 0, 0 } } },
        { .opt_flags = &include, .name = "include", .keywords =
-         (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
-                                 { "properties", DUMP_INCLUDE_PROPERTIES },
-                                 { "tags", DUMP_INCLUDE_TAGS} } },
+             (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
+                                     { "properties", DUMP_INCLUDE_PROPERTIES },
+                                     { "tags", DUMP_INCLUDE_TAGS } } },
        { .opt_string = &output_file_name, .name = "output" },
        { .opt_bool = &gzip_output, .name = "gzip" },
        { .opt_inherit = notmuch_shared_options },
index 327470d4288ee30a290e92b5c4fa74558779c009..1d3b015053deef8062c2f17a08d8cfe47231d9fe 100644 (file)
@@ -99,7 +99,7 @@ is_valid_folder_name (const char *folder)
        if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/'))
            return false;
        p = strchr (p, '/');
-       if (!p)
+       if (! p)
            return true;
        p++;
     }
@@ -120,7 +120,7 @@ mkdir_recursive (const void *ctx, const char *path, int mode)
     /* First check the common case: directory already exists. */
     r = stat (path, &st);
     if (r == 0) {
-        if (! S_ISDIR (st.st_mode)) {
+       if (! S_ISDIR (st.st_mode)) {
            fprintf (stderr, "Error: '%s' is not a directory: %s\n",
                     path, strerror (EEXIST));
            return false;
@@ -282,7 +282,7 @@ copy_fd (int fdout, int fdin)
        } while (remain > 0);
     }
 
-    return (!interrupted && !empty);
+    return (! interrupted && ! empty);
 }
 
 /*
@@ -311,7 +311,7 @@ maildir_write_tmp (const void *ctx, int fdin, const char *maildir, bool world_re
 
     return path;
 
-FAIL:
+  FAIL:
     close (fdout);
     unlink (path);
 
@@ -360,7 +360,7 @@ maildir_write_new (const void *ctx, int fdin, const char *maildir, bool world_re
 
     return newpath;
 
-FAIL:
+  FAIL:
     unlink (cleanpath);
 
     return NULL;
index 184e9aa2189ab7d201f5e5ebc56db9ea9f2c4be6..4075d395fd4ff89a992fbf9ed7cfbdc7a7761839 100644 (file)
@@ -87,7 +87,7 @@ handle_sigint (unused (int sig))
      * 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));
+    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
     interrupted = 1;
 }
 
@@ -184,23 +184,23 @@ dirent_type (const char *path, const struct dirent *entry)
     /* Mapping from d_type to stat mode_t.  We omit DT_LNK so that
      * we'll fall through to stat and get the real file type. */
     static const mode_t modes[] = {
-       [DT_BLK]  = S_IFBLK,
-       [DT_CHR]  = S_IFCHR,
-       [DT_DIR]  = S_IFDIR,
+       [DT_BLK] = S_IFBLK,
+       [DT_CHR] = S_IFCHR,
+       [DT_DIR] = S_IFDIR,
        [DT_FIFO] = S_IFIFO,
-       [DT_REG]  = S_IFREG,
+       [DT_REG] = S_IFREG,
        [DT_SOCK] = S_IFSOCK
     };
-    if (entry->d_type < ARRAY_SIZE(modes) && modes[entry->d_type])
+    if (entry->d_type < ARRAY_SIZE (modes) && modes[entry->d_type])
        return modes[entry->d_type];
 #endif
 
     abspath = talloc_asprintf (NULL, "%s/%s", path, entry->d_name);
-    if (!abspath) {
+    if (! abspath) {
        errno = ENOMEM;
        return -1;
     }
-    err = stat(abspath, &statbuf);
+    err = stat (abspath, &statbuf);
     saved_errno = errno;
     talloc_free (abspath);
     if (err < 0) {
@@ -226,10 +226,9 @@ _entries_resemble_maildir (const char *path, struct dirent **entries, int count)
        if (dirent_type (path, entries[i]) != S_IFDIR)
            continue;
 
-       if (strcmp(entries[i]->d_name, "new") == 0 ||
-           strcmp(entries[i]->d_name, "cur") == 0 ||
-           strcmp(entries[i]->d_name, "tmp") == 0)
-       {
+       if (strcmp (entries[i]->d_name, "new") == 0 ||
+           strcmp (entries[i]->d_name, "cur") == 0 ||
+           strcmp (entries[i]->d_name, "tmp") == 0) {
            found++;
            if (found == 3)
                return 1;
@@ -389,8 +388,11 @@ add_file (notmuch_database_t *notmuch, const char *filename,
            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_bool_t is_set;
+           /* Currently all errors from has_maildir_flag are fatal */
+           if ((status = notmuch_message_has_maildir_flag_st (message, 'S', &is_set)))
+               goto DONE;
+           if (strcmp ("unread", *tag) != 0 || ! is_set) {
                notmuch_message_add_tag (message, *tag);
            }
        }
@@ -415,7 +417,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:
-       (void) print_status_database("add_file", notmuch, status);
+       (void) print_status_database ("add_file", notmuch, status);
        goto DONE;
     default:
        INTERNAL_ERROR ("add_message returned unexpected value: %d", status);
@@ -534,7 +536,7 @@ add_files (notmuch_database_t *notmuch,
         * file system link count.  So, only bail early if the
         * database agrees that there are no sub-directories. */
        db_subdirs = notmuch_directory_get_child_directories (directory);
-       if (!notmuch_filenames_valid (db_subdirs))
+       if (! notmuch_filenames_valid (db_subdirs))
            goto DONE;
        notmuch_filenames_destroy (db_subdirs);
        db_subdirs = NULL;
@@ -631,7 +633,7 @@ add_files (notmuch_database_t *notmuch,
 
     /* Pass 2: Scan for new files, removed files, and removed directories. */
     for (i = 0; i < num_fs_entries && ! interrupted; i++) {
-        entry = fs_entries[i];
+       entry = fs_entries[i];
 
        /* Ignore special directories early. */
        if (_special_directory (entry->d_name))
@@ -648,8 +650,7 @@ add_files (notmuch_database_t *notmuch,
        /* Check if we've walked past any names in db_files or
         * db_subdirs. If so, these have been deleted. */
        while (notmuch_filenames_valid (db_files) &&
-              strcmp (notmuch_filenames_get (db_files), entry->d_name) < 0)
-       {
+              strcmp (notmuch_filenames_get (db_files), entry->d_name) < 0) {
            char *absolute = talloc_asprintf (state->removed_files,
                                              "%s/%s", path,
                                              notmuch_filenames_get (db_files));
@@ -664,17 +665,15 @@ add_files (notmuch_database_t *notmuch,
        }
 
        while (notmuch_filenames_valid (db_subdirs) &&
-              strcmp (notmuch_filenames_get (db_subdirs), entry->d_name) <= 0)
-       {
+              strcmp (notmuch_filenames_get (db_subdirs), entry->d_name) <= 0) {
            const char *filename = notmuch_filenames_get (db_subdirs);
 
-           if (strcmp (filename, entry->d_name) < 0)
-           {
+           if (strcmp (filename, entry->d_name) < 0) {
                char *absolute = talloc_asprintf (state->removed_directories,
                                                  "%s/%s", path, filename);
                if (state->debug)
                    printf ("(D) add_files, pass 2: queuing passed directory %s for deletion from database\n",
-                       absolute);
+                           absolute);
 
                _filename_list_add (state->removed_directories, absolute);
            }
@@ -694,8 +693,7 @@ add_files (notmuch_database_t *notmuch,
 
        /* Don't add a file that we've added before. */
        if (notmuch_filenames_valid (db_files) &&
-           strcmp (notmuch_filenames_get (db_files), entry->d_name) == 0)
-       {
+           strcmp (notmuch_filenames_get (db_files), entry->d_name) == 0) {
            notmuch_filenames_move_to_next (db_files);
            continue;
        }
@@ -708,12 +706,12 @@ add_files (notmuch_database_t *notmuch,
 
        if (state->verbosity >= VERBOSITY_VERBOSE) {
            if (state->output_is_a_tty)
-               printf("\r\033[K");
+               printf ("\r\033[K");
 
            printf ("%i/%i: %s", state->processed_files, state->total_files,
                    next);
 
-           putchar((state->output_is_a_tty) ? '\r' : '\n');
+           putchar ((state->output_is_a_tty) ? '\r' : '\n');
            fflush (stdout);
        }
 
@@ -738,8 +736,7 @@ add_files (notmuch_database_t *notmuch,
 
     /* Now that we've walked the whole filesystem list, anything left
      * over in the database lists has been deleted. */
-    while (notmuch_filenames_valid (db_files))
-    {
+    while (notmuch_filenames_valid (db_files)) {
        char *absolute = talloc_asprintf (state->removed_files,
                                          "%s/%s", path,
                                          notmuch_filenames_get (db_files));
@@ -752,8 +749,7 @@ add_files (notmuch_database_t *notmuch,
        notmuch_filenames_move_to_next (db_files);
     }
 
-    while (notmuch_filenames_valid (db_subdirs))
-    {
+    while (notmuch_filenames_valid (db_subdirs)) {
        char *absolute = talloc_asprintf (state->removed_directories,
                                          "%s/%s", path,
                                          notmuch_filenames_get (db_subdirs));
@@ -856,7 +852,7 @@ count_files (const char *path, int *count, add_files_state_t *state)
     }
 
     for (i = 0; i < num_fs_entries && ! interrupted; i++) {
-        entry = fs_entries[i];
+       entry = fs_entries[i];
 
        /* Ignore special directories to avoid infinite recursion.
         * Also ignore the .notmuch directory.
@@ -901,7 +897,7 @@ count_files (const char *path, int *count, add_files_state_t *state)
        for (i = 0; i < num_fs_entries; i++)
            free (fs_entries[i]);
 
-        free (fs_entries);
+       free (fs_entries);
     }
 }
 
@@ -939,6 +935,7 @@ remove_filename (notmuch_database_t *notmuch,
 {
     notmuch_status_t status;
     notmuch_message_t *message;
+
     status = notmuch_database_begin_atomic (notmuch);
     if (status)
        return status;
@@ -976,13 +973,12 @@ _remove_directory (void *ctx,
     char *absolute;
 
     status = notmuch_database_get_directory (notmuch, path, &directory);
-    if (status || !directory)
+    if (status || ! directory)
        return status;
 
     for (files = notmuch_directory_get_child_files (directory);
         notmuch_filenames_valid (files);
-        notmuch_filenames_move_to_next (files))
-    {
+        notmuch_filenames_move_to_next (files)) {
        absolute = talloc_asprintf (ctx, "%s/%s", path,
                                    notmuch_filenames_get (files));
        status = remove_filename (notmuch, absolute, add_files_state);
@@ -993,8 +989,7 @@ _remove_directory (void *ctx,
 
     for (subdirs = notmuch_directory_get_child_directories (directory);
         notmuch_filenames_valid (subdirs);
-        notmuch_filenames_move_to_next (subdirs))
-    {
+        notmuch_filenames_move_to_next (subdirs)) {
        absolute = talloc_asprintf (ctx, "%s/%s", path,
                                    notmuch_filenames_get (subdirs));
        status = _remove_directory (ctx, notmuch, absolute, add_files_state);
@@ -1234,32 +1229,32 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
        goto DONE;
 
     gettimeofday (&tv_start, NULL);
-    for (f = add_files_state.removed_files->head; f && !interrupted; f = f->next) {
+    for (f = add_files_state.removed_files->head; f && ! interrupted; f = f->next) {
        ret = remove_filename (notmuch, f->filename, &add_files_state);
        if (ret)
            goto DONE;
        if (do_print_progress) {
            do_print_progress = 0;
            generic_print_progress ("Cleaned up", "messages",
-               tv_start, add_files_state.removed_messages + add_files_state.renamed_messages,
-               add_files_state.removed_files->count);
+                                   tv_start, add_files_state.removed_messages + add_files_state.renamed_messages,
+                                   add_files_state.removed_files->count);
        }
     }
 
     gettimeofday (&tv_start, NULL);
-    for (f = add_files_state.removed_directories->head, i = 0; f && !interrupted; f = f->next, i++) {
+    for (f = add_files_state.removed_directories->head, i = 0; f && ! interrupted; f = f->next, i++) {
        ret = _remove_directory (config, notmuch, f->filename, &add_files_state);
        if (ret)
            goto DONE;
        if (do_print_progress) {
            do_print_progress = 0;
            generic_print_progress ("Cleaned up", "directories",
-               tv_start, i,
-               add_files_state.removed_directories->count);
+                                   tv_start, i,
+                                   add_files_state.removed_directories->count);
        }
     }
 
-    for (f = add_files_state.directory_mtimes->head; f && !interrupted; f = f->next) {
+    for (f = add_files_state.directory_mtimes->head; f && ! interrupted; f = f->next) {
        notmuch_directory_t *directory;
        status = notmuch_database_get_directory (notmuch, f->filename, &directory);
        if (status == NOTMUCH_STATUS_SUCCESS && directory) {
@@ -1285,7 +1280,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_database_destroy (notmuch);
 
-    if (hooks && !ret && !interrupted)
+    if (hooks && ! ret && ! interrupted)
        ret = notmuch_run_hook (db_path, "post-new");
 
     if (ret || interrupted)
index 3139a8c665a0fb46bb6626a77c32f26c4ff5c60a..5a39ade103de2f0fd6160639d2e56fa873784acd 100644 (file)
@@ -68,13 +68,13 @@ reindex_query (notmuch_database_t *notmuch, const char *query_string,
         notmuch_messages_move_to_next (messages)) {
        message = notmuch_messages_get (messages);
 
-       ret = notmuch_message_reindex(message, indexopts);
+       ret = notmuch_message_reindex (message, indexopts);
        if (ret != NOTMUCH_STATUS_SUCCESS)
            break;
        notmuch_message_destroy (message);
     }
 
-    if (!ret)
+    if (! ret)
        ret = notmuch_database_end_atomic (notmuch);
 
     notmuch_query_destroy (query);
@@ -124,7 +124,7 @@ notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[])
        return EXIT_FAILURE;
     }
 
-    query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
+    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;
@@ -134,7 +134,7 @@ notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[])
        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);
index 46bab4344f7464f57f603040c13608e1a14459a9..ceb4f39bc587b7fe21a9ec04289f362cfbbba644 100644 (file)
@@ -28,8 +28,8 @@ static void
 show_reply_headers (GMimeStream *stream, GMimeMessage *message)
 {
     /* Output RFC 2822 formatted (and RFC 2047 encoded) headers. */
-    if (g_mime_object_write_to_stream (GMIME_OBJECT(message), NULL, stream) < 0) {
-       INTERNAL_ERROR("failed to write headers to stdout\n");
+    if (g_mime_object_write_to_stream (GMIME_OBJECT (message), NULL, stream) < 0) {
+       INTERNAL_ERROR ("failed to write headers to stdout\n");
     }
 }
 
@@ -65,10 +65,11 @@ format_part_reply (GMimeStream *stream, mime_node_t *node)
        GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (node->part);
 
        if (g_mime_content_type_is_type (content_type, "application", "pgp-encrypted") ||
-           g_mime_content_type_is_type (content_type, "application", "pgp-signature")) {
-           /* Ignore PGP/MIME cruft parts */
+           g_mime_content_type_is_type (content_type, "application", "pgp-signature") ||
+           g_mime_content_type_is_type (content_type, "application", "pkcs7-mime")) {
+           /* Ignore PGP/MIME and S/MIME cruft parts */
        } else if (g_mime_content_type_is_type (content_type, "text", "*") &&
-                  !g_mime_content_type_is_type (content_type, "text", "html")) {
+                  ! g_mime_content_type_is_type (content_type, "text", "html")) {
            show_text_part_content (node->part, stream, NOTMUCH_SHOW_TEXT_PART_REPLY);
        } else if (disposition &&
                   strcasecmp (g_mime_content_disposition_get_disposition (disposition),
@@ -117,7 +118,7 @@ address_match (const char *str, notmuch_config_t *config, address_match_t mode)
     const char **other;
     size_t i, other_len;
 
-    if (!str || *str == '\0')
+    if (! str || *str == '\0')
        return NULL;
 
     primary = notmuch_config_get_user_primary_email (config);
@@ -263,14 +264,15 @@ reply_to_header_is_redundant (GMimeMessage *message,
     return ret;
 }
 
-static InternetAddressList *get_sender(GMimeMessage *message)
+static InternetAddressList *
+get_sender (GMimeMessage *message)
 {
     InternetAddressList *reply_to_list;
 
     reply_to_list = g_mime_message_get_reply_to_list (message);
     if (reply_to_list &&
        internet_address_list_length (reply_to_list) > 0) {
-        /*
+       /*
         * Some mailing lists munge the Reply-To header despite it
         * being A Bad Thing, see
         * http://marc.merlins.org/netrants/reply-to-harmful.html
@@ -290,17 +292,20 @@ static InternetAddressList *get_sender(GMimeMessage *message)
     return g_mime_message_get_from (message);
 }
 
-static InternetAddressList *get_to(GMimeMessage *message)
+static InternetAddressList *
+get_to (GMimeMessage *message)
 {
     return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_TO);
 }
 
-static InternetAddressList *get_cc(GMimeMessage *message)
+static InternetAddressList *
+get_cc (GMimeMessage *message)
 {
     return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_CC);
 }
 
-static InternetAddressList *get_bcc(GMimeMessage *message)
+static InternetAddressList *
+get_bcc (GMimeMessage *message)
 {
     return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_BCC);
 }
@@ -327,10 +332,10 @@ add_recipients_from_message (GMimeMessage *reply,
        InternetAddressList * (*get_header)(GMimeMessage *message);
        GMimeAddressType recipient_type;
     } reply_to_map[] = {
-       { get_sender,   GMIME_ADDRESS_TYPE_TO },
-       { get_to,       GMIME_ADDRESS_TYPE_TO },
-       { get_cc,       GMIME_ADDRESS_TYPE_CC },
-       { get_bcc,      GMIME_ADDRESS_TYPE_BCC },
+       { get_sender,   GMIME_ADDRESS_TYPE_TO },
+       { get_to,       GMIME_ADDRESS_TYPE_TO },
+       { get_cc,       GMIME_ADDRESS_TYPE_CC },
+       { get_bcc,      GMIME_ADDRESS_TYPE_BCC },
     };
     const char *from_addr = NULL;
     unsigned int i;
@@ -344,7 +349,7 @@ add_recipients_from_message (GMimeMessage *reply,
        n += scan_address_list (recipients, config, reply,
                                reply_to_map[i].recipient_type, &from_addr);
 
-       if (!reply_all && n) {
+       if (! reply_all && n) {
            /* Stop adding new recipients in reply-to-sender mode if
             * we have added some recipient(s) above.
             *
@@ -414,7 +419,7 @@ guess_from_in_received_by (notmuch_config_t *config, const char *received)
        if (*by == '\0')
            break;
        mta = xstrdup (by);
-       token = strtok(mta," \t");
+       token = strtok (mta, " \t");
        if (token == NULL) {
            free (mta);
            break;
@@ -518,12 +523,12 @@ get_from_in_to_headers (notmuch_config_t *config, notmuch_message_t *message)
 }
 
 static GMimeMessage *
-create_reply_message(void *ctx,
-                    notmuch_config_t *config,
-                    notmuch_message_t *message,
-                    GMimeMessage *mime_message,
-                    bool reply_all,
-                    bool limited)
+create_reply_message (void *ctx,
+                     notmuch_config_t *config,
+                     notmuch_message_t *message,
+                     GMimeMessage *mime_message,
+                     bool reply_all,
+                     bool limited)
 {
     const char *subject, *from_addr = NULL;
     const char *in_reply_to, *orig_references, *references;
@@ -533,6 +538,7 @@ create_reply_message(void *ctx,
      * otherwise.
      */
     GMimeMessage *reply = g_mime_message_new (limited ? 0 : 1);
+
     if (reply == NULL) {
        fprintf (stderr, "Out of memory\n");
        return NULL;
@@ -608,11 +614,12 @@ enum {
     FORMAT_HEADERS_ONLY,
 };
 
-static int do_reply(notmuch_config_t *config,
-                   notmuch_query_t *query,
-                   notmuch_show_params_t *params,
-                   int format,
-                   bool reply_all)
+static int
+do_reply (notmuch_config_t *config,
+         notmuch_query_t *query,
+         notmuch_show_params_t *params,
+         int format,
+         bool reply_all)
 {
     GMimeMessage *reply;
     mime_node_t *node;
@@ -645,8 +652,7 @@ static int do_reply(notmuch_config_t *config,
 
     for (;
         notmuch_messages_valid (messages);
-        notmuch_messages_move_to_next (messages))
-    {
+        notmuch_messages_move_to_next (messages)) {
        message = notmuch_messages_get (messages);
 
        if (mime_node_open (config, message, &params->crypto, &node))
@@ -655,7 +661,7 @@ static int do_reply(notmuch_config_t *config,
        reply = create_reply_message (config, config, message,
                                      GMIME_MESSAGE (node->part), reply_all,
                                      format == FORMAT_HEADERS_ONLY);
-       if (!reply)
+       if (! reply)
            return 1;
 
        if (format == FORMAT_JSON || format == FORMAT_SEXP) {
@@ -681,7 +687,7 @@ static int do_reply(notmuch_config_t *config,
                    format_part_reply (stream_stdout, node);
            }
            g_mime_stream_flush (stream_stdout);
-           g_object_unref(stream_stdout);
+           g_object_unref (stream_stdout);
        }
 
        g_object_unref (G_OBJECT (reply));
@@ -709,22 +715,22 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_opt_desc_t options[] = {
        { .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_keyword_t []){ { "default", FORMAT_DEFAULT },
+                                     { "json", FORMAT_JSON },
+                                     { "sexp", FORMAT_SEXP },
+                                     { "headers-only", FORMAT_HEADERS_ONLY },
+                                     { 0, 0 } } },
        { .opt_int = &notmuch_format_version, .name = "format-version" },
        { .opt_keyword = &reply_all, .name = "reply-to", .keywords =
-         (notmuch_keyword_t []){ { "all", true },
-                                 { "sender", false },
-                                 { 0, 0 } } },
-       { .opt_keyword = (int*)(&params.crypto.decrypt), .name = "decrypt",
+             (notmuch_keyword_t []){ { "all", true },
+                                     { "sender", false },
+                                     { 0, 0 } } },
+       { .opt_keyword = (int *) (&params.crypto.decrypt), .name = "decrypt",
          .keyword_no_arg_value = "true", .keywords =
-         (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
-                                 { "auto", NOTMUCH_DECRYPT_AUTO },
-                                 { "true", NOTMUCH_DECRYPT_NOSTASH },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
+                                     { "auto", NOTMUCH_DECRYPT_AUTO },
+                                     { "true", NOTMUCH_DECRYPT_NOSTASH },
+                                     { 0, 0 } } },
        { .opt_inherit = notmuch_shared_options },
        { }
     };
@@ -737,7 +743,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_exit_if_unsupported_format ();
 
-    query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
+    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;
index dee19c206d13b9ceb415a582e99bd747cd95d01b..e2dc3d4550d548ac09f3892e7a6056826d6472a6 100644 (file)
 #include "zlib-extra.h"
 
 static int
-process_config_line (notmuch_database_t *notmuch, const charline)
+process_config_line (notmuch_database_t *notmuch, const char *line)
 {
     const char *key_p, *val_p;
     char *key, *val;
-    size_t key_len,val_len;
+    size_t key_len, val_len;
     const char *delim = " \t\n";
     int ret = EXIT_FAILURE;
 
-    void *local = talloc_new(NULL);
+    void *local = talloc_new (NULL);
 
     key_p = strtok_len_c (line, delim, &key_len);
-    val_p = strtok_len_c (key_p+key_len, delim, &val_len);
+    val_p = strtok_len_c (key_p + key_len, delim, &val_len);
 
     key = talloc_strndup (local, key_p, key_len);
     val = talloc_strndup (local, val_p, val_len);
@@ -52,14 +52,13 @@ process_config_line (notmuch_database_t *notmuch, const char* line)
 
     ret = EXIT_SUCCESS;
 
- DONE:
 DONE:
     talloc_free (local);
     return ret;
 }
 
 static int
-process_properties_line (notmuch_database_t *notmuch, const char* line)
-
+process_properties_line (notmuch_database_t *notmuch, const char *line)
 {
     const char *id_p, *tok;
     size_t id_len = 0, tok_len = 0;
@@ -238,6 +237,7 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
     int opt_index;
     int include = 0;
     int input_format = DUMP_FORMAT_AUTO;
+    int errnum;
 
     if (notmuch_database_open (notmuch_config_get_database_path (config),
                               NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
@@ -248,14 +248,14 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_opt_desc_t options[] = {
        { .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_keyword_t []){ { "auto", DUMP_FORMAT_AUTO },
+                                     { "batch-tag", DUMP_FORMAT_BATCH_TAG },
+                                     { "sup", DUMP_FORMAT_SUP },
+                                     { 0, 0 } } },
        { .opt_flags = &include, .name = "include", .keywords =
-         (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
-                                 { "properties", DUMP_INCLUDE_PROPERTIES },
-                                 { "tags", DUMP_INCLUDE_TAGS} } },
+             (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
+                                     { "properties", DUMP_INCLUDE_PROPERTIES },
+                                     { "tags", DUMP_INCLUDE_TAGS } } },
 
        { .opt_string = &input_file_name, .name = "input" },
        { .opt_bool = &accumulate, .name = "accumulate" },
@@ -330,13 +330,13 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
 
        if (status) {
            fprintf (stderr, "Error reading (gzipped) input: %s\n",
-                    gz_error_string(status, input));
+                    gz_error_string (status, input));
            ret = EXIT_FAILURE;
            goto DONE;
        }
 
        if ((include & DUMP_INCLUDE_CONFIG) && line_len >= 2 && line[0] == '#' && line[1] == '@') {
-           ret = process_config_line(notmuch, line+2);
+           ret = process_config_line (notmuch, line + 2);
            if (ret)
                goto DONE;
        }
@@ -348,8 +348,8 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
 
     } while ((line_len == 0) ||
             (line[0] == '#') ||
-            /* the cast is safe because we checked about for line_len < 0 */
-            (strspn (line, " \t\n") == (unsigned)line_len));
+             /* the cast is safe because we checked about for line_len < 0 */
+            (strspn (line, " \t\n") == (unsigned) line_len));
 
     if (! ((include & DUMP_INCLUDE_TAGS) || (include & DUMP_INCLUDE_PROPERTIES))) {
        ret = EXIT_SUCCESS;
@@ -435,24 +435,27 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
        ret = EXIT_FAILURE;
     }
 
-    /* currently this should not be after DONE: since we don't 
+    /* currently this should not be after DONE: since we don't
      * know if the xregcomp was reached
      */
 
     if (input_format == DUMP_FORMAT_SUP)
        regfree (&regex);
 
- DONE:
 DONE:
     if (line_ctx != NULL)
        talloc_free (line_ctx);
 
     if (notmuch)
        notmuch_database_destroy (notmuch);
 
-    if (input && gzclose_r (input)) {
-       fprintf (stderr, "Error closing %s: %s\n",
-                name_for_error, gzerror (input, NULL));
-       ret = EXIT_FAILURE;
+    if (input) {
+       errnum = gzclose_r (input);
+       if (errnum) {
+           fprintf (stderr, "Error closing %s: %d\n",
+                    name_for_error, errnum);
+           ret = EXIT_FAILURE;
+       }
     }
 
     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
index e2dee4181b7400907723177e1cba687c8a9f21de..2805d960adf1fe2041a828e1554889cc3571c5b8 100644 (file)
@@ -87,13 +87,16 @@ get_thread_query (notmuch_thread_t *thread,
 
     for (messages = notmuch_thread_get_messages (thread);
         notmuch_messages_valid (messages);
-        notmuch_messages_move_to_next (messages))
-    {
+        notmuch_messages_move_to_next (messages)) {
        notmuch_message_t *message = notmuch_messages_get (messages);
        const char *mid = notmuch_message_get_message_id (message);
+       notmuch_bool_t is_set;
+       char **buf;
+
+       if (notmuch_message_get_flag_st (message, NOTMUCH_MESSAGE_FLAG_MATCH, &is_set))
+           return -1;
        /* Determine which query buffer to extend */
-       char **buf = notmuch_message_get_flag (
-           message, NOTMUCH_MESSAGE_FLAG_MATCH) ? matched_out : unmatched_out;
+       buf = is_set ? matched_out : unmatched_out;
        /* Add this message's id: query.  Since "id" is an exclusive
         * prefix, it is implicitly 'or'd together, so we only need to
         * join queries with a space. */
@@ -103,7 +106,7 @@ get_thread_query (notmuch_thread_t *thread,
            *buf = talloc_asprintf_append_buffer (*buf, " %s", escaped);
        else
            *buf = talloc_strdup (thread, escaped);
-       if (!*buf)
+       if (! *buf)
            return -1;
     }
     talloc_free (escaped);
@@ -134,15 +137,14 @@ do_search_threads (search_context_t *ctx)
     }
 
     status = notmuch_query_search_threads (ctx->query, &threads);
-    if (print_status_query("notmuch search", ctx->query, status))
+    if (print_status_query ("notmuch search", ctx->query, status))
        return 1;
 
     format->begin_list (format);
 
     for (i = 0;
         notmuch_threads_valid (threads) && (ctx->limit < 0 || i < ctx->offset + ctx->limit);
-        notmuch_threads_move_to_next (threads), i++)
-    {
+        notmuch_threads_move_to_next (threads), i++) {
        thread = notmuch_threads_get (threads);
 
        if (i < ctx->offset) {
@@ -176,23 +178,23 @@ do_search_threads (search_context_t *ctx)
            relative_date = notmuch_time_relative_date (ctx_quote, date);
 
            if (format->is_text_printer) {
-                /* Special case for the text formatter */
+               /* Special case for the text formatter */
                printf ("thread:%s %12s ",
                        thread_id,
                        relative_date);
                if (total == files)
                    printf ("[%d/%d] %s; %s (",
-                       matched,
-                       total,
-                       sanitize_string (ctx_quote, authors),
-                       sanitize_string (ctx_quote, subject));
+                           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));
+                           matched,
+                           total,
+                           files,
+                           sanitize_string (ctx_quote, authors),
+                           sanitize_string (ctx_quote, subject));
 
            } else { /* Structured Output */
                format->map_key (format, "thread");
@@ -237,12 +239,11 @@ do_search_threads (search_context_t *ctx)
 
            for (tags = notmuch_thread_get_tags (thread);
                 notmuch_tags_valid (tags);
-                notmuch_tags_move_to_next (tags))
-           {
+                notmuch_tags_move_to_next (tags)) {
                const char *tag = notmuch_tags_get (tags);
 
                if (format->is_text_printer) {
-                  /* Special case for the text formatter */
+                   /* Special case for the text formatter */
                    if (first_tag)
                        first_tag = false;
                    else
@@ -269,7 +270,8 @@ do_search_threads (search_context_t *ctx)
     return 0;
 }
 
-static mailbox_t *new_mailbox (void *ctx, const char *name, const char *addr)
+static mailbox_t *
+new_mailbox (void *ctx, const char *name, const char *addr)
 {
     mailbox_t *mailbox;
 
@@ -284,7 +286,8 @@ static mailbox_t *new_mailbox (void *ctx, const char *name, const char *addr)
     return mailbox;
 }
 
-static int mailbox_compare (const void *v1, const void *v2)
+static int
+mailbox_compare (const void *v1, const void *v2)
 {
     const mailbox_t *m1 = v1, *m2 = v2;
     int ret;
@@ -488,7 +491,7 @@ print_popular (const search_context_t *ctx, GList *list)
     }
 
     if (! mailbox)
-       INTERNAL_ERROR("Empty list in address hash table\n");
+       INTERNAL_ERROR ("Empty list in address hash table\n");
 
     /* The original count is no longer needed, so overwrite. */
     mailbox->count = total;
@@ -522,8 +525,8 @@ _count_filenames (notmuch_message_t *message)
     filenames = notmuch_message_get_filenames (message);
 
     while (notmuch_filenames_valid (filenames)) {
-        notmuch_filenames_move_to_next (filenames);
-        i++;
+       notmuch_filenames_move_to_next (filenames);
+       i++;
     }
 
     notmuch_filenames_destroy (filenames);
@@ -561,8 +564,7 @@ do_search_messages (search_context_t *ctx)
 
     for (i = 0;
         notmuch_messages_valid (messages) && (ctx->limit < 0 || i < ctx->offset + ctx->limit);
-        notmuch_messages_move_to_next (messages), i++)
-    {
+        notmuch_messages_move_to_next (messages), i++) {
        if (i < ctx->offset)
            continue;
 
@@ -574,24 +576,23 @@ do_search_messages (search_context_t *ctx)
 
            for (j = 1;
                 notmuch_filenames_valid (filenames);
-                notmuch_filenames_move_to_next (filenames), j++)
-           {
+                notmuch_filenames_move_to_next (filenames), j++) {
                if (ctx->dupe < 0 || ctx->dupe == j) {
                    format->string (format, notmuch_filenames_get (filenames));
                    format->separator (format);
                }
            }
-           
-           notmuch_filenames_destroy( filenames );
+
+           notmuch_filenames_destroy ( filenames );
 
        } else if (ctx->output == OUTPUT_MESSAGES) {
-            /* special case 1 for speed */
-            if (ctx->dupe <= 1 || ctx->dupe <= _count_filenames (message)) {
-                format->set_prefix (format, "id");
-                format->string (format,
-                                notmuch_message_get_message_id (message));
-                format->separator (format);
-            }
+           /* special case 1 for speed */
+           if (ctx->dupe <= 1 || ctx->dupe <= _count_filenames (message)) {
+               format->set_prefix (format, "id");
+               format->string (format,
+                               notmuch_message_get_message_id (message));
+               format->separator (format);
+           }
        } else {
            if (ctx->output & OUTPUT_SENDER) {
                const char *addrs;
@@ -657,8 +658,7 @@ do_search_tags (const search_context_t *ctx)
 
     for (;
         notmuch_tags_valid (tags);
-        notmuch_tags_move_to_next (tags))
-    {
+        notmuch_tags_move_to_next (tags)) {
        tag = notmuch_tags_get (tags);
 
        format->string (format, tag);
@@ -702,7 +702,7 @@ _notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int ar
        break;
     default:
        /* this should never happen */
-       INTERNAL_ERROR("no output format selected");
+       INTERNAL_ERROR ("no output format selected");
     }
 
     notmuch_exit_if_unsupported_format ();
@@ -752,8 +752,8 @@ _notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int ar
        size_t search_exclude_tags_length;
        notmuch_status_t status;
 
-       search_exclude_tags = notmuch_config_get_search_exclude_tags
-           (config, &search_exclude_tags_length);
+       search_exclude_tags = notmuch_config_get_search_exclude_tags (
+           config, &search_exclude_tags_length);
 
        for (i = 0; i < search_exclude_tags_length; i++) {
            status = notmuch_query_add_tag_exclude (ctx->query, search_exclude_tags[i]);
@@ -791,15 +791,15 @@ static search_context_t search_context = {
 
 static const notmuch_opt_desc_t common_options[] = {
     { .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_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
+                                 { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
+                                 { 0, 0 } } },
     { .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_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+                                 { "sexp", NOTMUCH_FORMAT_SEXP },
+                                 { "text", NOTMUCH_FORMAT_TEXT },
+                                 { "text0", NOTMUCH_FORMAT_TEXT0 },
+                                 { 0, 0 } } },
     { .opt_int = &notmuch_format_version, .name = "format-version" },
     { }
 };
@@ -812,18 +812,18 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_opt_desc_t options[] = {
        { .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 } } },
-        { .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_keyword_t []){ { "summary", OUTPUT_SUMMARY },
+                                     { "threads", OUTPUT_THREADS },
+                                     { "messages", OUTPUT_MESSAGES },
+                                     { "files", OUTPUT_FILES },
+                                     { "tags", OUTPUT_TAGS },
+                                     { 0, 0 } } },
+       { .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 } } },
        { .opt_int = &ctx->offset, .name = "offset" },
        { .opt_int = &ctx->limit, .name = "limit" },
        { .opt_int = &ctx->dupe, .name = "duplicate" },
@@ -841,8 +841,8 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 
     if (ctx->output != OUTPUT_FILES && ctx->output != OUTPUT_MESSAGES &&
        ctx->dupe != -1) {
-        fprintf (stderr, "Error: --duplicate=N is only supported with --output=files and --output=messages.\n");
-        return EXIT_FAILURE;
+       fprintf (stderr, "Error: --duplicate=N is only supported with --output=files and --output=messages.\n");
+       return EXIT_FAILURE;
     }
 
     if (_notmuch_search_prepare (ctx, config,
@@ -878,20 +878,20 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_opt_desc_t options[] = {
        { .opt_flags = &ctx->output, .name = "output", .keywords =
-         (notmuch_keyword_t []){ { "sender", OUTPUT_SENDER },
-                                 { "recipients", OUTPUT_RECIPIENTS },
-                                 { "count", OUTPUT_COUNT },
-                                 { "address", OUTPUT_ADDRESS },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "sender", OUTPUT_SENDER },
+                                     { "recipients", OUTPUT_RECIPIENTS },
+                                     { "count", OUTPUT_COUNT },
+                                     { "address", OUTPUT_ADDRESS },
+                                     { 0, 0 } } },
        { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords =
-         (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
-                                 { "false", NOTMUCH_EXCLUDE_FALSE },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
+                                     { "false", NOTMUCH_EXCLUDE_FALSE },
+                                     { 0, 0 } } },
        { .opt_keyword = &ctx->dedup, .name = "deduplicate", .keywords =
-         (notmuch_keyword_t []){ { "no", DEDUP_NONE },
-                                 { "mailbox", DEDUP_MAILBOX },
-                                 { "address", DEDUP_ADDRESS },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "no", DEDUP_NONE },
+                                     { "mailbox", DEDUP_MAILBOX },
+                                     { "address", DEDUP_ADDRESS },
+                                     { 0, 0 } } },
        { .opt_inherit = common_options },
        { .opt_inherit = notmuch_shared_options },
        { }
index 5304800553cfbe3897e2c8774ba702a30b8b9488..cd1a52fffffbac46877c192f176ad91d6c93c0d6 100644 (file)
@@ -47,50 +47,51 @@ static void
 welcome_message_pre_setup (void)
 {
     printf (
-"Welcome to notmuch!\n\n"
-
-"The goal of notmuch is to help you manage and search your collection of\n"
-"email, and to efficiently keep up with the flow of email as it comes in.\n\n"
-
-"Notmuch needs to know a few things about you such as your name and email\n"
-"address, as well as the directory that contains your email. This is where\n"
-"you already have mail stored and where messages will be delivered in the\n"
-"future. This directory can contain any number of sub-directories. Regular\n"
-"files in these directories should be individual email messages. If there\n"
-"are other, non-email files (such as indexes maintained by other email\n"
-"programs) then notmuch will do its best to detect those and ignore them.\n\n"
-
-"If you already have your email being delivered to directories in either\n"
-"maildir or mh format, then that's perfect. Mail storage that uses mbox\n"
-"format, (where one mbox file contains many messages), will not work with\n"
-"notmuch. If that's how your mail is currently stored, we recommend you\n"
-"first convert it to maildir format with a utility such as mb2md. You can\n"
-"continue configuring notmuch now, but be sure to complete the conversion\n"
-"before you run \"notmuch new\" for the first time.\n\n");
+       "Welcome to notmuch!\n\n"
+
+       "The goal of notmuch is to help you manage and search your collection of\n"
+       "email, and to efficiently keep up with the flow of email as it comes in.\n\n"
+
+       "Notmuch needs to know a few things about you such as your name and email\n"
+       "address, as well as the directory that contains your email. This is where\n"
+       "you already have mail stored and where messages will be delivered in the\n"
+       "future. This directory can contain any number of sub-directories. Regular\n"
+       "files in these directories should be individual email messages. If there\n"
+       "are other, non-email files (such as indexes maintained by other email\n"
+       "programs) then notmuch will do its best to detect those and ignore them.\n\n"
+
+       "If you already have your email being delivered to directories in either\n"
+       "maildir or mh format, then that's perfect. Mail storage that uses mbox\n"
+       "format, (where one mbox file contains many messages), will not work with\n"
+       "notmuch. If that's how your mail is currently stored, we recommend you\n"
+       "first convert it to maildir format with a utility such as mb2md. You can\n"
+       "continue configuring notmuch now, but be sure to complete the conversion\n"
+       "before you run \"notmuch new\" for the first time.\n\n");
 }
 
 static void
 welcome_message_post_setup (void)
 {
     printf ("\n"
-"Notmuch is now configured, and the configuration settings are saved in\n"
-"a file in your home directory named .notmuch-config . If you'd like to\n"
-"change the configuration in the future, you can either edit that file\n"
-"directly or run \"notmuch setup\".  To choose an alternate configuration\n"
-"location, set ${NOTMUCH_CONFIG}.\n\n"
-
-"The next step is to run \"notmuch new\" which will create a database\n"
-"that indexes all of your mail. Depending on the amount of mail you have\n"
-"the initial indexing process can take a long time, so expect that.\n"
-"Also, the resulting database will require roughly the same amount of\n"
-"storage space as your current collection of email. So please ensure you\n"
-"have sufficient storage space available now.\n\n");
+           "Notmuch is now configured, and the configuration settings are saved in\n"
+           "a file in your home directory named .notmuch-config . If you'd like to\n"
+           "change the configuration in the future, you can either edit that file\n"
+           "directly or run \"notmuch setup\".  To choose an alternate configuration\n"
+           "location, set ${NOTMUCH_CONFIG}.\n\n"
+
+           "The next step is to run \"notmuch new\" which will create a database\n"
+           "that indexes all of your mail. Depending on the amount of mail you have\n"
+           "the initial indexing process can take a long time, so expect that.\n"
+           "Also, the resulting database will require roughly the same amount of\n"
+           "storage space as your current collection of email. So please ensure you\n"
+           "have sufficient storage space available now.\n\n");
 }
 
 static void
 print_tag_list (const char **tags, size_t tags_len)
 {
     unsigned int i;
+
     for (i = 0; i < tags_len; i++) {
        if (i != 0)
            printf (" ");
@@ -121,7 +122,7 @@ parse_tag_list (void *ctx, char *response)
 
 int
 notmuch_setup_command (notmuch_config_t *config,
-                      unused (int argc), unused (char *argv[]))
+                      int argc, char *argv[])
 {
     char *response = NULL;
     size_t response_size = 0;
@@ -134,15 +135,15 @@ notmuch_setup_command (notmuch_config_t *config,
     const char **search_exclude_tags;
     size_t search_exclude_tags_len;
 
-#define prompt(format, ...)                                    \
-    do {                                                       \
-       printf (format, ##__VA_ARGS__);                         \
-       fflush (stdout);                                        \
-       if (getline (&response, &response_size, stdin) < 0) {   \
-           printf ("Exiting.\n");                              \
-           exit (EXIT_FAILURE);                                \
-       }                                                       \
-       chomp_newline (response);                               \
+#define prompt(format, ...)                                     \
+    do {                                                        \
+       printf (format, ##__VA_ARGS__);                         \
+       fflush (stdout);                                        \
+       if (getline (&response, &response_size, stdin) < 0) {   \
+           printf ("Exiting.\n");                              \
+           exit (EXIT_FAILURE);                                \
+       }                                                       \
+       chomp_newline (response);                               \
     } while (0)
 
     if (notmuch_minimal_options ("setup", argc, argv) < 0)
@@ -167,14 +168,14 @@ notmuch_setup_command (notmuch_config_t *config,
     other_emails = g_ptr_array_new ();
 
     old_other_emails = notmuch_config_get_user_other_email (config,
-                                            &old_other_emails_len);
+                                                           &old_other_emails_len);
     for (i = 0; i < old_other_emails_len; i++) {
        prompt ("Additional email address [%s]: ", old_other_emails[i]);
        if (strlen (response))
            g_ptr_array_add (other_emails, talloc_strdup (config, response));
        else
            g_ptr_array_add (other_emails, talloc_strdup (config,
-                                                        old_other_emails[i]));
+                                                         old_other_emails[i]));
     }
 
     do {
index 4dfe9c1d9def25c6294e335f999d2b5891b33ebb..dd836adda6ad6929680244c7c8628429466a32f3 100644 (file)
@@ -37,8 +37,7 @@ _get_tags_as_string (const void *ctx, notmuch_message_t *message)
 
     for (tags = notmuch_message_get_tags (message);
         notmuch_tags_valid (tags);
-        notmuch_tags_move_to_next (tags))
-    {
+        notmuch_tags_move_to_next (tags)) {
        tag = notmuch_tags_get (tags);
 
        result = talloc_asprintf_append (result, "%s%s",
@@ -69,17 +68,30 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
                            from, relative_date, tags);
 }
 
-static const char *_get_disposition(GMimeObject *meta)
+static const char *
+_get_disposition (GMimeObject *meta)
 {
     GMimeContentDisposition *disposition;
 
     disposition = g_mime_object_get_content_disposition (meta);
-    if (!disposition)
+    if (! disposition)
        return NULL;
 
     return g_mime_content_disposition_get_disposition (disposition);
 }
 
+static bool _get_message_flag (notmuch_message_t *message, notmuch_message_flag_t flag) {
+    notmuch_bool_t is_set;
+    notmuch_status_t status;
+
+    status = notmuch_message_get_flag_st (message, flag, &is_set);
+
+    if (print_status_message ("notmuch show", message, status))
+       INTERNAL_ERROR("unexpected error getting message flag\n");
+
+    return is_set;
+}
+
 /* Emit a sequence of key/value pairs for the metadata of message.
  * The caller should begin a map before calling this. */
 static void
@@ -97,10 +109,10 @@ format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
     sp->string (sp, notmuch_message_get_message_id (message));
 
     sp->map_key (sp, "match");
-    sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH));
+    sp->boolean (sp, _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH));
 
     sp->map_key (sp, "excluded");
-    sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED));
+    sp->boolean (sp, _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED));
 
     sp->map_key (sp, "filename");
     if (notmuch_format_version >= 3) {
@@ -170,7 +182,7 @@ _extract_email_address (const void *ctx, const char *from)
        g_object_unref (addresses);
 
     return email;
-   }
+}
 
 /* Return 1 if 'line' is an mbox From_ line---that is, a line
  * beginning with zero or more '>' characters followed by the
@@ -290,7 +302,7 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
     charset = g_mime_object_get_content_type_parameter (part, "charset");
     charset = charset ? g_mime_charset_canon_name (charset) : NULL;
     wrapper = g_mime_part_get_content (GMIME_PART (part));
-    if (wrapper && charset && !g_ascii_strncasecmp (charset, "iso-8859-", 9)) {
+    if (wrapper && charset && ! g_ascii_strncasecmp (charset, "iso-8859-", 9)) {
        GMimeStream *null_stream = NULL;
        GMimeStream *null_stream_filter = NULL;
 
@@ -298,10 +310,10 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
        null_stream = g_mime_stream_null_new ();
        null_stream_filter = g_mime_stream_filter_new (null_stream);
        windows_filter = g_mime_filter_windows_new (charset);
-       g_mime_stream_filter_add(GMIME_STREAM_FILTER (null_stream_filter),
-                                windows_filter);
+       g_mime_stream_filter_add (GMIME_STREAM_FILTER (null_stream_filter),
+                                 windows_filter);
        g_mime_data_wrapper_write_to_stream (wrapper, null_stream_filter);
-       charset = g_mime_filter_windows_real_charset(
+       charset = g_mime_filter_windows_real_charset (
            (GMimeFilterWindows *) windows_filter);
 
        if (null_stream_filter)
@@ -314,8 +326,8 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
 
     stream_filter = g_mime_stream_filter_new (stream_out);
     crlf_filter = g_mime_filter_dos2unix_new (false);
-    g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter),
-                            crlf_filter);
+    g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
+                             crlf_filter);
     g_object_unref (crlf_filter);
 
     if (charset) {
@@ -345,12 +357,12 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
     if (wrapper && stream_filter)
        g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
     if (stream_filter)
-       g_object_unref(stream_filter);
+       g_object_unref (stream_filter);
     if (windows_filter)
        g_object_unref (windows_filter);
 }
 
-static const char*
+static const char *
 signature_status_to_string (GMimeSignatureStatus status)
 {
     if (g_mime_signature_status_bad (status))
@@ -368,12 +380,13 @@ signature_status_to_string (GMimeSignatureStatus status)
 /* Print signature flags */
 struct key_map_struct {
     GMimeSignatureStatus bit;
-    const char * string;
+    const char *string;
 };
 
 static void
 do_format_signature_errors (sprinter_t *sp, struct key_map_struct *key_map,
-                           unsigned int array_map_len, GMimeSignatureStatus errors) {
+                           unsigned int array_map_len, GMimeSignatureStatus errors)
+{
     sp->map_key (sp, "errors");
     sp->begin_map (sp);
 
@@ -392,22 +405,22 @@ format_signature_errors (sprinter_t *sp, GMimeSignature *signature)
 {
     GMimeSignatureStatus errors = g_mime_signature_get_status (signature);
 
-    if (!(errors & GMIME_SIGNATURE_STATUS_ERROR_MASK))
+    if (! (errors & GMIME_SIGNATURE_STATUS_ERROR_MASK))
        return;
 
     struct key_map_struct key_map[] = {
-       { GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key-revoked"},
-       { GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key-expired"},
+       { GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key-revoked" },
+       { GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key-expired" },
        { GMIME_SIGNATURE_STATUS_SIG_EXPIRED, "sig-expired" },
-       { GMIME_SIGNATURE_STATUS_KEY_MISSING, "key-missing"},
-       { GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl-missing"},
-       { GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl-too-old"},
-       { GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad-policy"},
-       { GMIME_SIGNATURE_STATUS_SYS_ERROR, "sys-error"},
-       { GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu-conflict"},
+       { GMIME_SIGNATURE_STATUS_KEY_MISSING, "key-missing" },
+       { GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl-missing" },
+       { GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl-too-old" },
+       { GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad-policy" },
+       { GMIME_SIGNATURE_STATUS_SYS_ERROR, "sys-error" },
+       { GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu-conflict" },
     };
 
-    do_format_signature_errors (sp, key_map, ARRAY_SIZE(key_map), errors);
+    do_format_signature_errors (sp, key_map, ARRAY_SIZE (key_map), errors);
 }
 
 /* Signature status sprinter */
@@ -419,7 +432,7 @@ format_part_sigstatus_sprinter (sprinter_t *sp, GMimeSignatureList *siglist)
 
     sp->begin_list (sp);
 
-    if (!siglist) {
+    if (! siglist) {
        sp->end (sp);
        return;
     }
@@ -479,7 +492,7 @@ format_part_sigstatus_sprinter (sprinter_t *sp, GMimeSignatureList *siglist)
        }
 
        sp->end (sp);
-     }
+    }
 
     sp->end (sp);
 }
@@ -490,8 +503,8 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
 {
     /* The disposition and content-type metadata are associated with
      * the envelope for message parts */
-    GMimeObject *meta = node->envelope_part ?
-       GMIME_OBJECT (node->envelope_part) : node->part;
+    GMimeObject *meta = node->envelope_part ? (
+       GMIME_OBJECT (node->envelope_part) : node->part;
     GMimeContentType *content_type = g_mime_object_get_content_type (meta);
     const bool leaf = GMIME_IS_PART (node->part);
     GMimeStream *stream = params->out_stream;
@@ -506,15 +519,15 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
                              part_type,
                              notmuch_message_get_message_id (message),
                              indent,
-                             notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0,
-                             notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0,
+                             _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0,
+                             _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0,
                              notmuch_message_get_filename (message));
     } else {
        char *content_string;
        const char *disposition = _get_disposition (meta);
        const char *cid = g_mime_object_get_content_id (meta);
-       const char *filename = leaf ?
-           g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
+       const char *filename = leaf ? (
+           g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
 
        if (disposition &&
            strcasecmp (disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
@@ -555,8 +568,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
        g_mime_stream_printf (stream, "Date: %s\n", date_string);
        g_mime_stream_printf (stream, "\fheader}\n");
 
-       if (!params->output_body)
-       {
+       if (! params->output_body) {
            g_mime_stream_printf (stream, "\f%s}\n", part_type);
            return NOTMUCH_STATUS_SUCCESS;
        }
@@ -566,8 +578,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
     if (leaf) {
        if (g_mime_content_type_is_type (content_type, "text", "*") &&
            (params->include_html ||
-            ! g_mime_content_type_is_type (content_type, "text", "html")))
-       {
+            ! g_mime_content_type_is_type (content_type, "text", "html"))) {
            show_text_part_content (node->part, stream, 0);
        } else {
            char *content_string = g_mime_content_type_get_mime_type (content_type);
@@ -688,14 +699,14 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
 
     /* The disposition and content-type metadata are associated with
      * the envelope for message parts */
-    GMimeObject *meta = node->envelope_part ?
-       GMIME_OBJECT (node->envelope_part) : node->part;
+    GMimeObject *meta = node->envelope_part ? (
+       GMIME_OBJECT (node->envelope_part) : node->part;
     GMimeContentType *content_type = g_mime_object_get_content_type (meta);
     char *content_string;
     const char *disposition = _get_disposition (meta);
     const char *cid = g_mime_object_get_content_id (meta);
-    const char *filename = GMIME_IS_PART (node->part) ?
-       g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
+    const char *filename = GMIME_IS_PART (node->part) ? (
+       g_mime_part_get_filename (GMIME_PART (node->part) ) ) : NULL;
     int nclose = 0;
     int i;
 
@@ -751,8 +762,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
         */
        if (g_mime_content_type_is_type (content_type, "text", "*") &&
            (include_html ||
-            ! g_mime_content_type_is_type (content_type, "text", "html")))
-       {
+            ! g_mime_content_type_is_type (content_type, "text", "html"))) {
            GMimeStream *stream_memory = g_mime_stream_mem_new ();
            GByteArray *part_content;
            show_text_part_content (node->part, stream_memory, 0);
@@ -761,7 +771,16 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
            sp->string_len (sp, (char *) part_content->data, part_content->len);
            g_object_unref (stream_memory);
        } else {
-           format_omitted_part_meta_sprinter (sp, meta, GMIME_PART (node->part));
+           /* if we have a child part despite being a standard
+            * (non-multipart) MIME part, that means there is
+            * something to unwrap, which we will present in
+            * content: */
+           if (node->nchildren) {
+               sp->map_key (sp, "content");
+               sp->begin_list (sp);
+               nclose = 1;
+           } else
+               format_omitted_part_meta_sprinter (sp, meta, GMIME_PART (node->part));
        }
     } else if (GMIME_IS_MULTIPART (node->part)) {
        sp->map_key (sp, "content");
@@ -824,7 +843,7 @@ format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node,
     ssize_t line_size;
     ssize_t line_len;
 
-    if (!message)
+    if (! message)
        INTERNAL_ERROR ("format_part_mbox requires a root part");
 
     filename = notmuch_message_get_filename (message);
@@ -883,7 +902,7 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
        }
 
        while (! g_mime_stream_eos (stream)) {
-           ssize = g_mime_stream_read (stream, buf, sizeof(buf));
+           ssize = g_mime_stream_read (stream, buf, sizeof (buf));
            if (ssize < 0) {
                fprintf (stderr, "Error: Read failed from %s\n", filename);
                goto DONE;
@@ -898,7 +917,7 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
        ret = NOTMUCH_STATUS_SUCCESS;
 
        /* XXX This DONE is just for the special case of a node in a single file */
-    DONE:
+      DONE:
        if (stream)
            g_object_unref (stream);
 
@@ -990,20 +1009,19 @@ show_messages (void *ctx,
 
     for (;
         notmuch_messages_valid (messages);
-        notmuch_messages_move_to_next (messages))
-    {
+        notmuch_messages_move_to_next (messages)) {
        sp->begin_list (sp);
 
        message = notmuch_messages_get (messages);
 
-       match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
-       excluded = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED);
+       match = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
+       excluded = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED);
 
        next_indent = indent;
 
-       if ((match && (!excluded || !params->omit_excluded)) || params->entire_thread) {
+       if ((match && (! excluded || ! params->omit_excluded)) || params->entire_thread) {
            status = show_message (ctx, format, sp, message, indent, params);
-           if (status && !res)
+           if (status && ! res)
                res = status;
            next_indent = indent + 1;
        } else {
@@ -1015,7 +1033,7 @@ show_messages (void *ctx,
                                notmuch_message_get_replies (message),
                                next_indent,
                                params);
-       if (status && !res)
+       if (status && ! res)
            res = status;
 
        notmuch_message_destroy (message);
@@ -1064,32 +1082,31 @@ do_show_single (void *ctx,
     notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1);
 
     return show_message (ctx, format, sp, message, 0, params)
-       != NOTMUCH_STATUS_SUCCESS;
+          != NOTMUCH_STATUS_SUCCESS;
 }
 
 /* Formatted output of threads */
 static int
-do_show (void *ctx,
-        notmuch_query_t *query,
-        const notmuch_show_format_t *format,
-        sprinter_t *sp,
-        notmuch_show_params_t *params)
+do_show_threaded (void *ctx,
+                 notmuch_query_t *query,
+                 const notmuch_show_format_t *format,
+                 sprinter_t *sp,
+                 notmuch_show_params_t *params)
 {
     notmuch_threads_t *threads;
     notmuch_thread_t *thread;
     notmuch_messages_t *messages;
     notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
 
-    status= notmuch_query_search_threads (query, &threads);
+    status = notmuch_query_search_threads (query, &threads);
     if (print_status_query ("notmuch show", query, status))
        return 1;
 
     sp->begin_list (sp);
 
-    for ( ;
+    for (;
         notmuch_threads_valid (threads);
-        notmuch_threads_move_to_next (threads))
-    {
+        notmuch_threads_move_to_next (threads)) {
        thread = notmuch_threads_get (threads);
 
        messages = notmuch_thread_get_toplevel_messages (thread);
@@ -1099,7 +1116,7 @@ do_show (void *ctx,
                            notmuch_thread_get_thread_id (thread));
 
        status = show_messages (ctx, format, sp, messages, 0, params);
-       if (status && !res)
+       if (status && ! res)
            res = status;
 
        notmuch_thread_destroy (thread);
@@ -1111,6 +1128,50 @@ do_show (void *ctx,
     return res != NOTMUCH_STATUS_SUCCESS;
 }
 
+static int
+do_show_unthreaded (void *ctx,
+                   notmuch_query_t *query,
+                   const notmuch_show_format_t *format,
+                   sprinter_t *sp,
+                   notmuch_show_params_t *params)
+{
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
+    notmuch_bool_t excluded;
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (print_status_query ("notmuch show", query, status))
+       return 1;
+
+    sp->begin_list (sp);
+
+    for (;
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages)) {
+       sp->begin_list (sp);
+       sp->begin_list (sp);
+
+       message = notmuch_messages_get (messages);
+
+       notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, TRUE);
+       excluded = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED);
+
+       if (!excluded || !params->omit_excluded) {
+           status = show_message (ctx, format, sp, message, 0, params);
+           if (status && !res)
+               res = status;
+       } else {
+           sp->null (sp);
+       }
+       notmuch_message_destroy (message);
+       sp->end (sp);
+       sp->end (sp);
+    }
+    sp->end (sp);
+    return res;
+}
+
 enum {
     NOTMUCH_FORMAT_NOT_SPECIFIED,
     NOTMUCH_FORMAT_JSON,
@@ -1172,27 +1233,29 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
     bool exclude = true;
     bool entire_thread_set = false;
     bool single_message;
+    bool unthreaded = FALSE;
 
     notmuch_opt_desc_t options[] = {
        { .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_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+                                     { "text", NOTMUCH_FORMAT_TEXT },
+                                     { "sexp", NOTMUCH_FORMAT_SEXP },
+                                     { "mbox", NOTMUCH_FORMAT_MBOX },
+                                     { "raw", NOTMUCH_FORMAT_RAW },
+                                     { 0, 0 } } },
        { .opt_int = &notmuch_format_version, .name = "format-version" },
        { .opt_bool = &exclude, .name = "exclude" },
        { .opt_bool = &params.entire_thread, .name = "entire-thread",
          .present = &entire_thread_set },
+       { .opt_bool = &unthreaded, .name = "unthreaded" },
        { .opt_int = &params.part, .name = "part" },
-       { .opt_keyword = (int*)(&params.crypto.decrypt), .name = "decrypt",
+       { .opt_keyword = (int *) (&params.crypto.decrypt), .name = "decrypt",
          .keyword_no_arg_value = "true", .keywords =
-         (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
-                                 { "auto", NOTMUCH_DECRYPT_AUTO },
-                                 { "true", NOTMUCH_DECRYPT_NOSTASH },
-                                 { "stash", NOTMUCH_DECRYPT_TRUE },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
+                                     { "auto", NOTMUCH_DECRYPT_AUTO },
+                                     { "true", NOTMUCH_DECRYPT_NOSTASH },
+                                     { "stash", NOTMUCH_DECRYPT_TRUE },
+                                     { 0, 0 } } },
        { .opt_bool = &params.crypto.verify, .name = "verify" },
        { .opt_bool = &params.output_body, .name = "body" },
        { .opt_bool = &params.include_html, .name = "include-html" },
@@ -1240,7 +1303,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
        (format == NOTMUCH_FORMAT_JSON || format == NOTMUCH_FORMAT_SEXP))
        params.entire_thread = true;
 
-    if (!params.output_body) {
+    if (! params.output_body) {
        if (params.part > 0) {
            fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
            params.output_body = true;
@@ -1254,13 +1317,13 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
     }
 
     if (params.include_html &&
-        (format != NOTMUCH_FORMAT_TEXT &&
+       (format != NOTMUCH_FORMAT_TEXT &&
         format != NOTMUCH_FORMAT_JSON &&
         format != NOTMUCH_FORMAT_SEXP)) {
        fprintf (stderr, "Warning: --include-html only implemented for format=text, format=json and format=sexp\n");
     }
 
-    query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
+    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;
@@ -1288,7 +1351,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
 
     /* Create structure printer. */
     formatter = formatters[format];
-    sprinter = formatter->new_sprinter(config, stdout);
+    sprinter = formatter->new_sprinter (config, stdout);
 
     params.out_stream = g_mime_stream_stdout_new ();
 
@@ -1305,7 +1368,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
        notmuch_status_t status;
 
        search_exclude_tags = notmuch_config_get_search_exclude_tags
-           (config, &search_exclude_tags_length);
+                                 (config, &search_exclude_tags_length);
 
        for (i = 0; i < search_exclude_tags_length; i++) {
            status = notmuch_query_add_tag_exclude (query, search_exclude_tags[i]);
@@ -1321,10 +1384,13 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
            params.omit_excluded = false;
        }
 
-       ret = do_show (config, query, formatter, sprinter, &params);
+       if (unthreaded)
+           ret = do_show_unthreaded (config, query, formatter, sprinter, &params);
+       else
+           ret = do_show_threaded (config, query, formatter, sprinter, &params);
     }
 
- DONE:
 DONE:
     g_mime_stream_flush (params.out_stream);
     g_object_unref (params.out_stream);
 
index 2734b36a566e5a9fe63cd9ff0d1246405116abca..cc7ffc239fb003249e5b8d62301870c1c2051305 100644 (file)
  *
  */
 #define MINUTE (60)
-#define HOUR (60 * MINUTE)
-#define DAY (24 * HOUR)
+#define HOUR (60 *MINUTE)
+#define DAY (24 *HOUR)
 #define RELATIVE_DATE_MAX 20
 const char *
 notmuch_time_relative_date (const void *ctx, time_t then)
 {
     struct tm tm_now, tm_then;
-    time_t now = time(NULL);
+    time_t now = time (NULL);
     time_t delta;
     char *result;
 
@@ -76,26 +76,25 @@ notmuch_time_relative_date (const void *ctx, time_t then)
 
     if (delta <= 7 * DAY) {
        if (tm_then.tm_wday == tm_now.tm_wday &&
-           delta < DAY)
-       {
+           delta < DAY) {
            strftime (result, RELATIVE_DATE_MAX,
-                     "Today %R", &tm_then); /* Today 12:30 */
+                     "Today %R", &tm_then);    /* Today 12:30 */
            return result;
        } else if ((tm_now.tm_wday + 7 - tm_then.tm_wday) % 7 == 1) {
            strftime (result, RELATIVE_DATE_MAX,
-                     "Yest. %R", &tm_then); /* Yest. 12:30 */
+                     "Yest. %R", &tm_then);    /* Yest. 12:30 */
            return result;
        } else {
            if (tm_then.tm_wday != tm_now.tm_wday) {
                strftime (result, RELATIVE_DATE_MAX,
-                         "%a. %R", &tm_then); /* Mon. 12:30 */
+                         "%a. %R", &tm_then);  /* Mon. 12:30 */
                return result;
            }
        }
     }
 
     strftime (result, RELATIVE_DATE_MAX,
-             "%B %d", &tm_then); /* October 12 */
+             "%B %d", &tm_then);               /* October 12 */
     return result;
 }
 #undef MINUTE
index eeb794e85216115adaf8583744f51f382f8e5817..4ef1484fd8668a7973d7a10806f18a5257764822 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -61,9 +61,10 @@ const notmuch_opt_desc_t notmuch_shared_options [] = {
  * notmuch_process_shared_options (subcommand_name);
  */
 void
-notmuch_process_shared_options (const char *subcommand_name) {
+notmuch_process_shared_options (const char *subcommand_name)
+{
     if (print_version) {
-       printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
+       printf ("notmuch " STRINGIFY (NOTMUCH_VERSION) "\n");
        exit (EXIT_SUCCESS);
     }
 
@@ -76,8 +77,9 @@ notmuch_process_shared_options (const char *subcommand_name) {
 /* This is suitable for subcommands that do not actually open the
  * database.
  */
-int notmuch_minimal_options (const char *subcommand_name,
-                                 int argc, char **argv)
+int
+notmuch_minimal_options (const char *subcommand_name,
+                        int argc, char **argv)
 {
     int opt_index;
 
@@ -98,14 +100,14 @@ int notmuch_minimal_options (const char *subcommand_name,
 
 
 struct _notmuch_client_indexing_cli_choices indexing_cli_choices = { };
-const notmuch_opt_desc_t  notmuch_shared_indexing_options [] = {
+const notmuch_opt_desc_t notmuch_shared_indexing_options [] = {
     { .opt_keyword = &indexing_cli_choices.decrypt_policy,
       .present = &indexing_cli_choices.decrypt_policy_set, .keywords =
-      (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
-                             { "true", NOTMUCH_DECRYPT_TRUE },
-                             { "auto", NOTMUCH_DECRYPT_AUTO },
-                             { "nostash", NOTMUCH_DECRYPT_NOSTASH },
-                             { 0, 0 } },
+         (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
+                                 { "true", NOTMUCH_DECRYPT_TRUE },
+                                 { "auto", NOTMUCH_DECRYPT_AUTO },
+                                 { "nostash", NOTMUCH_DECRYPT_NOSTASH },
+                                 { 0, 0 } },
       .name = "decrypt" },
     { }
 };
@@ -192,7 +194,7 @@ find_command (const char *name)
     size_t i;
 
     for (i = 0; i < ARRAY_SIZE (commands); i++)
-       if ((!name && !commands[i].name) ||
+       if ((! name && ! commands[i].name) ||
            (name && commands[i].name && strcmp (name, commands[i].name) == 0))
            return &commands[i];
 
@@ -270,11 +272,11 @@ notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
 {
     const char *uuid = NULL;
 
-    if (!notmuch_requested_db_uuid)
+    if (! notmuch_requested_db_uuid)
        return;
     IGNORE_RESULT (notmuch_database_get_revision (notmuch, &uuid));
 
-    if (strcmp (notmuch_requested_db_uuid, uuid) != 0){
+    if (strcmp (notmuch_requested_db_uuid, uuid) != 0) {
        fprintf (stderr, "Error: requested database revision %s does not match %s\n",
                 notmuch_requested_db_uuid, uuid);
        exit (1);
@@ -297,7 +299,7 @@ _help_for (const char *topic_name)
     help_topic_t *topic;
     unsigned int i;
 
-    if (!topic_name) {
+    if (! topic_name) {
        printf ("The notmuch mail system.\n\n");
        usage (stdout);
        return EXIT_SUCCESS;
@@ -333,7 +335,7 @@ _help_for (const char *topic_name)
 }
 
 static int
-notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[])
+notmuch_help_command (unused (notmuch_config_t *config), int argc, char *argv[])
 {
     int opt_index;
 
@@ -342,8 +344,8 @@ notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[]
        return EXIT_FAILURE;
 
     /* skip at least subcommand argument */
-    argc-= opt_index;
-    argv+= opt_index;
+    argc -= opt_index;
+    argv += opt_index;
 
     if (argc == 0) {
        return _help_for (NULL);
@@ -358,7 +360,7 @@ notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[]
  */
 static int
 notmuch_command (notmuch_config_t *config,
-                unused(int argc), unused(char *argv[]))
+                unused(int argc), unused(char **argv))
 {
     char *db_path;
     struct stat st;
@@ -417,7 +419,8 @@ notmuch_command (notmuch_config_t *config,
  * executed. Return true if external command is not found. Return
  * false on errors.
  */
-static bool try_external_command(char *argv[])
+static bool
+try_external_command (char *argv[])
 {
     char *old_argv0 = argv[0];
     bool ret = true;
@@ -431,7 +434,7 @@ static bool try_external_command(char *argv[])
     execvp (argv[0], argv);
     if (errno != ENOENT) {
        fprintf (stderr, "Error: Running external command '%s' failed: %s\n",
-                argv[0], strerror(errno));
+                argv[0], strerror (errno));
        ret = false;
     }
 
@@ -464,7 +467,7 @@ main (int argc, char *argv[])
     local = talloc_new (NULL);
 
     g_mime_init ();
-#if !GLIB_CHECK_VERSION(2, 35, 1)
+#if ! GLIB_CHECK_VERSION (2, 35, 1)
     g_type_init ();
 #endif
 
@@ -484,9 +487,9 @@ main (int argc, char *argv[])
 
     command = find_command (command_name);
     /* if command->function is NULL, try external command */
-    if (!command || !command->function) {
+    if (! command || ! command->function) {
        /* This won't return if the external command is found. */
-       if (try_external_command(argv + opt_index))
+       if (try_external_command (argv + opt_index))
            fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
                     command_name);
        ret = EXIT_FAILURE;
@@ -494,7 +497,7 @@ main (int argc, char *argv[])
     }
 
     config = notmuch_config_open (local, config_file_name, command->config_mode);
-    if (!config) {
+    if (! config) {
        ret = EXIT_FAILURE;
        goto DONE;
     }
index 53534f3ed7d0f86beb0c8af101fdb67badf4846c..ee8030cc011a00081bda39810d2c54780a07ffce 100644 (file)
@@ -1,3 +1,5 @@
+# -*- makefile-gmake -*-
+
 dir := parse-time-string
 extra_cflags += -I$(srcdir)/$(dir)
 
index 48ec5b0c1843394aac2226bd58f62c7219033665..33372ecedf90af0d16b565a0d7d5d35398ce05a5 100644 (file)
 /* XXX: Redefine these to add i18n support. The keyword table uses
  * N_() to mark strings to be translated; they are accessed
  * dynamically using _(). */
-#define _(s) (s)       /* i18n: define as gettext (s) */
-#define N_(s) (s)      /* i18n: define as gettext_noop (s) */
+#define _(s) (s)        /* i18n: define as gettext (s) */
+#define N_(s) (s)       /* i18n: define as gettext_noop (s) */
 
 #define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
 
  */
 enum field {
     /* Keep SEC...YEAR in this order. */
-    TM_ABS_SEC,                /* seconds */
-    TM_ABS_MIN,                /* minutes */
-    TM_ABS_HOUR,       /* hours */
-    TM_ABS_MDAY,       /* day of the month */
-    TM_ABS_MON,                /* month */
-    TM_ABS_YEAR,       /* year */
+    TM_ABS_SEC,         /* seconds */
+    TM_ABS_MIN,         /* minutes */
+    TM_ABS_HOUR,        /* hours */
+    TM_ABS_MDAY,        /* day of the month */
+    TM_ABS_MON,         /* month */
+    TM_ABS_YEAR,        /* year */
 
-    TM_WDAY,           /* day of the week. special: may be relative */
-    TM_ABS_ISDST,      /* daylight saving time */
+    TM_WDAY,            /* day of the week. special: may be relative */
+    TM_ABS_ISDST,       /* daylight saving time */
 
-    TM_AMPM,           /* am vs. pm */
-    TM_TZ,             /* timezone in minutes */
+    TM_AMPM,            /* am vs. pm */
+    TM_TZ,              /* timezone in minutes */
 
     /* Keep SEC...YEAR in this order. */
-    TM_REL_SEC,                /* seconds relative to absolute or reference time */
-    TM_REL_MIN,                /* minutes ... */
-    TM_REL_HOUR,       /* hours ... */
-    TM_REL_DAY,                /* days ... */
-    TM_REL_MON,                /* months ... */
-    TM_REL_YEAR,       /* years ... */
-    TM_REL_WEEK,       /* weeks ... */
-
-    TM_NONE,           /* not a field */
-
-    TM_SIZE = TM_NONE,
-    TM_FIRST_ABS = TM_ABS_SEC,
-    TM_FIRST_REL = TM_REL_SEC,
+    TM_REL_SEC,         /* seconds relative to absolute or reference time */
+    TM_REL_MIN,         /* minutes ... */
+    TM_REL_HOUR,        /* hours ... */
+    TM_REL_DAY,         /* days ... */
+    TM_REL_MON,         /* months ... */
+    TM_REL_YEAR,        /* years ... */
+    TM_REL_WEEK,        /* weeks ... */
+
+    TM_NONE,            /* not a field */
+
+    TM_SIZE            = TM_NONE,
+    TM_FIRST_ABS       = TM_ABS_SEC,
+    TM_FIRST_REL       = TM_REL_SEC,
 };
 
 /* Values for the set array of struct state. */
 enum field_set {
-    FIELD_UNSET,       /* The field has not been touched by parser. */
-    FIELD_SET,         /* The field has been set by parser. */
-    FIELD_NOW,         /* The field will be set to reference time. */
+    FIELD_UNSET,        /* The field has not been touched by parser. */
+    FIELD_SET,          /* The field has been set by parser. */
+    FIELD_NOW,          /* The field will be set to reference time. */
 };
 
 static enum field
@@ -180,15 +180,15 @@ get_field_epoch_value (enum field field)
 
 /* The parsing state. */
 struct state {
-    int tm[TM_SIZE];                   /* parsed date and time */
-    enum field_set set[TM_SIZE];       /* set status of tm */
+    int tm[TM_SIZE];                    /* parsed date and time */
+    enum field_set set[TM_SIZE];        /* set status of tm */
 
-    enum field last_field;     /* Previously set field. */
+    enum field last_field;              /* Previously set field. */
     char delim;
 
-    int postponed_length;      /* Number of digits in postponed value. */
+    int postponed_length;               /* Number of digits in postponed value. */
     int postponed_value;
-    char postponed_delim;      /* The delimiter preceding postponed number. */
+    char postponed_delim;               /* The delimiter preceding postponed number. */
 };
 
 /*
@@ -215,7 +215,7 @@ get_postponed_length (struct state *state)
 static bool
 consume_postponed_number (struct state *state, int *v, int *n, char *d)
 {
-    if (!state->postponed_length)
+    if (! state->postponed_length)
        return false;
 
     if (n)
@@ -383,12 +383,14 @@ get_field (struct state *state, enum field field)
 /*
  * Validity checkers.
  */
-static bool is_valid_12hour (int h)
+static bool
+is_valid_12hour (int h)
 {
     return h >= 1 && h <= 12;
 }
 
-static bool is_valid_time (int h, int m, int s)
+static bool
+is_valid_time (int h, int m, int s)
 {
     /* Allow 24:00:00 to denote end of day. */
     if (h == 24 && m == 0 && s == 0)
@@ -397,22 +399,26 @@ static bool is_valid_time (int h, int m, int s)
     return h >= 0 && h <= 23 && m >= 0 && m <= 59 && s >= 0 && s <= 59;
 }
 
-static bool is_valid_mday (int mday)
+static bool
+is_valid_mday (int mday)
 {
     return mday >= 1 && mday <= 31;
 }
 
-static bool is_valid_mon (int mon)
+static bool
+is_valid_mon (int mon)
 {
     return mon >= 1 && mon <= 12;
 }
 
-static bool is_valid_year (int year)
+static bool
+is_valid_year (int year)
 {
     return year >= 1970;
 }
 
-static bool is_valid_date (int year, int mon, int mday)
+static bool
+is_valid_date (int year, int mon, int mday)
 {
     return is_valid_year (year) && is_valid_mon (mon) && is_valid_mday (mday);
 }
@@ -475,10 +481,10 @@ struct keyword;
 typedef int (*setter_t)(struct state *state, struct keyword *kw);
 
 struct keyword {
-    const char *name;  /* keyword */
-    enum field field;  /* field to set, or FIELD_NONE if N/A */
-    int value;         /* value to set, or 0 if N/A */
-    setter_t set;      /* function to use for setting, if non-NULL */
+    const char *name;   /* keyword */
+    enum field field;   /* field to set, or FIELD_NONE if N/A */
+    int value;          /* value to set, or 0 if N/A */
+    setter_t set;       /* function to use for setting, if non-NULL */
 };
 
 /*
@@ -515,7 +521,7 @@ kw_set_month (struct state *state, struct keyword *kw)
 
        consume_postponed_number (state, &v, NULL, NULL);
 
-       if (!is_valid_mday (v))
+       if (! is_valid_mday (v))
            return -PARSE_TIME_ERR_INVALIDDATE;
 
        r = set_field (state, TM_ABS_MDAY, v);
@@ -538,7 +544,7 @@ kw_set_ampm (struct state *state, struct keyword *kw)
 
        consume_postponed_number (state, &v, NULL, NULL);
 
-       if (!is_valid_12hour (v))
+       if (! is_valid_12hour (v))
            return -PARSE_TIME_ERR_INVALIDTIME;
 
        r = set_abs_time (state, v, 0, 0);
@@ -577,7 +583,7 @@ kw_set_ordinal (struct state *state, struct keyword *kw)
     int n, v;
 
     /* Require a postponed number. */
-    if (!consume_postponed_number (state, &v, &n, NULL))
+    if (! consume_postponed_number (state, &v, &n, NULL))
        return -PARSE_TIME_ERR_DATEFORMAT;
 
     /* Ordinals are mday. */
@@ -591,7 +597,7 @@ kw_set_ordinal (struct state *state, struct keyword *kw)
        return -PARSE_TIME_ERR_INVALIDDATE;
     else if (strcasecmp (kw->name, "rd") == 0 && v != 3 && v != 23)
        return -PARSE_TIME_ERR_INVALIDDATE;
-    else if (strcasecmp (kw->name, "th") == 0 && !is_valid_mday (v))
+    else if (strcasecmp (kw->name, "th") == 0 && ! is_valid_mday (v))
        return -PARSE_TIME_ERR_INVALIDDATE;
 
     return set_field (state, TM_ABS_MDAY, v);
@@ -622,95 +628,95 @@ kw_ignore (unused (struct state *state), unused (struct keyword *kw))
  */
 static struct keyword keywords[] = {
     /* Weekdays. */
-    { N_("sun|day"),   TM_WDAY,        0,      NULL },
-    { N_("mon|day"),   TM_WDAY,        1,      NULL },
-    { N_("tue|sday"),  TM_WDAY,        2,      NULL },
-    { N_("wed|nesday"),        TM_WDAY,        3,      NULL },
-    { N_("thu|rsday"), TM_WDAY,        4,      NULL },
-    { N_("fri|day"),   TM_WDAY,        5,      NULL },
-    { N_("sat|urday"), TM_WDAY,        6,      NULL },
+    { N_ ("sun|day"),    TM_WDAY,        0,      NULL },
+    { N_ ("mon|day"),    TM_WDAY,        1,      NULL },
+    { N_ ("tue|sday"),   TM_WDAY,        2,      NULL },
+    { N_ ("wed|nesday"), TM_WDAY,        3,      NULL },
+    { N_ ("thu|rsday"),  TM_WDAY,        4,      NULL },
+    { N_ ("fri|day"),    TM_WDAY,        5,      NULL },
+    { N_ ("sat|urday"),  TM_WDAY,        6,      NULL },
 
     /* Months. */
-    { N_("jan|uary"),  TM_ABS_MON,     1,      kw_set_month },
-    { N_("feb|ruary"), TM_ABS_MON,     2,      kw_set_month },
-    { N_("mar|ch"),    TM_ABS_MON,     3,      kw_set_month },
-    { N_("apr|il"),    TM_ABS_MON,     4,      kw_set_month },
-    { N_("may"),       TM_ABS_MON,     5,      kw_set_month },
-    { N_("jun|e"),     TM_ABS_MON,     6,      kw_set_month },
-    { N_("jul|y"),     TM_ABS_MON,     7,      kw_set_month },
-    { N_("aug|ust"),   TM_ABS_MON,     8,      kw_set_month },
-    { N_("sep|tember"),        TM_ABS_MON,     9,      kw_set_month },
-    { N_("oct|ober"),  TM_ABS_MON,     10,     kw_set_month },
-    { N_("nov|ember"), TM_ABS_MON,     11,     kw_set_month },
-    { N_("dec|ember"), TM_ABS_MON,     12,     kw_set_month },
+    { N_ ("jan|uary"),   TM_ABS_MON,     1,      kw_set_month },
+    { N_ ("feb|ruary"),  TM_ABS_MON,     2,      kw_set_month },
+    { N_ ("mar|ch"),     TM_ABS_MON,     3,      kw_set_month },
+    { N_ ("apr|il"),     TM_ABS_MON,     4,      kw_set_month },
+    { N_ ("may"),        TM_ABS_MON,     5,      kw_set_month },
+    { N_ ("jun|e"),      TM_ABS_MON,     6,      kw_set_month },
+    { N_ ("jul|y"),      TM_ABS_MON,     7,      kw_set_month },
+    { N_ ("aug|ust"),    TM_ABS_MON,     8,      kw_set_month },
+    { N_ ("sep|tember"), TM_ABS_MON,     9,      kw_set_month },
+    { N_ ("oct|ober"),   TM_ABS_MON,     10,     kw_set_month },
+    { N_ ("nov|ember"),  TM_ABS_MON,     11,     kw_set_month },
+    { N_ ("dec|ember"),  TM_ABS_MON,     12,     kw_set_month },
 
     /* Durations. */
-    { N_("y|ears"),    TM_REL_YEAR,    1,      kw_set_rel },
-    { N_("mo|nths"),   TM_REL_MON,     1,      kw_set_rel },
-    { N_("*M"),                TM_REL_MON,     1,      kw_set_rel },
-    { N_("w|eeks"),    TM_REL_WEEK,    1,      kw_set_rel },
-    { N_("d|ays"),     TM_REL_DAY,     1,      kw_set_rel },
-    { N_("h|ours"),    TM_REL_HOUR,    1,      kw_set_rel },
-    { N_("hr|s"),      TM_REL_HOUR,    1,      kw_set_rel },
-    { N_("mi|nutes"),  TM_REL_MIN,     1,      kw_set_rel },
-    { N_("mins"),      TM_REL_MIN,     1,      kw_set_rel },
-    { N_("*m"),                TM_REL_MIN,     1,      kw_set_rel },
-    { N_("s|econds"),  TM_REL_SEC,     1,      kw_set_rel },
-    { N_("secs"),      TM_REL_SEC,     1,      kw_set_rel },
+    { N_ ("y|ears"),     TM_REL_YEAR,    1,      kw_set_rel },
+    { N_ ("mo|nths"),    TM_REL_MON,     1,      kw_set_rel },
+    { N_ ("*M"),         TM_REL_MON,     1,      kw_set_rel },
+    { N_ ("w|eeks"),     TM_REL_WEEK,    1,      kw_set_rel },
+    { N_ ("d|ays"),      TM_REL_DAY,     1,      kw_set_rel },
+    { N_ ("h|ours"),     TM_REL_HOUR,    1,      kw_set_rel },
+    { N_ ("hr|s"),       TM_REL_HOUR,    1,      kw_set_rel },
+    { N_ ("mi|nutes"),   TM_REL_MIN,     1,      kw_set_rel },
+    { N_ ("mins"),       TM_REL_MIN,     1,      kw_set_rel },
+    { N_ ("*m"),         TM_REL_MIN,     1,      kw_set_rel },
+    { N_ ("s|econds"),   TM_REL_SEC,     1,      kw_set_rel },
+    { N_ ("secs"),       TM_REL_SEC,     1,      kw_set_rel },
 
     /* Numbers. */
-    { N_("one"),       TM_NONE,        1,      kw_set_number },
-    { N_("two"),       TM_NONE,        2,      kw_set_number },
-    { N_("three"),     TM_NONE,        3,      kw_set_number },
-    { N_("four"),      TM_NONE,        4,      kw_set_number },
-    { N_("five"),      TM_NONE,        5,      kw_set_number },
-    { N_("six"),       TM_NONE,        6,      kw_set_number },
-    { N_("seven"),     TM_NONE,        7,      kw_set_number },
-    { N_("eight"),     TM_NONE,        8,      kw_set_number },
-    { N_("nine"),      TM_NONE,        9,      kw_set_number },
-    { N_("ten"),       TM_NONE,        10,     kw_set_number },
-    { N_("dozen"),     TM_NONE,        12,     kw_set_number },
-    { N_("hundred"),   TM_NONE,        100,    kw_set_number },
+    { N_ ("one"),        TM_NONE,        1,      kw_set_number },
+    { N_ ("two"),        TM_NONE,        2,      kw_set_number },
+    { N_ ("three"),      TM_NONE,        3,      kw_set_number },
+    { N_ ("four"),       TM_NONE,        4,      kw_set_number },
+    { N_ ("five"),       TM_NONE,        5,      kw_set_number },
+    { N_ ("six"),        TM_NONE,        6,      kw_set_number },
+    { N_ ("seven"),      TM_NONE,        7,      kw_set_number },
+    { N_ ("eight"),      TM_NONE,        8,      kw_set_number },
+    { N_ ("nine"),       TM_NONE,        9,      kw_set_number },
+    { N_ ("ten"),        TM_NONE,        10,     kw_set_number },
+    { N_ ("dozen"),      TM_NONE,        12,     kw_set_number },
+    { N_ ("hundred"),    TM_NONE,        100,    kw_set_number },
 
     /* Special number forms. */
-    { N_("this"),      TM_NONE,        0,      kw_set_number },
-    { N_("last"),      TM_NONE,        1,      kw_set_number },
+    { N_ ("this"),       TM_NONE,        0,      kw_set_number },
+    { N_ ("last"),       TM_NONE,        1,      kw_set_number },
 
     /* Other special keywords. */
-    { N_("yesterday"), TM_REL_DAY,     1,      kw_set_rel },
-    { N_("today"),     TM_NONE,        0,      kw_set_today },
-    { N_("now"),       TM_NONE,        0,      kw_set_now },
-    { N_("noon"),      TM_NONE,        12,     kw_set_timeofday },
-    { N_("midnight"),  TM_NONE,        0,      kw_set_timeofday },
-    { N_("am"),                TM_AMPM,        0,      kw_set_ampm },
-    { N_("a.m."),      TM_AMPM,        0,      kw_set_ampm },
-    { N_("pm"),                TM_AMPM,        1,      kw_set_ampm },
-    { N_("p.m."),      TM_AMPM,        1,      kw_set_ampm },
-    { N_("st"),                TM_NONE,        0,      kw_set_ordinal },
-    { N_("nd"),                TM_NONE,        0,      kw_set_ordinal },
-    { N_("rd"),                TM_NONE,        0,      kw_set_ordinal },
-    { N_("th"),                TM_NONE,        0,      kw_set_ordinal },
-    { N_("ago"),               TM_NONE,        0,      kw_ignore },
+    { N_ ("yesterday"),  TM_REL_DAY,     1,      kw_set_rel },
+    { N_ ("today"),      TM_NONE,        0,      kw_set_today },
+    { N_ ("now"),        TM_NONE,        0,      kw_set_now },
+    { N_ ("noon"),       TM_NONE,        12,     kw_set_timeofday },
+    { N_ ("midnight"),   TM_NONE,        0,      kw_set_timeofday },
+    { N_ ("am"),         TM_AMPM,        0,      kw_set_ampm },
+    { N_ ("a.m."),       TM_AMPM,        0,      kw_set_ampm },
+    { N_ ("pm"),         TM_AMPM,        1,      kw_set_ampm },
+    { N_ ("p.m."),       TM_AMPM,        1,      kw_set_ampm },
+    { N_ ("st"),         TM_NONE,        0,      kw_set_ordinal },
+    { N_ ("nd"),         TM_NONE,        0,      kw_set_ordinal },
+    { N_ ("rd"),         TM_NONE,        0,      kw_set_ordinal },
+    { N_ ("th"),         TM_NONE,        0,      kw_set_ordinal },
+    { N_ ("ago"),        TM_NONE,        0,      kw_ignore },
 
     /* Timezone codes: offset in minutes. XXX: Add more codes. */
-    { N_("pst"),       TM_TZ,          -8*60,  NULL },
-    { N_("mst"),       TM_TZ,          -7*60,  NULL },
-    { N_("cst"),       TM_TZ,          -6*60,  NULL },
-    { N_("est"),       TM_TZ,          -5*60,  NULL },
-    { N_("ast"),       TM_TZ,          -4*60,  NULL },
-    { N_("nst"),       TM_TZ,          -(3*60+30),     NULL },
-
-    { N_("gmt"),       TM_TZ,          0,      NULL },
-    { N_("utc"),       TM_TZ,          0,      NULL },
-
-    { N_("wet"),       TM_TZ,          0,      NULL },
-    { N_("cet"),       TM_TZ,          1*60,   NULL },
-    { N_("eet"),       TM_TZ,          2*60,   NULL },
-    { N_("fet"),       TM_TZ,          3*60,   NULL },
-
-    { N_("wat"),       TM_TZ,          1*60,   NULL },
-    { N_("cat"),       TM_TZ,          2*60,   NULL },
-    { N_("eat"),       TM_TZ,          3*60,   NULL },
+    { N_ ("pst"),        TM_TZ,          -8 * 60,  NULL },
+    { N_ ("mst"),        TM_TZ,          -7 * 60,  NULL },
+    { N_ ("cst"),        TM_TZ,          -6 * 60,  NULL },
+    { N_ ("est"),        TM_TZ,          -5 * 60,  NULL },
+    { N_ ("ast"),        TM_TZ,          -4 * 60,  NULL },
+    { N_ ("nst"),        TM_TZ,          -(3 * 60 + 30),     NULL },
+
+    { N_ ("gmt"),        TM_TZ,          0,      NULL },
+    { N_ ("utc"),        TM_TZ,          0,      NULL },
+
+    { N_ ("wet"),        TM_TZ,          0,      NULL },
+    { N_ ("cet"),        TM_TZ,          1 * 60,   NULL },
+    { N_ ("eet"),        TM_TZ,          2 * 60,   NULL },
+    { N_ ("fet"),        TM_TZ,          3 * 60,   NULL },
+
+    { N_ ("wat"),        TM_TZ,          1 * 60,   NULL },
+    { N_ ("cat"),        TM_TZ,          2 * 60,   NULL },
+    { N_ ("eat"),        TM_TZ,          3 * 60,   NULL },
 };
 
 /*
@@ -745,7 +751,7 @@ match_keyword (const char *str, const char *keyword, bool match_case)
            keyword++;
        }
 
-       if (!*s || !isalpha ((unsigned char) *s) || !*keyword)
+       if (! *s || ! isalpha ((unsigned char) *s) || ! *keyword)
            break;
 
        if (match_case) {
@@ -765,7 +771,7 @@ match_keyword (const char *str, const char *keyword, bool match_case)
        return 0;
 
     /* did not match enough of keyword */
-    if (*keyword && !prefix_matched)
+    if (*keyword && ! prefix_matched)
        return 0;
 
     return s - str;
@@ -784,7 +790,7 @@ parse_keyword (struct state *state, const char *s)
     int r;
 
     for (i = 0; i < ARRAY_SIZE (keywords); i++) {
-       const char *keyword = _(keywords[i].name);
+       const char *keyword = _ (keywords[i].name);
        bool mcase = false;
 
        /* Match case if keyword begins with '*'. */
@@ -800,7 +806,7 @@ parse_keyword (struct state *state, const char *s)
        }
     }
 
-    if (!kw)
+    if (! kw)
        return -PARSE_TIME_ERR_KEYWORD;
 
     if (kw->set)
@@ -846,7 +852,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field))
     char d;
 
     /* Bail out if there's no postponed number. */
-    if (!consume_postponed_number (state, &v, &n, &d))
+    if (! consume_postponed_number (state, &v, &n, &d))
        return 0;
 
     if (n == 1 || n == 2) {
@@ -854,7 +860,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field))
         * handles "January 20". */
        if (state->last_field == TM_ABS_MON) {
            /* D[D] */
-           if (!is_valid_mday (v))
+           if (! is_valid_mday (v))
                return -PARSE_TIME_ERR_INVALIDDATE;
 
            return set_field (state, TM_ABS_MDAY, v);
@@ -869,7 +875,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field))
        /* Notable exception: Value affects parsing. Time zones are
         * always at most 1400 and we don't understand years before
         * 1970. */
-       if (!is_valid_year (v)) {
+       if (! is_valid_year (v)) {
            if (d == '+' || d == '-') {
                /* +/-HHMM */
                return set_user_tz (state, d, v / 100, v % 100);
@@ -884,7 +890,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field))
        int min = (v / 100) % 100;
        int sec = v % 100;
 
-       if (!is_valid_time (hour, min, sec))
+       if (! is_valid_time (hour, min, sec))
            return -PARSE_TIME_ERR_INVALIDTIME;
 
        return set_abs_time (state, hour, min, sec);
@@ -894,7 +900,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field))
        int mon = (v / 100) % 100;
        int mday = v % 100;
 
-       if (!is_valid_date (year, mon, mday))
+       if (! is_valid_date (year, mon, mday))
            return -PARSE_TIME_ERR_INVALIDDATE;
 
        return set_abs_date (state, year, mon, mday);
@@ -1039,13 +1045,13 @@ parse_date (struct state *state, char sep,
        break;
     }
 
-    if (year != UNSET && !is_valid_year (year))
+    if (year != UNSET && ! is_valid_year (year))
        return -PARSE_TIME_ERR_INVALIDDATE;
 
-    if (mon != UNSET && !is_valid_mon (mon))
+    if (mon != UNSET && ! is_valid_mon (mon))
        return -PARSE_TIME_ERR_INVALIDDATE;
 
-    if (mday != UNSET && !is_valid_mday (mday))
+    if (mday != UNSET && ! is_valid_mday (mday))
        return -PARSE_TIME_ERR_INVALIDDATE;
 
     return set_abs_date (state, year, mon, mday);
@@ -1081,7 +1087,7 @@ parse_time (struct state *state, char sep,
        return set_user_tz (state, state->delim, v1, v2);
     }
 
-    if (!is_valid_time (v1, v2, n3 ? v3 : 0))
+    if (! is_valid_time (v1, v2, n3 ? v3 : 0))
        return -PARSE_TIME_ERR_INVALIDTIME;
 
     return set_abs_time (state, v1, v2, n3 ? (int) v3 : UNSET);
@@ -1112,7 +1118,7 @@ parse_number (struct state *state, const char *s)
 
     v1 = strtoul_len (p, &p, &n1);
 
-    if (!is_sep (*p) || !isdigit ((unsigned char) *(p + 1))) {
+    if (! is_sep (*p) || ! isdigit ((unsigned char) *(p + 1))) {
        /* A single number. */
        r = parse_single_number (state, v1, n1);
        if (r)
@@ -1155,7 +1161,7 @@ parse_delim (struct state *state, const char *s)
      * Skip non-alpha and non-digit, and store the last for further
      * processing.
      */
-    while (*p && !isalnum ((unsigned char) *p)) {
+    while (*p && ! isalnum ((unsigned char) *p)) {
        set_delim (state, *p);
        p++;
     }
@@ -1258,7 +1264,7 @@ normalize_tm (struct tm *tm)
     if (t == (time_t) -1)
        return -PARSE_TIME_ERR_LIB;
 
-    if (!localtime_r (&t, tm))
+    if (! localtime_r (&t, tm))
        return -PARSE_TIME_ERR_LIB;
 
     return 0;
@@ -1269,14 +1275,14 @@ static int
 tm_get_field (const struct tm *tm, enum field field)
 {
     switch (field) {
-    case TM_ABS_SEC:   return tm->tm_sec;
-    case TM_ABS_MIN:   return tm->tm_min;
-    case TM_ABS_HOUR:  return tm->tm_hour;
-    case TM_ABS_MDAY:  return tm->tm_mday;
-    case TM_ABS_MON:   return tm->tm_mon + 1; /* 0- to 1-based */
-    case TM_ABS_YEAR:  return 1900 + tm->tm_year;
-    case TM_WDAY:      return tm->tm_wday;
-    case TM_ABS_ISDST: return tm->tm_isdst;
+    case TM_ABS_SEC:    return tm->tm_sec;
+    case TM_ABS_MIN:    return tm->tm_min;
+    case TM_ABS_HOUR:   return tm->tm_hour;
+    case TM_ABS_MDAY:   return tm->tm_mday;
+    case TM_ABS_MON:    return tm->tm_mon + 1; /* 0- to 1-based */
+    case TM_ABS_YEAR:   return 1900 + tm->tm_year;
+    case TM_WDAY:       return tm->tm_wday;
+    case TM_ABS_ISDST:  return tm->tm_isdst;
     default:
        assert (false);
        break;
@@ -1291,14 +1297,14 @@ fixup_ampm (struct state *state)
 {
     int hour, hdiff = 0;
 
-    if (!is_field_set (state, TM_AMPM))
+    if (! is_field_set (state, TM_AMPM))
        return 0;
 
-    if (!is_field_set (state, TM_ABS_HOUR))
+    if (! is_field_set (state, TM_ABS_HOUR))
        return -PARSE_TIME_ERR_TIMEFORMAT;
 
     hour = get_field (state, TM_ABS_HOUR);
-    if (!is_valid_12hour (hour))
+    if (! is_valid_12hour (hour))
        return -PARSE_TIME_ERR_INVALIDTIME;
 
     if (get_field (state, TM_AMPM)) {
@@ -1348,7 +1354,7 @@ create_output (struct state *state, time_t *t_out, const time_t *ref,
      * date?
      */
     if (is_field_set (state, TM_WDAY) &&
-       !is_field_set (state, TM_ABS_MDAY)) {
+       ! is_field_set (state, TM_ABS_MDAY)) {
        int wday = get_field (state, TM_WDAY);
        int today = tm_get_field (&now, TM_WDAY);
        int rel_days;
@@ -1405,7 +1411,7 @@ create_output (struct state *state, time_t *t_out, const time_t *ref,
            }
        }
 
-       if (!is_field_set (state, f))
+       if (! is_field_set (state, f))
            set_field (state, f, tm_get_field (&now, f));
     }
 
@@ -1489,7 +1495,7 @@ parse_time_string (const char *s, time_t *t, const time_t *ref, int round)
     struct state state = { .last_field = TM_NONE };
     int r;
 
-    if (!s || !t)
+    if (! s || ! t)
        return EXTERNAL_ERR (-PARSE_TIME_ERR);
 
     r = parse_input (&state, s);
index c394ecde1f5bc22538dd8bc8e9c2ae7d91e878ed..c90d694716cda99086f819c584449e6eeae2a148 100644 (file)
@@ -30,23 +30,23 @@ extern "C" {
 /* return values for parse_time_string() */
 enum {
     PARSE_TIME_OK = 0,
-    PARSE_TIME_ERR,            /* unspecified error */
-    PARSE_TIME_ERR_LIB,                /* library call failed */
-    PARSE_TIME_ERR_ALREADYSET, /* attempt to set unit twice */
-    PARSE_TIME_ERR_FORMAT,     /* generic date/time format error */
-    PARSE_TIME_ERR_DATEFORMAT, /* date format error */
-    PARSE_TIME_ERR_TIMEFORMAT, /* time format error */
-    PARSE_TIME_ERR_INVALIDDATE,        /* date value error */
-    PARSE_TIME_ERR_INVALIDTIME,        /* time value error */
-    PARSE_TIME_ERR_KEYWORD,    /* unknown keyword */
+    PARSE_TIME_ERR,             /* unspecified error */
+    PARSE_TIME_ERR_LIB,         /* library call failed */
+    PARSE_TIME_ERR_ALREADYSET,  /* attempt to set unit twice */
+    PARSE_TIME_ERR_FORMAT,      /* generic date/time format error */
+    PARSE_TIME_ERR_DATEFORMAT,  /* date format error */
+    PARSE_TIME_ERR_TIMEFORMAT,  /* time format error */
+    PARSE_TIME_ERR_INVALIDDATE, /* date value error */
+    PARSE_TIME_ERR_INVALIDTIME, /* time value error */
+    PARSE_TIME_ERR_KEYWORD,     /* unknown keyword */
 };
 
 /* round values for parse_time_string() */
 enum {
-    PARSE_TIME_ROUND_DOWN = -1,
-    PARSE_TIME_NO_ROUND = 0,
-    PARSE_TIME_ROUND_UP = 1,
-    PARSE_TIME_ROUND_UP_INCLUSIVE = 2,
+    PARSE_TIME_ROUND_DOWN              = -1,
+    PARSE_TIME_NO_ROUND                        = 0,
+    PARSE_TIME_ROUND_UP                        = 1,
+    PARSE_TIME_ROUND_UP_INCLUSIVE      = 2,
 };
 
 /**
index aab36e6953e2917b40a959a5bb4177d0c1cfd772..5858ab337fd8be17da7a64617fb3327971f45a0f 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='notmuch new'
 
index 32ab8dc9c0b99fac7e5375b17f8d184daea7187b..7850b4117d50740b322e09b2d8e82131fcc93d8c 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='dump and restore'
 
index 2e218fd3c4b6fd0c5ea9163a82084de627752918..40c5d4d756000d46fd12b2b5a16e1b2563f38a87 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='show'
 
index 343f5c7cfc24bba490ee67aa410be76937165763..a73a36abf1c75e163aac3c61e4c38c80695bb20b 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='search'
 
index 3c1205dbc41206f67c086de8640c3ef615d23f0f..3b0f9e7848a36da230b4bcfef062c47d24929a8e 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='search'
 
index 17e2c824698ab5852e11d57bd598222c686753f9..8ea7e7ee89a64c808d528ccde664da82d1f9a6c7 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='reindex'
 
index 5ae0656a505b1c909644ab6251988578db6d1bd8..12330c76981589ec46916925a3267c17be8047e3 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='search'
 
index 9dc260e3dc51f752105470ff6bb64d40b9aa6cd9..b9f580c7b6bf40e10ae9d1fe0d16ba5aae32fdec 100644 (file)
@@ -1,4 +1,4 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := performance-test
 
index 25391136c041c8417912e4dab82b3cd541b6521c..a14dd13f1477a7cab465e1ca6c3fefe88b53c690 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='notmuch new'
 
index 12f12e660533ecd288cbdd61e6dbfddb34b6f916..2a53e3b82f3bf4c113e570b9314e8a5c60055c80 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='dump and restore'
 
index 8c5dfd68031656503d2ba40b49271f9570b87eaa..9c895d6a910145266dde528039a61caabcc8fa70 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='tagging'
 
index 8e0a77f42c5d2da47b14fa1eb8c40c2ab2ac35e6..8db52a33a9e2d8c78ca3720bad83ebfceea07079 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='reindexing'
 
index 665d5a64d93fc0e27317d591c06f40b87dfb3e00..ba81f3836e371e1c6ae210c7b2db53e25885f80a 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 test_description='thread subqueries'
 
index c6ec857720d9e6acd0c0c4b9410afaa391249606..273bdeca3d736dcb16f90acdf4ee6377efb281d0 100644 (file)
@@ -1,3 +1,4 @@
+#include <inttypes.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <talloc.h>
@@ -124,11 +125,11 @@ json_string (struct sprinter *sp, const char *val)
 }
 
 static void
-json_integer (struct sprinter *sp, int val)
+json_integer (struct sprinter *sp, int64_t val)
 {
     struct sprinter_json *spj = json_begin_value (sp);
 
-    fprintf (spj->stream, "%d", val);
+    fprintf (spj->stream, "%"PRId64, val);
 }
 
 static void
index 6891ea4254f82e89b9758f0fb6921901f00377b3..35c007d5a25f55d7bcebae9c588b5a6d35d1a615 100644 (file)
@@ -18,6 +18,7 @@
  * Author: Peter Feigl <peter.feigl@gmx.at>
  */
 
+#include <inttypes.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <talloc.h>
@@ -161,11 +162,11 @@ sexp_keyword (struct sprinter *sp, const char *val)
 }
 
 static void
-sexp_integer (struct sprinter *sp, int val)
+sexp_integer (struct sprinter *sp, int64_t val)
 {
     struct sprinter_sexp *sps = sexp_begin_value (sp);
 
-    fprintf (sps->stream, "%d", val);
+    fprintf (sps->stream, "%"PRId64, val);
 }
 
 static void
index 648b54b1e886553cd839ca24f4f5f0a9378aa47b..7b68f98ccbb17e7a494cbc925e954af6347e3b91 100644 (file)
@@ -1,3 +1,4 @@
+#include <inttypes.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <talloc.h>
@@ -44,11 +45,11 @@ text_string (struct sprinter *sp, const char *val)
 }
 
 static void
-text_integer (struct sprinter *sp, int val)
+text_integer (struct sprinter *sp, int64_t val)
 {
     struct sprinter_text *sptxt = (struct sprinter_text *) sp;
 
-    fprintf (sptxt->stream, "%d", val);
+    fprintf (sptxt->stream, "%"PRId64, val);
 }
 
 static void
index 9d2e9b6f7140ad4f2aa2eae6d9981cb858d56019..528d8a2db332bdaea6801424a15f7ca42db673ec 100644 (file)
@@ -13,15 +13,15 @@ typedef struct sprinter {
      * a sequence of alternating calls to map_key and one of the
      * value-printing functions until the map is ended by end.
      */
-    void (*begin_map) (struct sprinter *);
+    void (*begin_map)(struct sprinter *);
 
     /* Start a new list/array structure.
      */
-    void (*begin_list) (struct sprinter *);
+    void (*begin_list)(struct sprinter *);
 
     /* End the last opened list or map structure.
      */
-    void (*end) (struct sprinter *);
+    void (*end)(struct sprinter *);
 
     /* Print one string/integer/boolean/null element (possibly inside
      * a list or map, followed or preceded by separators).  For string
@@ -31,16 +31,16 @@ typedef struct sprinter {
      * string (but not string_len) the string pointer passed may be
      * NULL.
      */
-    void (*string) (struct sprinter *, const char *);
-    void (*string_len) (struct sprinter *, const char *, size_t);
-    void (*integer) (struct sprinter *, int);
-    void (*boolean) (struct sprinter *, bool);
-    void (*null) (struct sprinter *);
+    void (*string)(struct sprinter *, const char *);
+    void (*string_len)(struct sprinter *, const char *, size_t);
+    void (*integer)(struct sprinter *, int64_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
      * encoded.
      */
-    void (*map_key) (struct sprinter *, const char *);
+    void (*map_key)(struct sprinter *, const char *);
 
     /* Insert a separator (usually extra whitespace). For the text
      * printers, this is a syntactic separator. For the structured
@@ -48,13 +48,13 @@ typedef struct sprinter {
      * the abstract syntax of the structure being printed. For JSON,
      * this could simply be a line break.
      */
-    void (*separator) (struct sprinter *);
+    void (*separator)(struct sprinter *);
 
     /* Set the current string prefix. This only affects the text
      * printer, which will print this string, followed by a colon,
      * before any string. For other printers, this does nothing.
      */
-    void (*set_prefix) (struct sprinter *, const char *);
+    void (*set_prefix)(struct sprinter *, const char *);
 
     /* True if this is the special-cased plain text printer.
      */
index 01fd8c99b8069030ffde0c026a095cbd701cbdfb..09d82a172ba8315aa5184db0aa6bedfa6651b180 100644 (file)
--- a/status.c
+++ b/status.c
@@ -42,8 +42,8 @@ print_status_message (const char *loc,
 
 notmuch_status_t
 print_status_database (const char *loc,
-                   const notmuch_database_t *notmuch,
-                   notmuch_status_t status)
+                      const notmuch_database_t *notmuch,
+                      notmuch_status_t status)
 {
     if (status) {
        const char *msg;
@@ -72,3 +72,17 @@ status_to_exit (notmuch_status_t status)
        return EXIT_FAILURE;
     }
 }
+
+notmuch_status_t
+print_status_gzbytes (const char *loc, gzFile file, int bytes)
+{
+    if (bytes <= 0) {
+       int errnum;
+       const char *errstr = gzerror (file, &errnum);
+       fprintf (stderr, "%s: zlib error %s (%d)\n", loc, errstr, errnum);
+       return NOTMUCH_STATUS_FILE_ERROR;
+    } else {
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+}
+
index 1837b1aeafa347cfe6840ec70add6426d3a8f3f1..accf299e0fedbe25e317c47be7aa6e09548a5a1b 100644 (file)
@@ -323,7 +323,7 @@ tag_op_list_apply (notmuch_message_t *message,
     if (flags & TAG_FLAG_MAILDIR_SYNC) {
        status = notmuch_message_tags_to_maildir_flags (message);
        if (status) {
-           message_error (message, status, "synching tags to maildir");
+           message_error (message, status, "syncing tags to maildir");
            return status;
        }
     }
index ba0d98c858d52ffd9583a8af0c290bc7dc6e0daa..411e8cae2a6daff907ddfd0405316ad1e54480c2 100644 (file)
@@ -8,25 +8,25 @@ typedef struct _tag_op_list_t tag_op_list_t;
 
 /* Use powers of 2 */
 typedef enum {
-    TAG_FLAG_NONE = 0,
+    TAG_FLAG_NONE              = 0,
 
     /* Operations are synced to maildir, if possible.
      */
-    TAG_FLAG_MAILDIR_SYNC = (1 << 0),
+    TAG_FLAG_MAILDIR_SYNC      = (1 << 0),
 
     /* Remove all tags from message before applying list.
      */
-    TAG_FLAG_REMOVE_ALL = (1 << 1),
+    TAG_FLAG_REMOVE_ALL                = (1 << 1),
 
     /* Don't try to avoid database operations. Useful when we
      * know that message passed needs these operations.
      */
-    TAG_FLAG_PRE_OPTIMIZED = (1 << 2),
+    TAG_FLAG_PRE_OPTIMIZED     = (1 << 2),
 
     /* Accept strange tags that might be user error;
      * intended for use by notmuch-restore.
      */
-    TAG_FLAG_BE_GENEROUS = (1 << 3)
+    TAG_FLAG_BE_GENEROUS       = (1 << 3)
 
 } tag_op_flag_t;
 
@@ -34,16 +34,16 @@ typedef enum {
  * skipped lines are positive.
  */
 typedef enum {
-    TAG_PARSE_OUT_OF_MEMORY = -1,
+    TAG_PARSE_OUT_OF_MEMORY    = -1,
 
     /* Line parsed successfully. */
-    TAG_PARSE_SUCCESS = 0,
+    TAG_PARSE_SUCCESS          = 0,
 
     /* Line has a syntax error */
-    TAG_PARSE_INVALID = 1,
+    TAG_PARSE_INVALID          = 1,
 
     /* Line was blank or a comment */
-    TAG_PARSE_SKIPPED = 2
+    TAG_PARSE_SKIPPED          = 2
 
 } tag_parse_status_t;
 
@@ -123,7 +123,7 @@ tag_op_list_append (tag_op_list_t *list,
 /*
  * Apply a list of tag operations, in order, to a given message.
  *
- * Flags can be bitwise ORed; see enum above for possibilies.
+ * Flags can be bitwise ORed; see enum above for possibilities.
  */
 
 notmuch_status_t
index 47244e8f0956977735b7ea0d4c1666211703ad1a..40574739890c8ce16ba3b2b7a735b4796b22b781 100644 (file)
@@ -1,4 +1,4 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := test
 
index 3f54af58876f324a851565a1e8b16633b61d5b08..11eaf18fa3fbd27c49251cf9832d87286077312b 100644 (file)
@@ -79,14 +79,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.
 
-Certain tests require precomputed databases to complete. You can fetch these
-databases with
-
-       make download-test-databases
-
-If you do not download the test databases, the relevant tests will be
-skipped.
-
 When invoking the test suite via "make test" any of the above options
 can be specified as follows:
 
index 58cd2ba74cefd7f9a1b6fa6808be7fd77c424a1c..02f8738fdfb5ce9b1ebe826f47781916dbab9286 100755 (executable)
@@ -10,18 +10,6 @@ notmuch tag +tag1 \*
 notmuch tag +tag2 subject:Two
 notmuch tag -tag1 +tag3 subject:Three
 
-if [ $NOTMUCH_HAVE_XAPIAN_COMPACT -eq 0 ]; then
-    test_begin_subtest "Compact unsupported: error message"
-    output=$(notmuch compact --quiet 2>&1)
-    test_expect_equal "$output" "notmuch was compiled against a xapian version lacking compaction support.
-Compaction failed: Unsupported operation"
-
-    test_begin_subtest "Compact unsupported: status code"
-    test_expect_code 1 "notmuch compact"
-
-    test_done
-fi
-
 test_begin_subtest "Running compact"
 test_expect_success "notmuch compact --backup=${TEST_DIRECTORY}/xapian.old"
 
index dfc8508f83e20ea93d370ec6a059d12df52e93db..009b26336a73e9c88c415d9e1ea909d68060e367 100755 (executable)
@@ -341,9 +341,9 @@ notmuch config set new.tags $OLDCONFIG
 
 
 test_begin_subtest "Xapian exception: read only files"
-chmod u-w  ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
+chmod u-w  ${MAIL_DIR}/.notmuch/xapian/*.*
 output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
-chmod u+w  ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
+chmod u+w  ${MAIL_DIR}/.notmuch/xapian/*.*
 test_expect_equal "$output" "A Xapian exception occurred opening database"
 
 
index 0c0bf47309e930477f0b69fafeb93130f747fd3c..a1ebf8ba527ea30948c3abd79427a6fa5e83277b 100755 (executable)
@@ -95,7 +95,8 @@ test_expect_equal_file EXPECTED OUTPUT
 
 backup_database
 test_begin_subtest "error message for database open"
-dd if=/dev/zero of="${MAIL_DIR}/.notmuch/xapian/postlist.${db_ending}" count=3
+target=(${MAIL_DIR}/.notmuch/xapian/postlist.*)
+dd if=/dev/zero of="$target" count=3
 notmuch count '*' 2>OUTPUT 1>/dev/null
 output=$(sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' OUTPUT)
 test_expect_equal "${output}" "A Xapian exception occurred opening database"
@@ -107,7 +108,7 @@ set logging file count-files-gdb.log
 set logging on
 break count_files
 commands
-shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.${db_ending}
+shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.*
 continue
 end
 run
index 48165caae5a718c56c846d25834ca9c45b979d46..1c7ca846e9ee8b0bd5b0b5315296ae5de4e4e732 100755 (executable)
@@ -2,8 +2,6 @@
 test_description='"notmuch insert"'
 . $(dirname "$0")/test-lib.sh || exit 1
 
-test_require_external_prereq gdb
-
 # subtests about file permissions assume that we're working with umask
 # 022 by default.
 umask 022
@@ -224,41 +222,41 @@ test_expect_equal "$output" "2"
 
 test_begin_subtest "Insert message, create invalid subfolder"
 gen_insert_msg
-test_expect_code 1 "notmuch insert --folder=../G --create-folder $gen_msg_filename"
+test_expect_code 1 "notmuch insert --folder=../G --create-folder $gen_msg_filename"
 
 OLDCONFIG=$(notmuch config get new.tags)
 
 test_begin_subtest "Empty tags in new.tags are forbidden"
 notmuch config set new.tags "foo;;bar"
 gen_insert_msg
-output=$(notmuch insert $gen_msg_filename 2>&1)
+output=$(notmuch insert $gen_msg_filename 2>&1)
 test_expect_equal "$output" "Error: tag '' in new.tags: empty tag forbidden"
 
 test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
 notmuch config set new.tags "-foo;bar"
 gen_insert_msg
-output=$(notmuch insert $gen_msg_filename 2>&1)
+output=$(notmuch insert $gen_msg_filename 2>&1)
 test_expect_equal "$output" "Error: tag '-foo' in new.tags: tag starting with '-' forbidden"
 
 test_begin_subtest "Invalid tags set exit code"
-test_expect_code 1 "notmuch insert $gen_msg_filename 2>&1"
+test_expect_code 1 "notmuch insert $gen_msg_filename 2>&1"
 
 notmuch config set new.tags $OLDCONFIG
 
 # DUPLICATE_MESSAGE_ID is not tested here, because it should actually pass.
-
-for code in OUT_OF_MEMORY XAPIAN_EXCEPTION FILE_NOT_EMAIL \
-    READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
-cat <<EOF > index-file-$code.gdb
-set breakpoint pending on
-set logging file index-file-$code.log
-set logging on
-break notmuch_database_index_file
-commands
-return NOTMUCH_STATUS_$code
-continue
-end
-run
+# pregenerate all of the test shims
+for code in  FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR OUT_OF_MEMORY XAPIAN_EXCEPTION; do
+    make_shim shim-$code <<EOF
+#include <notmuch.h>
+#include <stdio.h>
+notmuch_status_t
+notmuch_database_index_file (notmuch_database_t *notmuch,
+                             const char *filename,
+                             notmuch_indexopts_t *indexopts,
+                             notmuch_message_t **message_ret)
+{
+  return NOTMUCH_STATUS_$code;
+}
 EOF
 done
 
@@ -266,30 +264,18 @@ gen_insert_msg
 
 for code in  FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
     test_begin_subtest "EXIT_FAILURE when index_file returns $code"
-    test_expect_code 1 \
-         "${TEST_GDB} --batch-silent --return-child-result \
-            -ex 'set args insert < $gen_msg_filename' \
-            -x index-file-$code.gdb notmuch"
+    test_expect_code 1 "notmuch_with_shim shim-$code insert < \"$gen_msg_filename\""
 
     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' \
-            -x index-file-$code.gdb notmuch"
+    test_expect_code 0 "notmuch_with_shim shim-$code insert --keep < \"$gen_msg_filename\""
 done
 
 for code in OUT_OF_MEMORY XAPIAN_EXCEPTION ; do
     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_expect_code 75 "notmuch_with_shim shim-$code insert < \"$gen_msg_filename\""
 
     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' \
-            -x index-file-$code.gdb notmuch"
+    test_expect_code 0 "notmuch_with_shim shim-$code insert --keep < \"$gen_msg_filename\""
 done
 
 test_done
index 208b4b9806405e5c4e90b8d7f1af56afe6a4e412..2f0b3531412db146e500eff1ea43235ab53284d2 100755 (executable)
@@ -305,9 +305,9 @@ test_begin_subtest "Tag name beginning with -"
 test_expect_code 1 'notmuch tag +- One'
 
 test_begin_subtest "Xapian exception: read only files"
-chmod u-w  ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
+chmod u-w  ${MAIL_DIR}/.notmuch/xapian/*.*
 output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
-chmod u+w  ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
+chmod u+w  ${MAIL_DIR}/.notmuch/xapian/*.*
 test_expect_equal "$output" "A Xapian exception occurred opening database"
 
 test_done
index 004adb4ee4dfe53695a34de8e3dabad8770cffed..e8b75605fd2a391eaa0c51351d8f8bd6e0d80499 100755 (executable)
@@ -64,6 +64,23 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
  \"tags\": [\"inbox\",
  \"unread\"]}]"
 
+test_begin_subtest "Search message: json, 64-bit timestamp"
+if [ $NOTMUCH_HAVE_64BIT_TIME_T -ne 1 ]; then
+    test_subtest_known_broken
+fi
+add_message "[subject]=\"json-search-64bit-timestamp-subject\"" "[date]=\"Tue, 01 Jan 2999 12:00:00 -0000\"" "[body]=\"json-search-64bit-timestamp-message\""
+output=$(notmuch search --format=json "json-search-64bit-timestamp-message" | notmuch_search_sanitize)
+test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
+ \"timestamp\": 32472187200,
+ \"date_relative\": \"the future\",
+ \"matched\": 1,
+ \"total\": 1,
+ \"authors\": \"Notmuch Test Suite\",
+ \"subject\": \"json-search-64bit-timestamp-subject\",
+ \"query\": [\"id:$gen_msg_id\", null],
+ \"tags\": [\"inbox\",
+ \"unread\"]}]"
+
 test_begin_subtest "Format version: too low"
 test_expect_code 20 "notmuch search --format-version=0 \\*"
 
index 85e707d4741c8eb441535ac6a9a1fd7b9496764a..9a8b990c1c3fb4d923b5414a65198986a5412759 100755 (executable)
@@ -40,7 +40,7 @@ for pow in range(10,21):
     msg['To'] = msg['From']
     msg['Message-Id'] = 'size-{:07d}@notmuch-test-suite'.format(size)
     content = ""
-    msg.set_content("")
+    msg.set_content("\n")
     padding = size - len(bytes(msg))
     lines = []
     while padding > 0:
index 5f74305dd3ef912f8fd49dfb7b87086eee0acd26..78ac19a8655c150bd428657fcd387336b7b689d4 100755 (executable)
@@ -40,12 +40,14 @@ test_emacs '(notmuch-search "tag:inbox")
 test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox OUTPUT
 
 test_begin_subtest "Incremental parsing of search results"
-test_emacs "(ad-enable-advice 'notmuch-search-process-filter 'around 'pessimal)
-           (ad-activate 'notmuch-search-process-filter)
-           (notmuch-search \"tag:inbox\")
-           (notmuch-test-wait)
-           (ad-disable-advice 'notmuch-search-process-filter 'around 'pessimal)
-           (ad-activate 'notmuch-search-process-filter)
+test_emacs "(cl-letf* (((symbol-function 'orig)
+                       (symbol-function 'notmuch-search-process-filter))
+                      ((symbol-function 'notmuch-search-process-filter)
+                       (lambda (proc string)
+                         (cl-loop for char across string
+                                  do (orig proc (char-to-string char))))))
+             (notmuch-search \"tag:inbox\")
+             (notmuch-test-wait))
            (test-output)"
 test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox OUTPUT
 
diff --git a/test/T351-pgpmime-mangling.sh b/test/T351-pgpmime-mangling.sh
new file mode 100755 (executable)
index 0000000..71a68c0
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+test_description='PGP/MIME message mangling'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_gnupg_home
+add_email_corpus mangling
+
+bodytext='["body"][0]["content"][1]["content"]="The password is \"abcd1234!\", please do not tell anyone.\n"'
+
+test_begin_subtest "show 'Mixed-Up' mangled PGP/MIME message correctly"
+output=$(notmuch show --format=json --decrypt=true id:mixed-up@mangling.notmuchmail.org)
+test_json_nodes <<<"$output" \
+                'body:[0][0][0]'"$bodytext"
+
+test_begin_subtest "reply to 'Mixed-Up' mangled PGP/MIME message correctly"
+output=$(notmuch reply --format=json --decrypt=true id:mixed-up@mangling.notmuchmail.org)
+test_json_nodes <<<"$output" \
+                'body:["original"]'"$bodytext"
+
+test_begin_subtest "repaired 'Mixed-up' messages can be found with index.repaired=mixedup"
+output=$(notmuch search --output=messages property:index.repaired=mixedup)
+test_expect_equal "$output" id:mixed-up@mangling.notmuchmail.org
+
+test_begin_subtest "index cleartext of 'Mixed-Up' mangled PGP/MIME message"
+test_expect_success 'notmuch reindex --decrypt=true id:mixed-up@mangling.notmuchmail.org'
+
+test_begin_subtest "search cleartext of 'Mixed-Up' mangled PGP/MIME message"
+output=$(notmuch search --output=messages body:password)
+test_expect_equal "$output" id:mixed-up@mangling.notmuchmail.org
+
+test_done
index 336da917df46887bcb0c262870e9afb6307ed08c..8b2b52be032a34ec539ee673ffa8fee23dc8dc3d 100755 (executable)
@@ -3,25 +3,10 @@
 test_description='S/MIME signature verification and decryption'
 . $(dirname "$0")/test-lib.sh || exit 1
 
-add_gpgsm_home ()
-{
-    local fpr
-    [ -d ${GNUPGHOME} ] && return
-    _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 < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1
-    fpr=$(gpgsm  --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p')
-    echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt
-    test_debug "cat $GNUPGHOME/import.log"
-}
-
 test_require_external_prereq openssl
 test_require_external_prereq gpgsm
 
-cp $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)
+FINGERPRINT=$(openssl x509 -sha1 -fingerprint -in "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" -noout | sed -e 's/^.*=//' -e s/://g)
 
 add_gpgsm_home
 
@@ -37,7 +22,7 @@ test_begin_subtest "emacs delivery of S/MIME encrypted + signed message"
 test_expect_success \
 'emacs_fcc_message \
     "test encrypted message 001" \
-    "<#secure method=smime mode=signencrypt keyfile=\\\"test_suite.pem\\\" certfile=\\\"test_suite.pem\\\">\nThis is a test encrypted message.\n"'
+    "<#secure method=smime mode=signencrypt>\nThis is a test encrypted message.\n"'
 
 test_begin_subtest "Signature verification (openssl)"
 notmuch show --format=raw subject:"test signed message 001" |\
@@ -78,7 +63,7 @@ expected='[[[{"id": "XXXXX",
   "content-disposition": "attachment",
   "content-length": "NONZERO",
   "content-transfer-encoding": "base64",
-  "content-type": "application/x-pkcs7-signature",
+  "content-type": "application/pkcs7-signature",
   "filename": "smime.p7s"}]}]},
  []]]]'
 test_expect_equal_json \
@@ -87,11 +72,126 @@ 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 -decrypt -recip $NOTMUCH_SRCDIR/test/smime/key+cert.pem |\
     openssl smime -verify -CAfile $NOTMUCH_SRCDIR/test/smime/test.crt 2>OUTPUT
 cat <<EOF > EXPECTED
 Verification successful
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "Decryption (notmuch CLI)"
+notmuch show --decrypt=true subject:"test encrypted message 001" |\
+    grep "^This is a" > OUTPUT
+cat <<EOF > EXPECTED
+This is a test encrypted message.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Cryptographic message status (encrypted+signed)"
+output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001")
+test_json_nodes <<<"$output" \
+                'crypto_encrypted:[0][0][0]["crypto"]["decrypted"]["status"]="full"' \
+                'crypto_sigok:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \
+                'crypto_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="616F46CD73834C63847756AF0DFB64A6E0972A47"' \
+                'crypto_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Notmuch Test Suite"'
+
+test_begin_subtest "encrypted+signed message is known to be encrypted, but signature is unknown"
+output=$(notmuch search subject:"test encrypted message 001")
+test_expect_equal "$output" "thread:0000000000000002   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox)"
+
+test_begin_subtest "Encrypted body is not indexed"
+output=$(notmuch search 'this is a test encrypted message')
+test_expect_equal "$output" ""
+
+test_begin_subtest "Reindex cleartext"
+test_expect_success "notmuch reindex --decrypt=true subject:'test encrypted message 001'"
+
+test_begin_subtest "signature is now known"
+output=$(notmuch search subject:"test encrypted message 001")
+test_expect_equal "$output" "thread:0000000000000002   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox signed)"
+
+test_begin_subtest "Encrypted body is indexed"
+output=$(notmuch search 'this is a test encrypted message')
+test_expect_equal "$output" "thread:0000000000000002   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox signed)"
+
+add_email_corpus pkcs7
+
+test_begin_subtest "index PKCS#7 SignedData message"
+output=$(notmuch search --output=messages Thanks)
+expected=id:smime-onepart-signed@protected-headers.example
+test_expect_equal "$expected" "$output"
+
+test_begin_subtest "do not index embedded certificates from PKCS#7 SignedData"
+output=$(notmuch search --output=messages 'LAMPS Certificate')
+expected=''
+test_expect_equal "$expected" "$output"
+
+test_begin_subtest "know the MIME type of the embedded part in PKCS#7 SignedData"
+output=$(notmuch search --output=messages 'mimetype:text/plain')
+expected=id:smime-onepart-signed@protected-headers.example
+test_expect_equal "$expected" "$output"
+
+test_begin_subtest "PKCS#7 SignedData message is tagged 'signed'"
+output=$(notmuch dump id:smime-onepart-signed@protected-headers.example)
+expected='#notmuch-dump batch-tag:3 config,properties,tags
++inbox +signed +unread -- id:smime-onepart-signed@protected-headers.example'
+test_expect_equal "$expected" "$output"
+
+test_begin_subtest "show contents of PKCS#7 SignedData message"
+output=$(notmuch show --format=raw --part=2 id:smime-onepart-signed@protected-headers.example)
+whitespace=' '
+expected="Bob, we need to cancel this contract.
+
+Please start the necessary processes to make that happen today.
+
+Thanks, Alice
+--${whitespace}
+Alice Lovelace
+President
+OpenPGP Example Corp"
+test_expect_equal "$expected" "$output"
+
+test_begin_subtest "reply to PKCS#7 SignedData message with proper quoting and attribution"
+output=$(notmuch reply id:smime-onepart-signed@protected-headers.example)
+expected="From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: The FooCorp contract
+To: Alice Lovelace <alice@smime.example>, Bob Babbage <bob@smime.example>
+In-Reply-To: <smime-onepart-signed@protected-headers.example>
+References: <smime-onepart-signed@protected-headers.example>
+
+On Tue, 26 Nov 2019 20:11:29 -0400, Alice Lovelace <alice@smime.example> wrote:
+> Bob, we need to cancel this contract.
+>${whitespace}
+> Please start the necessary processes to make that happen today.
+>${whitespace}
+> Thanks, Alice
+> --${whitespace}
+> Alice Lovelace
+> President
+> OpenPGP Example Corp"
+test_expect_equal "$expected" "$output"
+
+test_begin_subtest "show PKCS#7 SignedData outputs valid JSON"
+output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
+test_valid_json "$output"
+
+test_begin_subtest "Verify signature on PKCS#7 SignedData message"
+if [ $NOTMUCH_HAVE_64BIT_TIME_T -ne 1 ]; then
+    test_subtest_known_broken
+fi
+output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
+
+test_json_nodes <<<"$output" \
+                'created:[0][0][0]["crypto"]["signed"]["status"][0]["created"]=1574813489' \
+                'expires:[0][0][0]["crypto"]["signed"]["status"][0]["expires"]=2611032858' \
+                'fingerprint:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
+                'status:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"'
+
+test_begin_subtest "Verify signature on PKCS#7 SignedData message signer User ID"
+if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then
+    test_subtest_known_broken
+fi
+test_json_nodes <<<"$output" \
+                'userid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"'
+
 test_done
index 4af018f30dc89620801dc88cff28172e7ae69937..074a23451cf7cf07aaadd1503e7dd90709e30d52 100755 (executable)
@@ -1,14 +1,14 @@
 #!/usr/bin/env bash
 
-# TODO:
-#  * check S/MIME as well as PGP/MIME
-
 test_description='Message decryption with protected headers'
 . $(dirname "$0")/test-lib.sh || exit 1
 
 ##################################################
 
+test_require_external_prereq gpgsm
+
 add_gnupg_home
+add_gpgsm_home
 
 add_email_corpus protected-headers
 
@@ -136,4 +136,73 @@ id:nested-rfc822-message@crypto.notmuchmail.org
 id:protected-header@crypto.notmuchmail.org
 id:subjectless-protected-header@crypto.notmuchmail.org'
 
+test_begin_subtest "when rendering protected headers, avoid rendering legacy-display part"
+output=$(notmuch show --format=json id:protected-with-legacy-display@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+                'subject:[0][0][0]["headers"]["Subject"]="Interrupting Cow"' \
+                'no_legacy_display:[0][0][0]["body"][0]["content"][1]["content-type"]="text/plain"'
+
+test_begin_subtest "when replying, avoid rendering legacy-display part"
+output=$(notmuch reply --format=json id:protected-with-legacy-display@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+                'no_legacy_display:["original"]["body"][0]["content"][1]["content-type"]="text/plain"'
+
+test_begin_subtest "do not treat legacy-display part as body when indexing"
+output=$(notmuch search --output=messages body:interrupting)
+test_expect_equal "$output" ''
+
+test_begin_subtest "identify message that had a legacy display part skipped during indexing"
+output=$(notmuch search --output=messages property:index.repaired=skip-protected-headers-legacy-display)
+test_expect_equal "$output" id:protected-with-legacy-display@crypto.notmuchmail.org
+
+for variant in multipart-signed onepart-signed; do
+    test_begin_subtest "verify signed PKCS#7 subject ($variant)"
+    output=$(notmuch show --verify --format=json "id:smime-${variant}@protected-headers.example")
+    test_json_nodes <<<"$output" \
+                    'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \
+                    'sig_good:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \
+                    'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
+                    'not_encrypted:[0][0][0]["crypto"]!"decrypted"'
+    test_begin_subtest "verify signed PKCS#7 subject ($variant) signer User ID"
+    if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then
+        test_subtest_known_broken
+    fi
+    test_json_nodes <<<"$output" \
+                    'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"'
+done
+
+for variant in sign+enc sign+enc+legacy-disp; do
+    test_begin_subtest "confirm signed and encrypted PKCS#7 subject ($variant)"
+    output=$(notmuch show --decrypt=true --format=json "id:smime-${variant}@protected-headers.example")
+    test_json_nodes <<<"$output" \
+                    'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \
+                    'sig_good:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \
+                    'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
+                    'encrypted:[0][0][0]["crypto"]["decrypted"]={"status":"full","header-mask":{"Subject":"..."}}'
+    test_begin_subtest "confirm signed and encrypted PKCS#7 subject ($variant) signer User ID"
+    if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then
+        test_subtest_known_broken
+    fi
+    test_json_nodes <<<"$output" \
+                    'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"'
+
+done
+
+test_begin_subtest "confirm encryption-protected PKCS#7 subject (enc+legacy-disp)"
+output=$(notmuch show --decrypt=true --format=json "id:smime-enc+legacy-disp@protected-headers.example")
+test_json_nodes <<<"$output" \
+                'encrypted:[0][0][0]["crypto"]["decrypted"]={"status":"full","header-mask":{"Subject":"..."}}' \
+                'no_sig:[0][0][0]["crypto"]!"signed"'
+
+
+# TODO: test that a part that looks like a legacy-display in
+# multipart/signed, but not encrypted, is indexed and not stripped.
+
+# TODO: test that a legacy-display in a decrypted subpart (not in the
+# cryptographic payload) is indexed and not stripped.
+
+# TODO: test that a legacy-display inside multiple MIME layers that
+# include an encryption layer (e.g. multipart/encrypted around
+# multipart/signed) is stripped and not indexed.
+
 test_done
index 8a2d4c0265a6f9b1c98b10b5727db1c87d02b0ce..1ed5f28cfc455bd74d30f0f047032f75595c2886 100755 (executable)
@@ -226,6 +226,7 @@ output=$(notmuch dump | LC_ALL=C sort)
 expected='#= simple-encrypted@crypto.notmuchmail.org index.decryption=failure
 #notmuch-dump batch-tag:3 config,properties,tags
 +encrypted +inbox +unread -- id:basic-encrypted@crypto.notmuchmail.org
++encrypted +inbox +unread -- id:encrypted-signed@crypto.notmuchmail.org
 +encrypted +inbox +unread -- id:simple-encrypted@crypto.notmuchmail.org'
 test_expect_equal \
     "$output" \
@@ -288,6 +289,27 @@ test_expect_equal \
     "$output" \
     "$expected"
 
+goodsig='good_sig:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"'
+nosig='no_sig:[0][0][0]["crypto"]!"signed"'
+
+test_begin_subtest "verify signature without a session key stashed when --decrypt=true"
+output=$(notmuch show --format=json --decrypt=true id:encrypted-signed@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" "$goodsig"
+
+test_begin_subtest "do not verify sig without a session key stashed if --decrypt=auto"
+output=$(notmuch show --format=json id:encrypted-signed@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" "$nosig"
+
+test_begin_subtest "verify signature when --decrypt=stash"
+output=$(notmuch show --format=json --decrypt=stash id:encrypted-signed@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" "$goodsig"
+
+test_begin_subtest "verify signature with stashed session key"
+output=$(notmuch show --format=json id:encrypted-signed@crypto.notmuchmail.org)
+if [ $NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY -ne 1 ]; then
+    test_subtest_known_broken
+fi
+test_json_nodes <<<"$output" "$goodsig"
 
 # TODO: test removal of a message from the message store between
 # indexing and reindexing.
index 43921cb4a275eecd00d6ebbc7e614be28c6c25f1..b34f1e54305eeb9cfbea6c83bd2bb0772c70648b 100755 (executable)
@@ -14,11 +14,11 @@ test_description='exception symbol hiding'
 test_begin_subtest 'running test' run_test
 mkdir -p ${PWD}/fakedb/.notmuch
 $TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent 2>&1 \
-       | notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,${NOTMUCH_DEFAULT_XAPIAN_BACKEND},backend,g" > OUTPUT
+       | notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,No [^[:space:]]* database,No XXXXXX database,g" > OUTPUT
 
 cat <<EOF > EXPECTED
 A Xapian exception occurred opening database: Couldn't stat 'CWD/fakedb/.notmuch/xapian'
-caught No backend database found at path 'CWD/nonexistent'
+caught No XXXXXX database found at path 'CWD/nonexistent'
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
diff --git a/test/T391-python-cffi.sh b/test/T391-python-cffi.sh
new file mode 100755 (executable)
index 0000000..f961069
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+test_description="python bindings (pytest)"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; then
+    test_done
+fi
+
+
+test_begin_subtest "python cffi tests"
+pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
+printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
+test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --log-file=$TMP_DIRECTORY/test.output)"
+test_done
index de1755d2da890d6891db54cdff0e35bd7e986f58..cca56ca32cd6875e6da4dbcd7b3b90932b3e3bef 100755 (executable)
@@ -177,7 +177,7 @@ test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\"))
                   (let ((inhibit-read-only t)) (erase-buffer)))
               (condition-case err
                   (notmuch-show \"*\")
-                (error (message \"%s\" (second err))))
+                (error (message \"%s\" (cadr err))))
               (notmuch-test-wait)
               (with-current-buffer \"*Messages*\"
                  (test-output \"MESSAGES\"))
index f84b0962c25ee5446d94b6208e88035f29c43e1e..85ff831f4f6d163ccf5bf2f26a9d32932ea1e017 100755 (executable)
@@ -14,9 +14,6 @@ test_expect_equal "$output" "thread:XXX   2010-12-16 [1/1] Olivier Berger; Essai
 
 test_begin_subtest "Absolute date field"
 output=$(notmuch search date:2010-12-16 | notmuch_search_sanitize)
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal "$output" "thread:XXX   2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)"
 
 test_begin_subtest "Absolute time range with TZ"
diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh
deleted file mode 100755 (executable)
index 2124dde..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/usr/bin/env bash
-test_description="database upgrade"
-
-. $(dirname "$0")/test-lib.sh || exit 1
-
-dbtarball=database-v1.tar.xz
-
-# XXX: Accomplish the same with test lib helpers
-if [ ! -e ${TEST_DIRECTORY}/test-databases/${dbtarball} ]; then
-    test_subtest_missing_external_prereq_["${dbtarball} - fetch with 'make download-test-databases'"]=t
-fi
-
-test_begin_subtest "database checksum"
-test_expect_success \
-    '( cd $TEST_DIRECTORY/test-databases &&
-       sha256sum --quiet --check --status ${dbtarball}.sha256 )'
-
-tar xf $TEST_DIRECTORY/test-databases/${dbtarball} -C ${MAIL_DIR} --strip-components=1
-
-test_begin_subtest "folder: search does not work with old database version"
-output=$(notmuch search folder:foo)
-test_expect_equal "$output" ""
-
-test_begin_subtest "path: search does not work with old database version"
-output=$(notmuch search path:foo)
-test_expect_equal "$output" ""
-
-test_begin_subtest "pre upgrade dump"
-test_expect_success 'notmuch dump | sort > pre-upgrade-dump'
-
-test_begin_subtest "database upgrade from format version 1"
-output=$(notmuch new | sed -e 's/^Backing up tags to .*$/Backing up tags to FILENAME/')
-test_expect_equal "$output" "\
-Welcome to a new version of notmuch! Your database will now be upgraded.
-This process is safe to interrupt.
-Backing up tags to FILENAME
-Your notmuch database has now been upgraded.
-No new mail."
-
-test_begin_subtest "tag backup matches pre-upgrade dump"
-gunzip -c ${MAIL_DIR}/.notmuch/dump-*.gz | sort > backup-dump
-test_expect_equal_file pre-upgrade-dump backup-dump
-
-test_begin_subtest "folder: no longer matches in the middle of path"
-output=$(notmuch search folder:baz)
-test_expect_equal "$output" ""
-
-test_begin_subtest "folder: search"
-output=$(notmuch search --output=files folder:foo | notmuch_search_files_sanitize | sort)
-test_expect_equal "$output" "MAIL_DIR/foo/06:2,
-MAIL_DIR/foo/cur/07:2,
-MAIL_DIR/foo/cur/08:2,
-MAIL_DIR/foo/new/03:2,
-MAIL_DIR/foo/new/09:2,
-MAIL_DIR/foo/new/10:2,"
-
-test_begin_subtest "top level folder: search"
-output=$(notmuch search --output=files folder:'""' | notmuch_search_files_sanitize | sort)
-# bar/18:2, is a duplicate of cur/51:2,
-test_expect_equal "$output" "MAIL_DIR/01:2,
-MAIL_DIR/02:2,
-MAIL_DIR/bar/18:2,
-MAIL_DIR/cur/29:2,
-MAIL_DIR/cur/30:2,
-MAIL_DIR/cur/31:2,
-MAIL_DIR/cur/32:2,
-MAIL_DIR/cur/33:2,
-MAIL_DIR/cur/34:2,
-MAIL_DIR/cur/35:2,
-MAIL_DIR/cur/36:2,
-MAIL_DIR/cur/37:2,
-MAIL_DIR/cur/38:2,
-MAIL_DIR/cur/39:2,
-MAIL_DIR/cur/40:2,
-MAIL_DIR/cur/41:2,
-MAIL_DIR/cur/42:2,
-MAIL_DIR/cur/43:2,
-MAIL_DIR/cur/44:2,
-MAIL_DIR/cur/45:2,
-MAIL_DIR/cur/46:2,
-MAIL_DIR/cur/47:2,
-MAIL_DIR/cur/48:2,
-MAIL_DIR/cur/49:2,
-MAIL_DIR/cur/50:2,
-MAIL_DIR/cur/51:2,
-MAIL_DIR/cur/52:2,
-MAIL_DIR/cur/53:2,
-MAIL_DIR/new/04:2,"
-
-test_begin_subtest "path: search"
-output=$(notmuch search --output=files path:"bar" | notmuch_search_files_sanitize | sort)
-# cur/51:2, is a duplicate of bar/18:2,
-test_expect_equal "$output" "MAIL_DIR/bar/17:2,
-MAIL_DIR/bar/18:2,
-MAIL_DIR/cur/51:2,"
-
-test_begin_subtest "top level path: search"
-output=$(notmuch search --output=files path:'""' | notmuch_search_files_sanitize | sort)
-test_expect_equal "$output" "MAIL_DIR/01:2,
-MAIL_DIR/02:2,"
-
-test_begin_subtest "recursive path: search"
-output=$(notmuch search --output=files path:"bar/**" | notmuch_search_files_sanitize | sort)
-# cur/51:2, is a duplicate of bar/18:2,
-test_expect_equal "$output" "MAIL_DIR/bar/17:2,
-MAIL_DIR/bar/18:2,
-MAIL_DIR/bar/baz/05:2,
-MAIL_DIR/bar/baz/23:2,
-MAIL_DIR/bar/baz/24:2,
-MAIL_DIR/bar/baz/cur/25:2,
-MAIL_DIR/bar/baz/cur/26:2,
-MAIL_DIR/bar/baz/new/27:2,
-MAIL_DIR/bar/baz/new/28:2,
-MAIL_DIR/bar/cur/19:2,
-MAIL_DIR/bar/cur/20:2,
-MAIL_DIR/bar/new/21:2,
-MAIL_DIR/bar/new/22:2,
-MAIL_DIR/cur/51:2,"
-
-test_begin_subtest "body: same as unprefixed before reindex"
-notmuch search --output=messages body:close > OUTPUT
-notmuch search --output=messages close  > EXPECTED
-test_expect_equal_file EXPECTED OUTPUT
-
-test_begin_subtest "body: subset of unprefixed after reindex"
-notmuch reindex '*'
-notmuch search --output=messages body:close | sort > BODY
-notmuch search --output=messages close | sort > UNPREFIXED
-diff -e UNPREFIXED BODY | cut -c2- > OUTPUT
-cat <<EOF > EXPECTED
-d
-d
-EOF
-test_expect_equal_file EXPECTED OUTPUT
-
-test_done
index 06a6b860ae8a09f5362a18a3a36acf4a734c1ed4..260ac1200a2daa0b7a1809076371959cd0ab8f4c 100755 (executable)
@@ -211,8 +211,7 @@ int main (int argc, char** argv)
      fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
      exit (1);
    }
-   path = talloc_asprintf (db, "%s/.notmuch/xapian/postlist.${db_ending}", argv[1]);
-   fd = open(path,O_WRONLY|O_TRUNC);
+   fd = open(argv[2],O_WRONLY|O_TRUNC);
    if (fd < 0) {
        fprintf (stderr, "error opening %s\n", argv[1]);
        exit (1);
@@ -228,9 +227,10 @@ cat <<'EOF' > c_tail
 }
 EOF
 
+POSTLIST_PATH=(${MAIL_DIR}/.notmuch/xapian/postlist.*)
 backup_database
 test_begin_subtest "Xapian exception finding message"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH}
    {
        notmuch_message_t *message = NULL;
        stat = notmuch_database_find_message (db, "id:nonexistent", &message);
@@ -247,7 +247,7 @@ restore_database
 
 backup_database
 test_begin_subtest "Xapian exception getting tags"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH}
    {
        notmuch_tags_t *tags = NULL;
        tags = notmuch_database_get_all_tags (db);
@@ -265,7 +265,7 @@ restore_database
 
 backup_database
 test_begin_subtest "Xapian exception creating directory"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH}
    {
        notmuch_directory_t *directory = NULL;
        stat = notmuch_database_get_directory (db, "none/existing", &directory);
@@ -275,14 +275,14 @@ sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
 cat <<'EOF' >EXPECTED
 == stdout ==
 == stderr ==
-A Xapian exception occurred creating a directory
+A Xapian exception occurred finding/creating a directory
 EOF
 test_expect_equal_file EXPECTED OUTPUT.clean
 restore_database
 
 backup_database
 test_begin_subtest "Xapian exception searching messages"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH}
    {
        notmuch_messages_t *messages = NULL;
        notmuch_query_t *query=notmuch_query_create (db, "*");
@@ -301,7 +301,7 @@ restore_database
 
 backup_database
 test_begin_subtest "Xapian exception counting messages"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH}
    {
        int count;
        notmuch_query_t *query=notmuch_query_create (db, "id:87ocn0qh6d.fsf@yoom.home.cworth.org");
diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh
new file mode 100755 (executable)
index 0000000..dd4f256
--- /dev/null
@@ -0,0 +1,428 @@
+#!/usr/bin/env bash
+test_description="notmuch_database_* API"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "building database"
+test_expect_success "NOTMUCH_NEW"
+
+cat <<EOF > c_head
+#include <stdio.h>
+#include <notmuch.h>
+#include <notmuch-test.h>
+#include <talloc.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat = NOTMUCH_STATUS_SUCCESS;
+   char *msg = NULL;
+
+   stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
+     exit (1);
+   }
+EOF
+
+cat <<'EOF' > c_tail
+   if (stat) {
+       const char *stat_str = notmuch_database_status_string (db);
+       if (stat_str)
+           fputs (stat_str, stderr);
+    }
+
+}
+EOF
+
+test_begin_subtest "get status_string with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *str;
+        EXPECT0(notmuch_database_close (db));
+        str = notmuch_database_status_string (db);
+        printf("%d\n", str == NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get path with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *path;
+        EXPECT0(notmuch_database_close (db));
+        path = notmuch_database_get_path (db);
+        printf("%s\n", path);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+MAIL_DIR
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get version with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        unsigned int version;
+        EXPECT0(notmuch_database_close (db));
+        version = notmuch_database_get_version (db);
+        printf ("%u\n", version);
+        stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+0
+== stderr ==
+A Xapian exception occurred at lib/database.cc:XXX: Database has been closed
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "re-close a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_close (db);
+        printf ("%d\n", stat);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+0
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "destroy a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        unsigned int version;
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_destroy (db);
+        printf ("%d\n", stat);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+0
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "destroy an open db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        unsigned int version;
+        stat = notmuch_database_destroy (db);
+        printf ("%d\n", stat);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+0
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "check a closed db for upgrade"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_bool_t ret;
+
+        EXPECT0(notmuch_database_close (db));
+        ret = notmuch_database_needs_upgrade (db);
+        printf ("%d\n", ret == FALSE);
+        stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred at lib/database.cc:XXX: Database has been closed
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "upgrade a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_upgrade (db, NULL, NULL);
+        printf ("%d\n", stat == NOTMUCH_STATUS_SUCCESS);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "begin atomic section for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_begin_atomic (db);
+        printf ("%d\n", stat == NOTMUCH_STATUS_SUCCESS ||
+                        stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+        stat = NOTMUCH_STATUS_SUCCESS;
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "end atomic section for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        EXPECT0(notmuch_database_close (db));
+        EXPECT0(notmuch_database_begin_atomic (db));
+        stat = notmuch_database_end_atomic (db);
+        printf ("%d\n", stat == NOTMUCH_STATUS_SUCCESS ||
+                        stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+        stat = NOTMUCH_STATUS_SUCCESS;
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get revision for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *uuid;
+        unsigned long rev;
+
+        EXPECT0(notmuch_database_close (db));
+        rev = notmuch_database_get_revision (db, &uuid);
+        printf ("%d\n", rev, uuid);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+53
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get directory for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_directory_t *dir;
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_get_directory (db, "/nonexistent", &dir);
+        printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred finding/creating a directory: Database has been closed.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "index file with a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_message_t *msg;
+        const char *path = talloc_asprintf(db, "%s/01:2,", argv[1]);
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_index_file (db, path, NULL, &msg);
+        printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred finding message: Database has been closed.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+generate_message '[filename]=relative_path'
+test_begin_subtest "index file (relative path)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_message_t *msg;
+        stat = notmuch_database_index_file (db, "relative_path", NULL, &msg);
+        printf ("%d\n", stat == NOTMUCH_STATUS_SUCCESS);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "index file (absolute path outside mail root)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_message_t *msg;
+        stat = notmuch_database_index_file (db, "/dev/zero", NULL, &msg);
+        printf ("%d\n", stat == NOTMUCH_STATUS_FILE_ERROR);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+Error opening /dev/zero: path outside mail root
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "remove message file with a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_remove_message (db, "01:2,");
+        printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred finding/creating a directory: Database has been closed.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "find message by filename with a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_message_t *msg;
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_find_message_by_filename (db, "01:2,", &msg);
+        printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred finding/creating a directory: Database has been closed.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle getting tags from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_tags_t *result;
+        EXPECT0(notmuch_database_close (db));
+        result = notmuch_database_get_all_tags (db);
+        printf("%d\n",  result == NULL);
+        stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred getting tags: Database has been closed.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get config from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *result;
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_get_config (db, "foo", &result);
+        printf("%d\n",  stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+Error: A Xapian exception occurred getting metadata: Database has been closed
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "set config in closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_database_set_config (db, "foo", "bar");
+        printf("%d\n",  stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+Error: A Xapian exception occurred setting metadata: Database has been closed
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get indexopts from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_indexopts_t *result;
+        EXPECT0(notmuch_database_close (db));
+        result = notmuch_database_get_default_indexopts (db);
+        printf("%d\n",  result == NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get decryption policy from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_indexopts_t *result;
+        result = notmuch_database_get_default_indexopts (db);
+        EXPECT0(notmuch_database_close (db));
+        notmuch_decryption_policy_t policy = notmuch_indexopts_get_decrypt_policy (result);
+        printf ("%d\n",  policy == NOTMUCH_DECRYPT_AUTO);
+        notmuch_indexopts_destroy (result);
+        printf ("SUCCESS\n");
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+SUCCESS
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "set decryption policy with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_indexopts_t *result;
+        result = notmuch_database_get_default_indexopts (db);
+        EXPECT0(notmuch_database_close (db));
+        notmuch_decryption_policy_t policy = notmuch_indexopts_get_decrypt_policy (result);
+        stat = notmuch_indexopts_set_decrypt_policy (result, policy);
+        printf("%d\n%d\n",  policy == NOTMUCH_DECRYPT_AUTO, stat == NOTMUCH_STATUS_SUCCESS);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh
new file mode 100755 (executable)
index 0000000..28325ff
--- /dev/null
@@ -0,0 +1,130 @@
+#!/usr/bin/env bash
+test_description="notmuch_directory_* API"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "building database"
+test_expect_success "NOTMUCH_NEW"
+
+cat <<EOF > c_head
+#include <stdio.h>
+#include <notmuch.h>
+#include <notmuch-test.h>
+#include <talloc.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_directory_t *dir;
+   notmuch_status_t stat = NOTMUCH_STATUS_SUCCESS;
+   char *msg = NULL;
+
+   stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
+     exit (1);
+   }
+
+   EXPECT0(notmuch_database_get_directory (db, "bar", &dir));
+   EXPECT0(notmuch_database_close (db));
+EOF
+
+cat <<'EOF' > c_tail
+   if (stat) {
+       const char *stat_str = notmuch_database_status_string (db);
+       if (stat_str)
+           fputs (stat_str, stderr);
+    }
+
+}
+EOF
+
+test_begin_subtest "get child directories for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_filenames_t *children;
+        children = notmuch_directory_get_child_directories (dir);
+        printf ("%d\n", children == NULL);
+        stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get child filenames for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_filenames_t *children;
+        children = notmuch_directory_get_child_files (dir);
+        printf ("%d\n", children == NULL);
+        stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+backup_database
+test_begin_subtest "delete directory document for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        stat = notmuch_directory_delete (dir);
+        printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred deleting directory entry: Database has been closed.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "get/set mtime of directory for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        time_t stamp = notmuch_directory_get_mtime (dir);
+        stat = notmuch_directory_set_mtime (dir, stamp);
+        printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred setting directory mtime: Database has been closed.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "get/set mtime of directory for a closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        time_t stamp = notmuch_directory_get_mtime (dir);
+        stat = notmuch_directory_set_mtime (dir, stamp);
+        printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred setting directory mtime: Database has been closed.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+test_done
diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh
new file mode 100755 (executable)
index 0000000..50b0a88
--- /dev/null
@@ -0,0 +1,254 @@
+#!/usr/bin/env bash
+test_description="notmuch_query_* API"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "building database"
+test_expect_success "NOTMUCH_NEW"
+
+cat <<EOF > c_head
+#include <stdio.h>
+#include <notmuch.h>
+#include <notmuch-test.h>
+#include <talloc.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   char *msg = NULL;
+
+   stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
+     exit (1);
+   }
+EOF
+
+cat <<'EOF' > c_tail
+   if (stat) {
+       const char *stat_str = notmuch_database_status_string (db);
+       if (stat_str)
+           fputs (stat_str, stderr);
+    }
+
+}
+EOF
+
+test_begin_subtest "roundtrip query string with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+        const char *ret;
+
+        EXPECT0(notmuch_database_close (db));
+        query = notmuch_query_create (db, str);
+        ret = notmuch_query_get_query_string (query);
+
+        printf("%s\n%s\n", str, ret);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net
+id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "retrieve closed db from query"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+        notmuch_database_t *db2;
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        db2 = notmuch_query_get_database (query);
+
+        printf("%d\n", db == db2);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "set omit_excluded on closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        notmuch_query_set_omit_excluded (query, NOTMUCH_EXCLUDE_ALL);
+
+        printf("SUCCESS\n");
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+SUCCESS
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "roundtrip sort on closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+        notmuch_sort_t sort;
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+        sort = notmuch_query_get_sort (query);
+        printf("%d\n", sort == NOTMUCH_SORT_UNSORTED);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "add tag_exclude on closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_query_add_tag_exclude (query, "spam");
+        printf("%d\n", stat == NOTMUCH_STATUS_SUCCESS);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search threads on closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+        notmuch_threads_t *threads;
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_query_search_threads (query, &threads);
+
+        printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred performing query: Database has been closed
+Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search messages on closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+        notmuch_messages_t *messages;
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_query_search_messages (query, &messages);
+
+        printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred performing query: Database has been closed
+Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "count messages on closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+        unsigned int count;
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_query_count_messages (query, &count);
+
+        printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred performing query: Database has been closed
+Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "count threads on closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+        unsigned int count;
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        stat = notmuch_query_count_threads (query, &count);
+
+        printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred performing query: Database has been closed
+Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "destroy query with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_query_t *query;
+        const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+
+        query = notmuch_query_create (db, str);
+        EXPECT0(notmuch_database_close (db));
+        notmuch_query_destroy (query);
+
+        printf("SUCCESS\n");
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+SUCCESS
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T566-lib-message.sh b/test/T566-lib-message.sh
new file mode 100755 (executable)
index 0000000..0ba601f
--- /dev/null
@@ -0,0 +1,404 @@
+#!/usr/bin/env bash
+test_description="API tests for notmuch_message_*"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "building database"
+test_expect_success "NOTMUCH_NEW"
+
+cat <<'EOF' > c_tail
+   if (stat) {
+       const char *stat_str = notmuch_database_status_string (db);
+       if (stat_str)
+           fputs (stat_str, stderr);
+    }
+
+}
+EOF
+
+cat <<EOF > c_head0
+#include <stdio.h>
+#include <notmuch.h>
+#include <notmuch-test.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   char *msg = NULL;
+   notmuch_message_t *message = NULL;
+   const char *id = "1258471718-6781-1-git-send-email-dottedmag@dottedmag.net";
+
+   stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
+     exit (1);
+   }
+   EXPECT0(notmuch_database_find_message (db, id, &message));
+EOF
+
+cp c_head0 c_head
+echo "   EXPECT0(notmuch_database_close (db));" >> c_head
+
+test_begin_subtest "Handle getting message-id from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *id2;
+        id2=notmuch_message_get_message_id (message);
+        printf("%d\n%d\n", message != NULL, id2==NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle getting thread-id from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *id2;
+        id2=notmuch_message_get_thread_id (message);
+        printf("%d\n%d\n", message != NULL, id2==NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle getting header from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *from;
+        from=notmuch_message_get_header (message, "from");
+        printf("%s\n%d\n", id, from == NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1258471718-6781-1-git-send-email-dottedmag@dottedmag.net
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+# XXX this test only tests the trivial code path
+test_begin_subtest "Handle getting replies from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_messages_t *replies;
+        replies = notmuch_message_get_replies (message);
+        printf("%d\n%d\n", message != NULL, replies==NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle getting message filename from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *filename;
+        filename = notmuch_message_get_filename (message);
+        printf("%d\n%d\n", message != NULL, filename == NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle getting all message filenames from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_filenames_t *filenames;
+        filenames = notmuch_message_get_filenames (message);
+        printf("%d\n%d\n", message != NULL, filenames == NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "iterate over all message filenames from closed database"
+cat c_head0 - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_filenames_t *filenames;
+        filenames = notmuch_message_get_filenames (message);
+        EXPECT0(notmuch_database_close (db));
+        for (; notmuch_filenames_valid (filenames);
+               notmuch_filenames_move_to_next (filenames)) {
+            const char *filename = notmuch_filenames_get (filenames);
+            printf("%s\n", filename);
+        }
+        notmuch_filenames_destroy (filenames);
+        printf("SUCCESS\n");
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+MAIL_DIR/01:2,
+SUCCESS
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle getting ghost flag from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_bool_t result;
+        result = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST);
+        printf("%d\n%d\n", message != NULL, result == FALSE);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle getting date from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        time_t result;
+        result = notmuch_message_get_date (message);
+        printf("%d\n%d\n", message != NULL, result == 0);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle getting tags from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_tags_t *result;
+        result = notmuch_message_get_tags (message);
+        printf("%d\n%d\n", message != NULL, result == NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle counting files from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        int result;
+        result = notmuch_message_count_files (message);
+        printf("%d\n%d\n", message != NULL, result < 0);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle adding tag with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_status_t status;
+        status = notmuch_message_add_tag (message, "boom");
+        printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle removing tag with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_status_t status;
+        status = notmuch_message_remove_tag (message, "boom");
+        printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle read maildir flag with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_bool_t is_set = -1;
+        is_set = notmuch_message_has_maildir_flag (message, 'S');
+        printf("%d\n%d\n", message != NULL, is_set == FALSE || is_set == TRUE);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle checking maildir flag with closed db (new API)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_status_t status;
+        notmuch_bool_t out;
+        status = notmuch_message_has_maildir_flag_st (message, 'S', &out);
+        printf("%d\n%d\n", message != NULL,  status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle converting maildir flags to tags with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_status_t status;
+        status = notmuch_message_maildir_flags_to_tags (message);
+        printf("%d\n%d\n", message != NULL,  status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle removing all tags with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_status_t status;
+        status = notmuch_message_remove_all_tags (message);
+        printf("%d\n%d\n", message != NULL,  status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle freezing message with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_status_t status;
+        status = notmuch_message_freeze (message);
+        printf("%d\n%d\n", message != NULL,  status == NOTMUCH_STATUS_SUCCESS);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle thawing message with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_status_t status;
+        status = notmuch_message_thaw (message);
+        printf("%d\n%d\n", message != NULL,  status == NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle destroying message with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_message_destroy (message);
+        printf("%d\n%d\n", message != NULL,  1);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle retrieving closed db from message"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_database_t *db2;
+        db2 = notmuch_message_get_database (message);
+        printf("%d\n%d\n", message != NULL,  db == db2);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Handle reindexing message with closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_status_t status;
+        status = notmuch_message_reindex (message, NULL);
+        printf("%d\n%d\n", message != NULL,  status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh
new file mode 100755 (executable)
index 0000000..ac13d98
--- /dev/null
@@ -0,0 +1,334 @@
+#!/usr/bin/env bash
+test_description="API tests for notmuch_thread_*"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "building database"
+test_expect_success "NOTMUCH_NEW"
+
+cat <<'EOF' > c_tail
+   if (stat) {
+       const char *stat_str = notmuch_database_status_string (db);
+       if (stat_str)
+           fputs (stat_str, stderr);
+    }
+
+}
+EOF
+
+cat <<EOF > c_head
+#include <stdio.h>
+#include <notmuch.h>
+#include <notmuch-test.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   char *msg = NULL;
+   notmuch_thread_t *thread = NULL;
+   notmuch_threads_t *threads = NULL;
+   notmuch_query_t *query = NULL;
+   const char *id = "thread:0000000000000009";
+
+   stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
+     exit (1);
+   }
+
+   query = notmuch_query_create (db, id);
+   EXPECT0(notmuch_query_search_threads (query, &threads));
+   thread = notmuch_threads_get (threads);
+   EXPECT0(notmuch_database_close (db));
+EOF
+
+test_begin_subtest "get thread-id from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *id2;
+        id2 = notmuch_thread_get_thread_id (thread);
+        printf("%d\n%s\n", thread != NULL, id2);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+0000000000000009
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get total messages with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        int count;
+        count = notmuch_thread_get_total_messages (thread);
+        printf("%d\n%d\n", thread != NULL, count);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+7
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get total files with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        int count;
+        count = notmuch_thread_get_total_files (thread);
+        printf("%d\n%d\n", thread != NULL, count);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+7
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get top level messages with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        notmuch_messages_t *messages;
+        messages = notmuch_thread_get_toplevel_messages (thread);
+        printf("%d\n%d\n", thread != NULL, messages != NULL);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "iterate over level messages with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+      notmuch_messages_t *messages;
+      for (messages = notmuch_thread_get_toplevel_messages (thread);
+           notmuch_messages_valid (messages);
+           notmuch_messages_move_to_next (messages)) {
+        notmuch_message_t *message = notmuch_messages_get (messages);
+        const char *mid = notmuch_message_get_message_id (message);
+        printf("%s\n", mid);
+      }
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+20091117190054.GU3165@dottiness.seas.harvard.edu
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "iterate over level messages with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+      notmuch_messages_t *messages;
+      for (messages = notmuch_thread_get_toplevel_messages (thread);
+           notmuch_messages_valid (messages);
+           notmuch_messages_move_to_next (messages)) {
+        notmuch_message_t *message = notmuch_messages_get (messages);
+        const char *mid = notmuch_message_get_message_id (message);
+        printf("%s\n", mid);
+      }
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+20091117190054.GU3165@dottiness.seas.harvard.edu
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "iterate over replies with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+      notmuch_messages_t *messages = notmuch_thread_get_toplevel_messages (thread);
+      notmuch_message_t *message = notmuch_messages_get (messages);
+      notmuch_messages_t *replies;
+      for (replies = notmuch_message_get_replies (message);
+           notmuch_messages_valid (replies);
+           notmuch_messages_move_to_next (replies)) {
+        notmuch_message_t *message = notmuch_messages_get (replies);
+        const char *mid = notmuch_message_get_message_id (message);
+
+        printf("%s\n", mid);
+      }
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+87iqd9rn3l.fsf@vertex.dottedmag
+87ocn0qh6d.fsf@yoom.home.cworth.org
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "iterate over all messages with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+      notmuch_messages_t *messages;
+      for (messages = notmuch_thread_get_messages (thread);
+           notmuch_messages_valid (messages);
+           notmuch_messages_move_to_next (messages)) {
+        notmuch_message_t *message = notmuch_messages_get (messages);
+        const char *mid = notmuch_message_get_message_id (message);
+        printf("%s\n", mid);
+      }
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+20091117190054.GU3165@dottiness.seas.harvard.edu
+87iqd9rn3l.fsf@vertex.dottedmag
+20091117203301.GV3165@dottiness.seas.harvard.edu
+87fx8can9z.fsf@vertex.dottedmag
+yunaayketfm.fsf@aiko.keithp.com
+20091118005040.GA25380@dottiness.seas.harvard.edu
+87ocn0qh6d.fsf@yoom.home.cworth.org
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get authors from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *authors;
+        authors = notmuch_thread_get_authors (thread);
+        printf("%d\n%s\n", thread != NULL, authors);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get subject from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        const char *subject;
+        subject = notmuch_thread_get_subject (thread);
+        printf("%d\n%s\n", thread != NULL, subject);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+[notmuch] Working with Maildir storage?
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "oldest date from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        time_t stamp;
+        stamp = notmuch_thread_get_oldest_date (thread);
+        printf("%d\n%d\n", thread != NULL, stamp > 0);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "newest date from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        time_t stamp;
+        stamp = notmuch_thread_get_newest_date (thread);
+        printf("%d\n%d\n", thread != NULL, stamp > 0);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "iterate tags from closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+      notmuch_tags_t *tags;
+      const char *tag;
+      for (tags = notmuch_thread_get_tags (thread);
+           notmuch_tags_valid (tags);
+           notmuch_tags_move_to_next (tags))
+        {
+          tag = notmuch_tags_get (tags);
+          printf ("%s\n", tag);
+        }
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+inbox
+signed
+unread
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "collect tags with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+      notmuch_messages_t *messages = notmuch_thread_get_messages (thread);
+
+      notmuch_tags_t *tags = notmuch_messages_collect_tags (messages);
+
+      const char *tag;
+      for (tags = notmuch_thread_get_tags (thread);
+           notmuch_tags_valid (tags);
+           notmuch_tags_move_to_next (tags))
+        {
+          tag = notmuch_tags_get (tags);
+          printf ("%s\n", tag);
+        }
+      notmuch_tags_destroy (tags);
+      notmuch_messages_destroy (messages);
+
+      printf("SUCCESS\n");
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+inbox
+signed
+unread
+SUCCESS
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "destroy thread with closed database"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+    {
+        time_t stamp;
+        notmuch_thread_destroy (thread);
+        printf("SUCCESS\n");
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+SUCCESS
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
index bf9894d3cb457cbb4e312327c5dfa3814ad6ae3b..71ced1491568d5514307c29b9e9035205c585bf3 100755 (executable)
@@ -14,9 +14,6 @@ count=$(notmuch count from:keithp and to:keithp)
 test_expect_equal 0 "$count"
 
 test_begin_subtest "Same query against threads"
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
-    test_subtest_known_broken
-fi
 notmuch search thread:{from:keithp} and thread:{to:keithp} | notmuch_search_sanitize > OUTPUT
 cat<<EOF > EXPECTED
 thread:XXX   2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
@@ -24,9 +21,6 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Mix thread and non-threads query"
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
-    test_subtest_known_broken
-fi
 notmuch search thread:{from:keithp} and to:keithp | notmuch_search_sanitize > OUTPUT
 cat<<EOF > EXPECTED
 thread:XXX   2009-11-18 [1/7] Lars Kellogg-Stedman| Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
@@ -34,9 +28,6 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Compound subquery"
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
-    test_subtest_known_broken
-fi
 notmuch search 'thread:"{from:keithp and date:2009}" and thread:{to:keithp}' | notmuch_search_sanitize > OUTPUT
 cat<<EOF > EXPECTED
 thread:XXX   2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
@@ -44,9 +35,6 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Syntax/quoting error in subquery"
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
-    test_subtest_known_broken
-fi
 notmuch search 'thread:{from:keithp and date:2009} and thread:{to:keithp}' 1>OUTPUT 2>&1
 cat<<EOF > EXPECTED
 notmuch search: A Xapian exception occurred
index 46f3a76d574fbd8c485179ac6e241a5cc93ed07a..360e45b08913eae399694c5d224a85aeec628ed1 100755 (executable)
@@ -61,6 +61,21 @@ valid = 0
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "notmuch_database_get_config_list: closed db"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_config_list_t *list;
+   EXPECT0(notmuch_database_close (db));
+   stat = notmuch_database_get_config_list (db, "nonexistent", &list);
+   printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "notmuch_database_get_config_list: all pairs"
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
@@ -85,6 +100,28 @@ zzzafter afterval
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "notmuch_database_get_config_list: all pairs (closed db)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_config_list_t *list;
+   EXPECT0(notmuch_database_get_config_list (db, "", &list));
+   EXPECT0(notmuch_database_close (db));
+   for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+      printf("%s %d\n", notmuch_config_list_key (list), NULL == notmuch_config_list_value(list));
+   }
+   notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+aaabefore 1
+testkey1 1
+testkey2 1
+zzzafter 1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "notmuch_database_get_config_list: one prefix"
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
index abaee3b7ca4c7d7165d1bf42df806694ab56156d..0ae8b83d976a8ac897ed26f8127d7495cbba22b6 100755 (executable)
@@ -36,6 +36,21 @@ cat<<EOF > QUERIES.BEFORE
 EOF
 test_expect_equal_file QUERIES.BEFORE OUTPUT
 
+test_begin_subtest 'dumping large queries'
+# This value is just large enough to trigger a limitation of gzprintf
+# to 8191 bytes in total (by default).
+repeat=1329
+notmuch config set query.big "$(seq -s' ' $repeat)"
+notmuch dump --include=config > OUTPUT
+notmuch config set query.big ''
+printf "#notmuch-dump batch-tag:3 config\n#@ query.big " > EXPECTED
+seq -s'%20' $repeat >> EXPECTED
+cat <<EOF >> EXPECTED
+#@ query.test date%3a2009-11-18..2009-11-18%20and%20tag%3aunread
+#@ query.test2 query%3atest%20and%20subject%3aMaildir
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "delete named queries"
 notmuch dump > BEFORE
 notmuch config set query.test
@@ -53,17 +68,11 @@ test_expect_equal_file QUERIES.BEFORE OUTPUT
 test_begin_subtest "search named query"
 notmuch search query:test > OUTPUT
 notmuch search $QUERYSTR > EXPECTED
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "search named query with other terms"
 notmuch search query:test and subject:Maildir > OUTPUT
 notmuch search $QUERYSTR and subject:Maildir > EXPECTED
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then
-    test_subtest_known_broken
-fi
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "search nested named query"
index 53a0be3bdf3c3cb170ad59fe9c8fc6b9dffa780e..d0e52f4ac46cbb481b76197682f43ecb3adce728 100755 (executable)
@@ -65,7 +65,7 @@ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
    EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
    printf("testkey2 = %s\n", val);
 
-   /* remove non-existant value for key */
+   /* remove non-existent value for key */
    EXPECT0(notmuch_message_remove_property (message, "testkey2", "this value has spaces and = sign"));
    EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
    printf("testkey2 = %s\n", val);
@@ -186,6 +186,18 @@ EXPECT0(notmuch_message_add_property (message, "testkey3", "testvalue3"));
 EXPECT0(notmuch_message_add_property (message, "testkey3", "alice3"));
 print_properties (message, "testkey", FALSE);
 EOF
+# expected: 4 values for testkey1, 3 values for testkey3
+# they are not guaranteed to be sorted, so sort them, leaving the first
+# line '== stdout ==' and the end ('== stderr ==' and whatever error
+# may have been printed) alone
+mv OUTPUT unsorted_OUTPUT
+awk ' NR == 1 { print; next } \
+      NR < 6  { print | "sort"; next } \
+      NR == 6 { close("sort") } \
+      NR < 9  { print | "sort"; next } \
+      NR == 9 { close("sort") } \
+      { print }' unsorted_OUTPUT > OUTPUT
+rm unsorted_OUTPUT
 cat <<'EOF' >EXPECTED
 == stdout ==
 alice
index 43af3b47ced7826ed0a710a3bda3e9df1b54c2a1..55dc6c8870d3a1fd8eccbf280bc79628e9b36aec 100755 (executable)
@@ -2,10 +2,6 @@
 test_description='regular expression searches'
 . $(dirname "$0")/test-lib.sh || exit 1
 
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
-    test_done
-fi
-
 add_message '[dir]=bad' '[subject]="To the bone"'
 add_message '[dir]=.' '[subject]="Top level"'
 add_message '[dir]=bad/news' '[subject]="Bears"'
index c17ccb6970ac0740517c00dbaa6325e9d0e259ea..4e5672abc4e880a1b6c536366d5936d03d7c373b 100755 (executable)
@@ -48,11 +48,7 @@ notmuch search --output=files subject:'"message 2"' | notmuch_dir_sanitize > OUT
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest 'Regexp search for second subject'
-# Note that missing field processor support really means the test
-# doesn't make sense, but it happens to pass.
-if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 1 ]; then
-    test_subtest_known_broken
-fi
+test_subtest_known_broken
 cat <<EOF >EXPECTED
 MAIL_DIR/copy0
 MAIL_DIR/copy1
index 9e795896a4661bb27b844b4fb47860981a6edd51..3d7c930d6bdb0fe557b665c76385e5343478d541 100755 (executable)
@@ -33,6 +33,14 @@ notmuch reindex '*'
 notmuch dump > OUTPUT
 test_expect_equal_file initial-dump OUTPUT
 
+test_begin_subtest 'reindex preserves tags with special prefixes'
+notmuch tag +attachment2 +encrypted2 +signed2  '*'
+notmuch dump > EXPECTED
+notmuch reindex '*'
+notmuch dump > OUTPUT
+notmuch tag -attachment2 -encrypted2 -signed2  '*'
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest 'reindex moves a message between threads'
 notmuch search --output=threads id:87iqd9rn3l.fsf@vertex.dottedmag > EXPECTED
 # re-parent
index e73d6ba972d7bf2b73a053549183bb7b989e1204..5129d84c544dc5f0b101fed30a51da0e5cd7c590 100755 (executable)
@@ -29,7 +29,7 @@ GOOD: 1258787708-21121-2-git-send-email-keithp@keithp.com
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-test_begin_subtest "<> delimeters are required"
+test_begin_subtest "<> delimiters are required"
 ${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
 018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org>
 <1530507300.raoomurnbf.astroid@strange.none
index 6322854675fdae93cabbf55ac9a7cb531513b00d..75400e6e3e3f6f14dbecff9902df84ec5aaf26f9 100755 (executable)
@@ -7,87 +7,75 @@ success=0
 failed=0
 broken=0
 total=0
+all_skipped=0
 
 for file
 do
        while read type value
        do
                case $type in
-               '')
-                       continue ;;
                fixed)
-                       fixed=$(($fixed + $value)) ;;
+                       fixed=$((fixed + value)) ;;
                success)
-                       success=$(($success + $value)) ;;
+                       success=$((success + value)) ;;
                failed)
-                       failed=$(($failed + $value)) ;;
+                       failed=$((failed + value)) ;;
                broken)
-                       broken=$(($broken + $value)) ;;
+                       broken=$((broken + value)) ;;
                total)
-                       total=$(($total + $value)) ;;
+                       total=$((total + value))
+                       if [ "$value" -eq 0 ]; then
+                               all_skipped=$((all_skipped + 1))
+                       fi
                esac
        done <"$file"
 done
 
-pluralize () {
-    case $2 in
-       1)
-           case $1 in
-               test)
-                   echo test ;;
-               failure)
-                   echo failure ;;
-           esac
-           ;;
-       *)
-           case $1 in
-               test)
-                   echo tests ;;
-               failure)
-                   echo failures ;;
-           esac
-           ;;
-    esac
-}
+pluralize_s () { [ "$1" -eq 1 ] && s='' || s='s'; }
 
 echo "Notmuch test suite complete."
-if [ "$fixed" = "0" ] && [ "$failed" = "0" ]; then
-    tests=$(pluralize "test" $total)
-    printf "All $total $tests "
-    if [ "$broken" = "0" ]; then
-       echo "passed."
-    else
-       failures=$(pluralize "failure" $broken)
-       echo "behaved as expected ($broken expected $failures)."
-    fi;
+
+if [ "$fixed" -eq 0 ] && [ "$failed" -eq 0 ]; then
+       pluralize_s "$total"
+       printf "All $total test$s "
+       if [ "$broken" -eq 0 ]; then
+               echo "passed."
+       else
+               pluralize_s "$broken"
+               echo "behaved as expected ($broken expected failure$s)."
+       fi
 else
-    echo "$success/$total tests passed."
-    if [ "$broken" != "0" ]; then
-       tests=$(pluralize "test" $broken)
-       echo "$broken broken $tests failed as expected."
-    fi
-    if [ "$fixed" != "0" ]; then
-       tests=$(pluralize "test" $fixed)
-       echo "$fixed broken $tests now fixed."
-    fi
-    if [ "$failed" != "0" ]; then
-       tests=$(pluralize "test" $failed)
-       echo "$failed $tests failed."
-    fi
+       echo "$success/$total tests passed."
+       if [ "$broken" -ne 0 ]; then
+               pluralize_s "$broken"
+               echo "$broken broken test$s failed as expected."
+       fi
+       if [ "$fixed" -ne 0 ]; then
+               pluralize_s "$fixed"
+               echo "$fixed broken test$s now fixed."
+       fi
+       if [ "$failed" -ne 0 ]; then
+               pluralize_s "$failed"
+               echo "$failed test$s failed."
+       fi
 fi
 
-skipped=$(($total - $fixed - $success - $failed - $broken))
-if [ "$skipped" != "0" ]; then
-    tests=$(pluralize "test" $skipped)
-    echo "$skipped $tests skipped."
+skipped=$((total - fixed - success - failed - broken))
+if [ "$skipped" -ne 0 ]; then
+       pluralize_s "$skipped"
+       echo "$skipped test$s skipped."
+fi
+if [ "$all_skipped" -ne 0 ]; then
+       pluralize_s "$all_skipped"
+       echo "All tests in $all_skipped file$s skipped."
 fi
 
 # Note that we currently do not consider skipped tests as failing the
 # build.
 
-if [ $success -gt 0 -a $fixed -eq 0 -a $failed -eq 0 ]
+if [ "$success" -gt 0 ] && [ "$fixed" -eq 0 ] && [ "$failed" -eq 0 ]
 then
-    exit 0
+       exit 0
 else
-    exit 1
+       exit 1
 fi
index a218f967e7ff01665b1575a63e44b2b1dae3ac41..44477730c274f32146bdee71e63d9b5d0c61ab78 100644 (file)
@@ -2,27 +2,29 @@
 #include "command-line-arguments.h"
 
 
-int main(int argc, char **argv){
-
-    int opt_index=1;
-
-    int kw_val=0;
-    int kwb_val=0;
-    int fl_val=0;
-    int int_val=0;
-    const char *pos_arg1=NULL;
-    const char *pos_arg2=NULL;
-    const char *string_val=NULL;
+int
+main (int argc, char **argv)
+{
+
+    int opt_index = 1;
+
+    int kw_val = 0;
+    int kwb_val = 0;
+    int fl_val = 0;
+    int int_val = 0;
+    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, kwb_set = false,
-       kw_set = false, string_set = false, pos1_set = false, pos2_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_keyword_t []){ { "one",   1 << 0 },
+                                     { "two",   1 << 1 },
+                                     { "three", 1 << 2 },
+                                     { 0, 0 } } },
        { .opt_int = &int_val, .name = "int", .present = &int_set },
        { }
     };
@@ -30,16 +32,16 @@ int main(int argc, char **argv){
     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 } } },
+             (notmuch_keyword_t []){ { "zero", 0 },
+                                     { "one", 1 },
+                                     { "two", 2 },
+                                     { 0, 0 } } },
        { .opt_keyword = &kwb_val, .name = "boolkeyword", .present = &kwb_set,
          .keyword_no_arg_value = "true", .keywords =
-         (notmuch_keyword_t []){ { "false", 0 },
-                                 { "true", 1 },
-                                 { "auto", 2 },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "false", 0 },
+                                     { "true", 1 },
+                                     { "auto", 2 },
+                                     { 0, 0 } } },
        { .opt_inherit = parent_options },
        { .opt_string = &string_val, .name = "string", .present = &string_set },
        { .opt_position = &pos_arg1, .present = &pos1_set },
@@ -47,38 +49,38 @@ int main(int argc, char **argv){
        { }
     };
 
-    opt_index = parse_arguments(argc, argv, options, 1);
+    opt_index = parse_arguments (argc, argv, options, 1);
 
     if (opt_index < 0)
        return 1;
 
     if (bool_set)
-       printf("boolean %d\n", bool_val);
+       printf ("boolean %d\n", bool_val);
 
     if (kw_set)
-       printf("keyword %d\n", kw_val);
+       printf ("keyword %d\n", kw_val);
 
     if (kwb_set)
-       printf("boolkeyword %d\n", kwb_val);
+       printf ("boolkeyword %d\n", kwb_val);
 
     if (fl_set)
-       printf("flags %d\n", fl_val);
+       printf ("flags %d\n", fl_val);
 
     if (int_set)
-       printf("int %d\n", int_val);
+       printf ("int %d\n", int_val);
 
     if (string_set)
-       printf("string %s\n", string_val);
+       printf ("string %s\n", string_val);
 
     if (pos1_set)
-       printf("positional arg 1 %s\n", pos_arg1);
+       printf ("positional arg 1 %s\n", pos_arg1);
 
     if (pos2_set)
-       printf("positional arg 2 %s\n", pos_arg2);
+       printf ("positional arg 2 %s\n", pos_arg2);
 
 
-    for ( ; opt_index < argc ; opt_index ++) {
-       printf("non parsed arg %d = %s\n", opt_index, argv[opt_index]);
+    for (; opt_index < argc; opt_index++) {
+       printf ("non parsed arg %d = %s\n", opt_index, argv[opt_index]);
     }
 
     return 0;
diff --git a/test/corpora/crypto/encrypted-signed.eml b/test/corpora/crypto/encrypted-signed.eml
new file mode 100644 (file)
index 0000000..0345e3e
--- /dev/null
@@ -0,0 +1,35 @@
+From: test_suite@notmuchmail.org
+To: test_suite@notmuchmail.org
+Subject: Lyrics
+Date: Wed 29 May 2019 06:09:22 PM EDT
+Message-ID: <encrypted-signed@crypto.notmuchmail.org>
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; boundary="=-=-=";
+       protocol="application/pgp-encrypted"
+
+--=-=-=
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--=-=-=
+Content-Type: application/octet-stream
+
+-----BEGIN PGP MESSAGE-----
+
+hIwDxE023q1UqxYBBAC9z781zV7QAInGMKHX6TKU5Xw/OkoWXahpDL88F6Ocm5R9
+7M9z2ocvlyrbgRhqE+nvFeGH/K7rVkBBT6TAcdIe/C8Qzbd3stPPcx1PlunGROj7
+H/WAcmDksK3HkXpHwmInUtzNw1pkhOoLy/sFSbPvtyg8GCUzXbafHAIIo0rB2tLB
+DwGWD3l4WdcyQWuYD9QJKuDIqdWo8E3TTcKkiOAt/6liwPNZ0jGzDeCuSTnWFj6Z
+AiXGeNtD3I1tCN/8T3NjEKOCQ+bdT5Y06dDaL61FpQ23eIuSUgksVxjnkEAb6iPe
+07gjzcyNuGP3WPI/0qu0wtZwpAQxvaNygDsQj/OjR5kn9luBd/VqodM3TWWS8miV
+m0z1tYbqYAQWW6TS7fXlsyXoOxTLW5MCfe3D36VSErL/NJItETklVKzNfKjMmRKx
+CI2ZUzugxPWSLQzOp5yl7iICk8e+vS9TkQw2j0nXAQYLYgmqZMhf4av5GlFv3tQu
+heO4XLT6NBDTHMFTDbgW42kE0N4MDPc29AqVFGImcTHvflF4Vp0qIbSJdIcHwKkU
+5LKqvicAa0lsIoJbsW3lHrzowyjov2vLH/VGd/wIX+MS3KT7cySdyp8HVMcwwyZu
+Y9nrTN/7G1FwKWlcGa4uJNcFFkYlcEymZj1EX2cyrdezPtX7K5vhwBYddptFD+Bn
+IVkghRut3UDeXe83F8OutWiZfK5EVYABq/aP3//hIbQl2o4Dkd3z9m+8LobrIV5s
+NXjAjU5WQOjRLoHBebG2HkMpFsWhXD/Fb/Bb58VOpdI=
+=x12v
+-----END PGP MESSAGE-----
+--=-=-=--
diff --git a/test/corpora/mangling/mixed-up.eml b/test/corpora/mangling/mixed-up.eml
new file mode 100644 (file)
index 0000000..a09f619
--- /dev/null
@@ -0,0 +1,33 @@
+From: test_suite@notmuchmail.org
+To: test_suite@notmuchmail.org
+Subject: Here is the password
+Date: Sat, 01 Jan 2000 12:00:00 +0000
+Message-ID: <mixed-up@mangling.notmuchmail.org>
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="=-=-="
+
+--=-=-=
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: quoted-printable
+
+
+--=-=-=
+Content-Type: application/pgp-encrypted
+Content-Transfer-Encoding: base64
+
+VmVyc2lvbjogMQ0K
+
+--=-=-=
+Content-Type: application/octet-stream
+Content-Transfer-Encoding: base64
+
+LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tDQoNCmhJd0R4RTAyM3ExVXF4WUJCQUNwNzBlN0tQ
+eTlPWWFoZUlya0x6bWhxMWxScW15NTFhTDFqQkwwSy9xTjdyZksNCkJaRUcxY1I4amVMalRGZFBL
+UExWS0pJODByN0ZnS0kweXd2V3ZsNlIxYUUxVHk1Qm5WWFQ5WHpDckVIN2ZxQ2wNClNLSzgyRXZv
+bFhUb2hBWkhVcmg2SzY2ZVFRVFRJQUMxbjdCMEE4aEVyemtnYU00K3NlTjNMbHZlelQ2VExOS00N
+CkFUcHFzRWJNMk1WckdndzBiM29Vc0dHQVBFdDJNbWpORVlzcmlLbnF3dDZkSkRaYy8vWHloamdN
+UWF5aUQ4ZGENCk4xZ1Qzb3FndS9nS0NwQlpEWXpIZjlPdFZpMlVubEZEV3k2cnJNWkxqV0RuSXY0
+dmU5UG4vcW9sd0hWanpkSjENClpmak5DNXQwejNYQURLR3JqTjl3dXRyNHFtN1NUVzFySEFYSFA2
+OFRRVHhJMHFnSktqUFhOS1dFdzZnPQ0KPXBKRzQNCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0N
+Cg==
+--=-=-=--
diff --git a/test/corpora/pkcs7/smime-onepart-signed.eml b/test/corpora/pkcs7/smime-onepart-signed.eml
new file mode 100644 (file)
index 0000000..070303b
--- /dev/null
@@ -0,0 +1,51 @@
+Received: from localhost (localhost [127.0.0.1]); Tue, 26 Nov 2019
+ 20:11:46 -0400 (UTC-04:00)
+Content-Transfer-Encoding: base64
+Content-Type: application/pkcs7-mime; name="smime.p7m";
+ smime-type="signed-data"
+MIME-Version: 1.0
+From: Alice Lovelace <alice@smime.example>
+To: Bob Babbage <bob@smime.example>
+Date: Tue, 26 Nov 2019 20:11:29 -0400
+Subject: The FooCorp contract
+Message-ID: <smime-onepart-signed@protected-headers.example>
+
+MIIHRQYJKoZIhvcNAQcCoIIHNjCCBzICAQExDTALBglghkgBZQMEAgEwggHJBgkq
+hkiG9w0BBwGgggG6BIIBtkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNl
+dD0idXMtYXNjaWkiDQpGcm9tOiBBbGljZSBMb3ZlbGFjZSA8YWxpY2VAc21pbWUu
+ZXhhbXBsZT4NClRvOiBCb2IgQmFiYmFnZSA8Ym9iQHNtaW1lLmV4YW1wbGU+DQpE
+YXRlOiBUdWUsIDI2IE5vdiAyMDE5IDIwOjExOjI5IC0wNDAwDQpTdWJqZWN0OiBU
+aGUgRm9vQ29ycCBjb250cmFjdA0KTWVzc2FnZS1JRDogPHNtaW1lLW9uZXBhcnQt
+c2lnbmVkQHByb3RlY3RlZC1oZWFkZXJzLmV4YW1wbGU+DQoNCkJvYiwgd2UgbmVl
+ZCB0byBjYW5jZWwgdGhpcyBjb250cmFjdC4NCg0KUGxlYXNlIHN0YXJ0IHRoZSBu
+ZWNlc3NhcnkgcHJvY2Vzc2VzIHRvIG1ha2UgdGhhdCBoYXBwZW4gdG9kYXkuDQoN
+ClRoYW5rcywgQWxpY2UNCi0tIA0KQWxpY2UgTG92ZWxhY2UNClByZXNpZGVudA0K
+T3BlblBHUCBFeGFtcGxlIENvcnANCqCCA3IwggNuMIICVqADAgECAhRngrRZc1JL
+wfRxRxlq8P0RiqpMCzANBgkqhkiG9w0BAQ0FADAtMSswKQYDVQQDEyJTYW1wbGUg
+TEFNUFMgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MCAXDTE5MTEyMDA2NTQxOFoYDzIw
+NTIwOTI3MDY1NDE4WjAZMRcwFQYDVQQDEw5BbGljZSBMb3ZlbGFjZTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMPurfll0bYkDPMkY1kNn2xXsAqHSGVF
++gWNNk3mbhF6BABhLJqDjei5aLXFE3Rq9/RRNivCMrTipF1XsbMIAKgQqr/GI1Q6
+yN8lfNsK5uU3d9kw5cOyEooGpOGUrvlKMD0LPGDt6MaiJj+KJ2TR73Wd4rfRIIJo
+FMmV9HZkOs+Tvcg8x6SzGhNq18X2HD10MD78eLXKm039obRD+z2JwWvGvrLbNBey
+O5A+aMxmCPXRoP1xrNZWBFgKB+WGYDRXW5CXXChthTwMBXFWf4aBpurKMZAyjK2E
+grQafn6h/DFddQz/NtT6Dr7UhJ2hfFFEW2rYbNsiqQAdllCb4FucWuECAwEAAaOB
+lzCBlDAMBgNVHRMBAf8EAjAAMB4GA1UdEQQXMBWBE2FsaWNlQHNtaW1lLmV4YW1w
+bGUwEwYDVR0lBAwwCgYIKwYBBQUHAwQwDwYDVR0PAQH/BAUDAwegADAdBgNVHQ4E
+FgQUrC5UWqT9VRivLuhmRDjRJdHXAHkwHwYDVR0jBBgwFoAUt1JNc8CIPbLDeloM
+85T394Cid9swDQYJKoZIhvcNAQENBQADggEBAHvqjhjPvKtVIVyleoutwa10jir3
+dooJcQIILM1AunjJ6yHpuuppkc0m3BhwnlOptTKb2EnvSIkTiMY037IBlHWW217Q
+cUpggEozgQm6Yb77aGptRovPi2XToEdpA8K//02I1jur1H1z8HqzVjMeHCqRaG3Z
+r4C2AngGSkb6D4yZkxBX8CjtHAsUon06UxYsGYRcVykgk3Qek9qxPScSX8yai1K7
+7xGcKUCLfIV/JMpv7ysPtXG7Jd62oNnp1T/3+KoP9JlLs5AiPLC13fjeYALPcHVG
+UXEwdIDp1AB/Zu0a6apHQqICncqRhEB4+hompiQHtlp3TqeAWXQbQUc437sxggHZ
+MIIB1QIBATBFMC0xKzApBgNVBAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0ZSBB
+dXRob3JpdHkCFGeCtFlzUkvB9HFHGWrw/RGKqkwLMAsGCWCGSAFlAwQCAaBpMBgG
+CSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE5MTEyNzAw
+MTEyOVowLwYJKoZIhvcNAQkEMSIEILsI9kL3zfZiVOEDjAUWrbjHjGMLoGUwEqYH
+pOA9XZ+QMA0GCSqGSIb3DQEBAQUABIIBAGDat8UYN9MShlKEw3hYVVUk6HKO6Xjp
+rdgCBKpoyoWJy0VJis0xHxaT2gn/+TPu8a5l6RslgeALjMyflzyzAmrqnknQQG8K
+bvbt/MwpU/TxnmxT+2oP9TVmAx/IQOq4pQ35uK7peSPck2CcTvZjHTeVBWcsLVEk
+hELoSD8XFRBo34qdinBzW0/sMlyK1XnlN7khKry1g7uaXcurVqptRA1rWOvCOt72
+aElKG/Q7OoVgHxbUpdzV3Hqe9/UeTRDUqCs++on2pLlA0TA0Pq8RQ0hDHD/p0t41
+1RAT1/RbnGQiVfRilMan+VGT4shokb1RoANy/1rOO9ZKlyWToYdRl9E=
diff --git a/test/corpora/protected-headers/protected-with-legacy-display.eml b/test/corpora/protected-headers/protected-with-legacy-display.eml
new file mode 100644 (file)
index 0000000..8c5dd25
--- /dev/null
@@ -0,0 +1,40 @@
+From: test_suite@notmuchmail.org
+To: test_suite@notmuchmail.org
+Subject: Subject Unavailable
+Date: Sat, 01 Jan 2000 12:00:00 +0000
+Message-Id: <protected-with-legacy-display@crypto.notmuchmail.org>
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; boundary="=-=-=";
+       protocol="application/pgp-encrypted"
+
+--=-=-=
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--=-=-=
+Content-Type: application/octet-stream
+
+-----BEGIN PGP MESSAGE-----
+
+hIwDxE023q1UqxYBBACkgwtKOAP+UlKYDzYkZY+gDuMNKnHjWIvv2Cdnovy40QzL
+5sbuib40y7orO+MqYMCWpoFtgBVsGiOUE3bZAg8n3Ji38/zVGwQveu6sh7SAy0Q9
+zFEvLhtajw17nPe+QH2UmIyfVikA57Mot13THq4i6C4ozVCyhyIltx+sNJkmw9Lp
+AdQd+cgCMRSMbi++eRwIi4zgxKrfAoGOmdMiVzBrh3yZqnbI0rCxJIKu7gEWuQLT
+7BuvN2bJUkPGLAUhUanFararVoD7WWOl67IlWFkyncES0PRskUf9coV68WZnYjsR
+Y3LdLnha1sdMwUNeBKQ44XBd2e7mXbDSp1cSjTDf9euwB4m7uQFTLwoQ8Of+LmQD
+KMHzjmucbkNAIpfAjcDusTA/oaaqUiEgGIgYYMDqG1CaaxdT55S7tMjW5yJryQmo
+pg65jrUMgEn5XHZ+KI2OsCmwGdoBYNau8p1a2hsiKhHJmLUeEAu34gFI3hylIOC0
+0KC40d0zTSb0s7SZuTrD6vYgiXG9aFktHvAWFH0ATCts7qyiRN7k5jt7yWfRntE2
+UCexTGE3TH7aju+IqDPC1XsaKF4T3CVhdr8WmKCa+0VOaw7xHRGYnzq9y91GcaCx
+8AcoZ3kYs+f2LIn+T667A0KKP4Z6OmLjCx3b1RvRUQYR9taruEMAQbIuAajiyTe9
+KfUrsUULZfInE50x+OneYvDhzoSgSJoHIK+18X/wo6YcyleJ9fZxCQ/vaXTDkAeF
+ve7TFcbIqmJ4MHygXILHUuDwp7P4t/tIL7SZwja70P3digjsgoNZY29VTnU8uyIb
+d6eOjgpeNVhRjDWxbUvhFD7i4rHCi/bbXFlW0cCXoiaVQBtYmiNysRoRZOv0h3TW
+q/+/UmqkaQFnF3zp5sr87y+ValItgPWmb9Ds0lyAoSvQx35zVh8DFfH04m7hmsb7
+gcvemlPTAnQWkIMC3c/bZWgt8tNcG7tQeUMWd9n4281y/hApbm90x2NLzEqvVcRq
+K0iIgVxbCHSKqGh4TtbIwpNhzSP+KHYkZ8h6+QUDRwGEV9QqZKg=
+=2O0V
+-----END PGP MESSAGE-----
+
+--=-=-=--
diff --git a/test/corpora/protected-headers/smime-enc+legacy-disp.eml b/test/corpora/protected-headers/smime-enc+legacy-disp.eml
new file mode 100644 (file)
index 0000000..6f5c941
--- /dev/null
@@ -0,0 +1,50 @@
+Received: from localhost (localhost [127.0.0.1]); Wed, 27 Nov 2019
+ 01:27:28 -0700 (UTC-07:00)
+MIME-Version: 1.0
+Content-Transfer-Encoding: base64
+Content-Type: application/pkcs7-mime; name="smime.p7m";
+ smime-type="enveloped-data"
+From: Alice Lovelace <alice@smime.example>
+To: Bob Babbage <bob@smime.example>
+Date: Wed, 27 Nov 2019 01:27:00 -0700
+Message-ID: <smime-enc+legacy-disp@protected-headers.example>
+Subject: ...
+
+MIIG5QYJKoZIhvcNAQcDoIIG1jCCBtICAQAxggLCMIIBXQIBADBFMC0xKzApBgNV
+BAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkCFCJT7jBtAgsf
+As31ycE+Ot95phvCMA0GCSqGSIb3DQEBAQUABIIBADEhlzhFzYj6tUAdsRCrSiLl
+d9cgKtlAesJ4cDY4szFWAbnwrCmEcFxjFDUOjbfQCYCG80Sxd+xntni73I7PI2rR
+QLjk3w9VhLwFRyzy7qyJi2CavjKTxysX9f36+FXA+THfVQRM5ypiyYJg91X51PNX
+hJj3DHrnxqKeSl/z1hdt9r+s6XAUCBSvL99BGnODWhNIZtPDzt8fMNcgarfw+D5F
+IZJb6+wX30tkztHkpHHKrrDPveyfnlS/p06Gi3ekrrhBtMQMRb9PA/E+ivDPktsm
+aKg0Oauw4oZSKW3f4ukYhbnndbbagNsnTfs/QFy/p+hhKTrfCd0h1N8mTzedVX0w
+ggFdAgEAMEUwLTErMCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1
+dGhvcml0eQIUZ4K0WXNSS8H0cUcZavD9EYqqTAswDQYJKoZIhvcNAQEBBQAEggEA
+FaK5QaPXJ133D2uybQt//oeDm6PkCAFW9YVOgjnLLz6FD54Dt2i1KCQu1Xlg9W3P
+1zJdYXOftDgilylNfmt/muEsvbRfFtMWUq0VGirHz//BWmY2cW/ocinFO514iviL
+MLE1umsXRNwVIVIk/uh7AmqXjPkRZgRgIMUbSbtmW4DDja+ZM0vmqFQ1iUIlApth
+FpjFfPDHHD8isLTbGi2iK6dEN3DIJFGbg5o3nK6yAhVZ7x3LfFNSNVDDSY5mPFG9
+Vm6uRgEE3Y5P6DbXXo6MHTgg0XY2f4y6MEWhOg37NT9aFAfzBBxJ1oSBWpOOfZnV
+K1DvAwPaemSRz9oWDcBM8DCCBAUGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIsFkN
+8DEx8muAggPgWGF2WsPq3/a9jUa5GA0YFPiINuETCGTNaEXiVxnT0h0CF+EhZ0T2
+HFCiZEM0dzO05zt9WdVvAREaCSh7ZWG9D9wJF9x+tqQbzMuJ2AdKuoOH73kClvkx
+pHxANLhkY7hzIqRb/eLG5D7Xh8iCDiFecXDh7EHqD/R+sfLN9aHKOcKyY36kesBQ
+R8aHZbbFnnD+oXSDNIPcntGG3BSGMxsWuOp+rpTKeIHWFIungDNKsLIy3kWleENw
+FVIcjUF6QhI1HYW6BeXuVq40GV2OOkmB24rYEW1Jg0hAtY+5rn2mRoyxvUC87bjQ
+hLu6xgPmhun9J324eM5aYVwkmVBnRW9hyxClZ7Sv0zlL7lGQ0VQG+zWHeJ+h/M2j
+mQpLgAUEGxxNCm5ASHuXPIN6pSvrOVplrT8kKLPpmMYEwmTX2/rBO4P8I8uNrqYD
+AyX8p0/l2ArczkWzGTz2luBahrD+cTZPApe5SeyXOxWBl1Lmb0G8o4twBeeBLiHP
+XwYvttx0JYG/hc/lmMpEemJqwj9uZ3wGD03dIhhDX2Oj4ek/7jT6yqJh8C1H+PqA
++HNfNXsFQDrRORoqJS8YVEiYRDQNyePy2ugzLTh88nPtJp92hY7bk9zl3AYaiVFH
++szlLoyzfM9D+geZemR8XfI2ijGnrWMlnyPah/zA6J6RwemhuiMklZGYG85hMU9H
+K4CFVM+m7xYxKpwFVnmkVZjzWInirJhehElhtCXpx/IFGxH9CPbCyEZV1WVStrl/
+0fWTGicMXez6hVQCadWCXy96/eLIXOrC54gSoIJX2TD6jdVEu1YptutyGI6KdQ2p
+yXwhs98Uj7DM3nmFeAcjjN3e8pPoX7aG8eP+MfmHlWN6jA44jMaJmIdp9J20g74J
+MdjvnHa/cGibW/RamPiFObN0F94A83vcpUfU/zZ8cFHi/3/lN6Rm9+3/giGRZa9E
+Y6e2/CEq1cUbPQ09fPwRJmjZCfDce71DKe+ZFGdYtFR7JwDEeZ6BB4Ff4rXctcWD
+PgUJqUGv/SXBcFn4cNUK9MYYqVu1ovd/T7FMf+i3c5MH6BRCvft/i5aeBR+A26Gk
+2awtBPYdHW6+AslrFjncBbtPDlU6vX9AWuC0k0MQYnNkTWS8gTvsriXJZ6Zu5iFE
+ExNuFz7YcnMKnguOn2ph5azzeMm83AYzWXzZPu3mdr5Siuu/Ke38oADKP+BZ08Za
+XVvKvvfnRPXO9kG9hgvEMRU9KOcxn82XoGPNZib+9SPa2zYx5P6HX1Bqe/cmKAen
+FKEiJLSTP2/pc6AWAICqJl978HaUHfMFiN7jEUppAifpAWqNcIGSW5w=
+
diff --git a/test/corpora/protected-headers/smime-multipart-signed.eml b/test/corpora/protected-headers/smime-multipart-signed.eml
new file mode 100644 (file)
index 0000000..f05d2d9
--- /dev/null
@@ -0,0 +1,68 @@
+Received: from localhost (localhost [127.0.0.1]); Tue, 26 Nov 2019
+ 20:03:17 -0400 (UTC-04:00)
+MIME-Version: 1.0
+Content-Type: multipart/signed; boundary="179";
+ protocol="application/pkcs7-signature"; micalg="sha-256"
+From: Alice Lovelace <alice@smime.example>
+To: Bob Babbage <bob@smime.example>
+Date: Tue, 26 Nov 2019 20:03:00 -0400
+Subject: The FooCorp contract
+Message-ID: <smime-multipart-signed@protected-headers.example>
+
+--179
+Content-Type: text/plain; charset="us-ascii"; protected-headers="v1"
+From: Alice Lovelace <alice@smime.example>
+To: Bob Babbage <bob@smime.example>
+Date: Tue, 26 Nov 2019 20:03:00 -0400
+Subject: The FooCorp contract
+Message-ID: <smime-multipart-signed@protected-headers.example>
+
+Bob, we need to cancel this contract.
+
+Please start the necessary processes to make that happen today.
+
+(this is the 'smime-multipart-signed' message)
+
+Thanks, Alice
+-- 
+Alice Lovelace
+President
+Example Corp
+
+--179
+Content-Transfer-Encoding: base64
+Content-Type: application/pkcs7-signature; name="smime.p7s"
+
+MIIFhQYJKoZIhvcNAQcCoIIFdjCCBXICAQExDTALBglghkgBZQMEAgEwCwYJKoZI
+hvcNAQcBoIIDcjCCA24wggJWoAMCAQICFGeCtFlzUkvB9HFHGWrw/RGKqkwLMA0G
+CSqGSIb3DQEBDQUAMC0xKzApBgNVBAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0
+ZSBBdXRob3JpdHkwIBcNMTkxMTIwMDY1NDE4WhgPMjA1MjA5MjcwNjU0MThaMBkx
+FzAVBgNVBAMTDkFsaWNlIExvdmVsYWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAw+6t+WXRtiQM8yRjWQ2fbFewCodIZUX6BY02TeZuEXoEAGEsmoON
+6LlotcUTdGr39FE2K8IytOKkXVexswgAqBCqv8YjVDrI3yV82wrm5Td32TDlw7IS
+igak4ZSu+UowPQs8YO3oxqImP4onZNHvdZ3it9EggmgUyZX0dmQ6z5O9yDzHpLMa
+E2rXxfYcPXQwPvx4tcqbTf2htEP7PYnBa8a+sts0F7I7kD5ozGYI9dGg/XGs1lYE
+WAoH5YZgNFdbkJdcKG2FPAwFcVZ/hoGm6soxkDKMrYSCtBp+fqH8MV11DP821PoO
+vtSEnaF8UURbaths2yKpAB2WUJvgW5xa4QIDAQABo4GXMIGUMAwGA1UdEwEB/wQC
+MAAwHgYDVR0RBBcwFYETYWxpY2VAc21pbWUuZXhhbXBsZTATBgNVHSUEDDAKBggr
+BgEFBQcDBDAPBgNVHQ8BAf8EBQMDB6AAMB0GA1UdDgQWBBSsLlRapP1VGK8u6GZE
+ONEl0dcAeTAfBgNVHSMEGDAWgBS3Uk1zwIg9ssN6WgzzlPf3gKJ32zANBgkqhkiG
+9w0BAQ0FAAOCAQEAe+qOGM+8q1UhXKV6i63BrXSOKvd2iglxAggszUC6eMnrIem6
+6mmRzSbcGHCeU6m1MpvYSe9IiROIxjTfsgGUdZbbXtBxSmCASjOBCbphvvtoam1G
+i8+LZdOgR2kDwr//TYjWO6vUfXPwerNWMx4cKpFobdmvgLYCeAZKRvoPjJmTEFfw
+KO0cCxSifTpTFiwZhFxXKSCTdB6T2rE9JxJfzJqLUrvvEZwpQIt8hX8kym/vKw+1
+cbsl3rag2enVP/f4qg/0mUuzkCI8sLXd+N5gAs9wdUZRcTB0gOnUAH9m7RrpqkdC
+ogKdypGEQHj6GiamJAe2WndOp4BZdBtBRzjfuzGCAdkwggHVAgEBMEUwLTErMCkG
+A1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1dGhvcml0eQIUZ4K0WXNS
+S8H0cUcZavD9EYqqTAswCwYJYIZIAWUDBAIBoGkwGAYJKoZIhvcNAQkDMQsGCSqG
+SIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkxMTI3MDAwMzAwWjAvBgkqhkiG9w0B
+CQQxIgQgGeoQw8WDmjB606EKGR5n1oMuV7Te1VjfA2oB2ebW390wDQYJKoZIhvcN
+AQEBBQAEggEABblYEWSnYyzL3jTS3AoPr93YKksIZr5q/b8Y5/1rMxdYxPm+iReO
+RHRgpbFQeiqZXzRXtMohfoIkh7RmdQoSV4OpwiUmNU+f0ZEAu8cMVJM6gdyUD+1D
+JwDNr+YNLV/1UUGhqx0FExOa/4O92KYBD4eRQw4KDWrkfh9dlSj0Bsl4thrZYGLz
+e7ut3FN5TBruZfmqMy50xZ9yUW91YyQUBLiIcuF185y5ZW/aQCxBKBbrNNGXLJbo
+8yKFJqSPiWZvwUmVQvfgL182hg823OJTtP4VImcUakTF0+k+BM//qqKXYrlX/tZn
+QzG+4ZH/XM1vgHl7ShjHS6TSOHz2ODqD6Q==
+
+--179--
+
diff --git a/test/corpora/protected-headers/smime-onepart-signed.eml b/test/corpora/protected-headers/smime-onepart-signed.eml
new file mode 100644 (file)
index 0000000..028e02c
--- /dev/null
@@ -0,0 +1,54 @@
+Received: from localhost (localhost [127.0.0.1]); Tue, 26 Nov 2019
+ 20:06:17 -0400 (UTC-04:00)
+Content-Transfer-Encoding: base64
+Content-Type: application/pkcs7-mime; name="smime.p7m";
+ smime-type="signed-data"
+MIME-Version: 1.0
+From: Alice Lovelace <alice@smime.example>
+To: Bob Babbage <bob@smime.example>
+Date: Tue, 26 Nov 2019 20:06:00 -0400
+Subject: The FooCorp contract
+Message-ID: <smime-onepart-signed@protected-headers.example>
+
+MIIHhQYJKoZIhvcNAQcCoIIHdjCCB3ICAQExDTALBglghkgBZQMEAgEwggIJBgkq
+hkiG9w0BBwGgggH6BIIB9kNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNl
+dD0idXMtYXNjaWkiOyBwcm90ZWN0ZWQtaGVhZGVycz0idjEiDQpGcm9tOiBBbGlj
+ZSBMb3ZlbGFjZSA8YWxpY2VAc21pbWUuZXhhbXBsZT4NClRvOiBCb2IgQmFiYmFn
+ZSA8Ym9iQHNtaW1lLmV4YW1wbGU+DQpEYXRlOiBUdWUsIDI2IE5vdiAyMDE5IDIw
+OjA2OjAwIC0wNDAwDQpTdWJqZWN0OiBUaGUgRm9vQ29ycCBjb250cmFjdA0KTWVz
+c2FnZS1JRDogPHNtaW1lLW9uZXBhcnQtc2lnbmVkQHByb3RlY3RlZC1oZWFkZXJz
+LmV4YW1wbGU+DQoNCkJvYiwgd2UgbmVlZCB0byBjYW5jZWwgdGhpcyBjb250cmFj
+dC4NCg0KUGxlYXNlIHN0YXJ0IHRoZSBuZWNlc3NhcnkgcHJvY2Vzc2VzIHRvIG1h
+a2UgdGhhdCBoYXBwZW4gdG9kYXkuDQoNCih0aGlzIGlzIHRoZSAnc21pbWUtb25l
+cGFydC1zaWduZWQnIG1lc3NhZ2UpDQoNClRoYW5rcywgQWxpY2UNCi0tIA0KQWxp
+Y2UgTG92ZWxhY2UNClByZXNpZGVudA0KRXhhbXBsZSBDb3JwDQqgggNyMIIDbjCC
+AlagAwIBAgIUZ4K0WXNSS8H0cUcZavD9EYqqTAswDQYJKoZIhvcNAQENBQAwLTEr
+MCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1dGhvcml0eTAgFw0x
+OTExMjAwNjU0MThaGA8yMDUyMDkyNzA2NTQxOFowGTEXMBUGA1UEAxMOQWxpY2Ug
+TG92ZWxhY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDD7q35ZdG2
+JAzzJGNZDZ9sV7AKh0hlRfoFjTZN5m4RegQAYSyag43ouWi1xRN0avf0UTYrwjK0
+4qRdV7GzCACoEKq/xiNUOsjfJXzbCublN3fZMOXDshKKBqThlK75SjA9Czxg7ejG
+oiY/iidk0e91neK30SCCaBTJlfR2ZDrPk73IPMeksxoTatfF9hw9dDA+/Hi1yptN
+/aG0Q/s9icFrxr6y2zQXsjuQPmjMZgj10aD9cazWVgRYCgflhmA0V1uQl1wobYU8
+DAVxVn+GgabqyjGQMoythIK0Gn5+ofwxXXUM/zbU+g6+1ISdoXxRRFtq2GzbIqkA
+HZZQm+BbnFrhAgMBAAGjgZcwgZQwDAYDVR0TAQH/BAIwADAeBgNVHREEFzAVgRNh
+bGljZUBzbWltZS5leGFtcGxlMBMGA1UdJQQMMAoGCCsGAQUFBwMEMA8GA1UdDwEB
+/wQFAwMHoAAwHQYDVR0OBBYEFKwuVFqk/VUYry7oZkQ40SXR1wB5MB8GA1UdIwQY
+MBaAFLdSTXPAiD2yw3paDPOU9/eAonfbMA0GCSqGSIb3DQEBDQUAA4IBAQB76o4Y
+z7yrVSFcpXqLrcGtdI4q93aKCXECCCzNQLp4yesh6brqaZHNJtwYcJ5TqbUym9hJ
+70iJE4jGNN+yAZR1ltte0HFKYIBKM4EJumG++2hqbUaLz4tl06BHaQPCv/9NiNY7
+q9R9c/B6s1YzHhwqkWht2a+AtgJ4BkpG+g+MmZMQV/Ao7RwLFKJ9OlMWLBmEXFcp
+IJN0HpPasT0nEl/MmotSu+8RnClAi3yFfyTKb+8rD7VxuyXetqDZ6dU/9/iqD/SZ
+S7OQIjywtd343mACz3B1RlFxMHSA6dQAf2btGumqR0KiAp3KkYRAePoaJqYkB7Za
+d06ngFl0G0FHON+7MYIB2TCCAdUCAQEwRTAtMSswKQYDVQQDEyJTYW1wbGUgTEFN
+UFMgQ2VydGlmaWNhdGUgQXV0aG9yaXR5AhRngrRZc1JLwfRxRxlq8P0RiqpMCzAL
+BglghkgBZQMEAgGgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3
+DQEJBTEPFw0xOTExMjcwMDA2MDBaMC8GCSqGSIb3DQEJBDEiBCAKDM98nuDl98sK
+i4SDvP2xlxr2SdV/xNVYs6SeGCBRuTANBgkqhkiG9w0BAQEFAASCAQAcryWkSIbG
+rrc/aDF1Z4KRnoRpr+fOutQSLV7k0Tgezt+X/kJCIiuLvjUxLrTux1yUWCKUPb6T
+KLYASPJpwDXrNzqmGs1pJmWHTZwUhbFVXt16FaQZkDSATtvhQU39Rsot2j1pP/UV
+J7+5FPQwNc4dt7MFW7jU4TBHo2VrzjZ2K8ioELPxsixOCAp3ytkhf1Umw6bC5M/u
+oWjsa6xzAl4fw5+pxZw0JdbrYn5kmPiekSsYy2/+yOwzrtIYtHW5dY7DoWWXDXtD
+cmCGHkO8qry+MnMy3PwvXiX0warQo1fnhXB5tlk2K9YdiDcOtnAshEBXAudnxlPK
+JGzeJVUfbfM0
+
diff --git a/test/corpora/protected-headers/smime-sign+enc+legacy-disp.eml b/test/corpora/protected-headers/smime-sign+enc+legacy-disp.eml
new file mode 100644 (file)
index 0000000..7ec3396
--- /dev/null
@@ -0,0 +1,102 @@
+Received: from localhost (localhost [127.0.0.1]); Wed, 27 Nov 2019
+ 01:24:28 -0700 (UTC-07:00)
+MIME-Version: 1.0
+Content-Transfer-Encoding: base64
+Content-Type: application/pkcs7-mime; name="smime.p7m";
+ smime-type="enveloped-data"
+From: Alice Lovelace <alice@smime.example>
+To: Bob Babbage <bob@smime.example>
+Date: Wed, 27 Nov 2019 01:24:00 -0700
+Message-ID: <smime-sign+enc+legacy-disp@protected-headers.example>
+Subject: ...
+
+MIIQjQYJKoZIhvcNAQcDoIIQfjCCEHoCAQAxggLCMIIBXQIBADBFMC0xKzApBgNV
+BAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkCFCJT7jBtAgsf
+As31ycE+Ot95phvCMA0GCSqGSIb3DQEBAQUABIIBAFbDR6j4ZB/Mo9BQygYItwFc
+P+4rO4d1ak51hc1DpSqyhiMcGahA3yxDRbZ4W1rbmC/s3d5+OWXKYgs1nNMQJ48F
+f45BtNTNslPZ1+NZVbkoVJO8Bxv1rjB8/qWuSUsroqzn9enS8DUBxxPL5aSWKQQN
+G2IaH9BUkMXLPUYA46GATly94IS4fZqwBtNNBP5eiIIPc9Ogjy+7At5GG7rVMN0M
+G5FL0oq52SYUe1167jp378JI+2dkA1q5+Cru/ZE2Rdw3DrMDAFO5GwC7fWKg4zPm
+IHZj92caVj1IyfTmGogT2o5tLMqn61BkptqxZwHDr3FI/aYo4vcHgmlKR/TdbHww
+ggFdAgEAMEUwLTErMCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1
+dGhvcml0eQIUZ4K0WXNSS8H0cUcZavD9EYqqTAswDQYJKoZIhvcNAQEBBQAEggEA
+hXeYVSUsT1EBZ/+AjwyEcnlM0kuFMaNvGlBMhAZzAsy012rrZTWbqWkcA3abgm/M
+CuZX7mQL0I79KZdmClGpLx6gQFjLemHaClQV0ZNdX4DxakWuME/kCMqbo4MZXStT
+a0MHlKUdoMt72Rz4YBzNQCL7ePaii5w6Nd2KD7yJAirLYUMJEjVweVaMI9y9LmbO
+vb0g0iuoUe0vp9B20LRcIX37nN5D1GG4tHLPjBD43gC8iqxZQf0uah2cWD1mAG5R
+oBgIDKXPy2eVbcMdSaOirDKYZ49WFe9Lad9q3mHHbFs6K6/yuBm/thMEdCJKZTHo
+jiPvYdYF8IJfEd368I+DujCCDa0GCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIsb1a
+JX/RU9aAgg2I0VXWfs5fc/Yad2qvawUVNX+LObjA6/+t9WxuV2emOeBYzQGjo7q+
+xaIXQwbbF1ej27efGhxUYDwBNS56c0uI0Ta7jxv5OFZhzQGLRzoFp0bbZ+uVC4eP
+bFHarRQiPzlg900XASO0RW+UOtqN5raZ3Ry2lKwXxuStZ0pX666Rz4c8PrmMb4/B
+aQYn6iKcT6fDU2TpSbWY9iph6kZczSeewK+pIj9nXfjDKXScs8D2Raezev2ciq/V
+ZRpRH8JxieimI2yeBmEzTCq11TDYycDfMHB6reGaiCGX//8kAWtskzRyNlV61unY
+ZKSNhVKLwKmCQh1V1Nd3oLApT41EeM2oWedUqNBYqB+XGCD4DUYdm1e+4h73d4dn
+JTkCdadxEn+9RRvZ4YMlw3mvT997Dy3rTXT29dj14TstZZf2O63pY0TpYy0HZy6Z
+Jug1qoe/vdcJ9SPOSfJE6VWCeVjxB+eGgheFLKqzK8Hs/Bm0/wDKpSFgEpOPnkJ4
+HJ2Uzgn1Emo6gBDJt+qn3s2UnowcMsTgellhKvgzVq59LTyRyWL5U8XMBsXT4qjm
+0LkRvDkOIjMQH7kqvWbpPlnWpLKo/VVoxifldEegWAqFVrP7f5Y+nNQttAYV79uk
+MXvR+5YFkvmQAerfllPqXBJdbB65ovikSVsy/kAboGpRG1oAZ4ODdwdGyiGIzyyc
+lE0x/8+gY8BqWzRtWX4GySKyZ50/+xkJe5ss0IXPCgq/09bdihsRn57v4V4SpdDO
+k3g/Dce+LzCRL8uTbUhrhZnjKSjRc3fFaD/BpLYjEDbnGF0ICslN3vb2xWUK1u4M
+uUH9r7lH/DCb0+TxIBtxOnP7W02bz8gGJAxEVEqk6pjxxOYqfS9/uBrrAY8P21Y9
+PFLdeHzEdYemq3il+4S7OU3uNUuAYijxmCRs7JQxZ9puA0iaTME9gK1yikzsLtVZ
+f+9osk2nYgfXvlL0AiYabd5cU2GNW33TkdDMNBsB7lx77J9erVLZpPKNo4vgHA7b
+owrDaYe0AgcZm79fvmR0RdtIZI91MouEhkdhaPiXmypmszjR/M0Ot3Y+oU/ks+yV
+Sle0S0h4V8wJRJYG/9VVurm8012ke2U3EGFlVnSv/IYtpssC+U4McRCmakKCrGU7
+OhL5JKBQN/DFTu4pV39IQlLLhg3wzA2FSkyIL5gEbS6sP9GTPo5LlNm2nYfJQX9A
+sHKSrfh68dvjSNExxi/8hdmFnnRwbAnUCI/WObGOkKdheOfdQ1AAHtLO7G65X1Cx
+RctbAJWa93M+iRUN6qnB+vIbPPnI1Mc7i6mPYzgtPrM9bYqEZz69pQtHcGTfxOrU
+tm+/h36CRzJBfXodBZbwQ9mZAzfkKdlArlZYIeBUw3ORQnQ7UlJgG8KsZpUhTxCc
+gvMoExtlvkXcYLRUBFfZWyOi6FePzQjuCK1w58OdweJgXprEAWSvyxhmVdg4jUpX
+MYKE0tZI9xwujyWjACO0myYqTdmsqyds+BgfBn96XiA9OFUH2C0/GAomhNs8uPSO
+T3Gt7Ld/FByxEVrtl9A37X6bAwZO01j5tHmdXFPmMVep0R8zsWtPn3RyGAjcgcq6
+50wJRwhvofdI7wilZ0KUBsAaPj3MK52cRyD19VXKNNwt2bLDV6gcWQ8+QEMusxfp
+1Dc9N9DSs+w3lGsFfpoeQ53/fXcVNJm6Bv89bH9anLGYdCdRGvZsvw+xRuglykqb
+xLtL2lB6wzlRFREJoWTzCVsdpIZ8znPmk1cB0wDlbMeu6sddHmv+6fpyuvQfQmdj
+D8WLRTuyxax94TmBlhJCFYxmO/y4Ivlx5C60GIRTkHpBYL/M0RjrbIszXEqcogzU
+bdwjLIhdEnpJ5vy0uXwhltce8BDpenmHE7y1kHvPBiUG3vB7AIXqhohFsJU3AYUj
+d1TvFKS2AsizUTLuq0Ydbnz3AxMfmnZe8qYkNu2zRygL2xTa58f/MwsHKakk3OmS
+9JFZLrkkVWZKXoARctuahYtWBAsykaWVNnB6zGcdX1MGVccl930Z6QWHyydtZpQc
+ivNdEGdGv9B0K7/ngNdVgD5Wd29AMMFnS8+55mLfRZDCjUmshSySaf6Ein4HD9Hr
+vk6dJvBPjnI5UjeUPjmH+wcZKIjLHW/aV/6/zoxzBh61rWFlr/daec+CFZE/+epr
+LRRYSmv8oY47fF4duDDhoexcvP/CH+A2Hr40OfciL4vKy3nuUDCNa59xO9JWv4NL
+n3MQypC9bcaVPkXa7TK3ECq1Jgv8gwfdh5/ovG5OdZA4uIcO+aqcskt/PD252c63
+0Znww3RXXf46KT4GdKO5A377ixkUMkznnCMvottmkPxjnhQjAsQg3bJeQk8EoX8f
+Pq0If4i7SRBSDtb2OH1pPmk0RVPtxlRDTVj3vS3Lci4xADFgC09n9nIvPO/55aau
+O6StbJtLmpubS5giuDH3uftwuyRiLqm3gtbSKPdoTk+dJhHXbbpBknL4XYTPxSsR
+IIaRds6w30vf7/IscyunMcquJlsO929SSa93UevKEIZbqbV9oGIqwkiUMdVZK09g
+rW0F//Ts4a5nYdEQth/fq3JnwqeHvvUfKdasK4TtrTnUBX7qZk/K3Y1fZwjKdd/8
+t9t1z7Kb2d9hWwtY7xP8liDluVFTsq8NM54ZC2218X5ViWz1yFmF2LXvRixsmYJv
+Tz8lUUnC2B/Etm1kkU4zrYK0/L77EikKVl+B7BXfEqx6ow41j7e1YZYaqmZ9mph+
+UieSdzqVYxhPwT25DrkU3r74iS28gKsbFhUaNklaFOO5iDWsKgBXT+wdZqlYQ6Fo
+oPe66025iJMwK8t+d53jEduHezHO2sTMAuf2hpdaZo7+rP/hRTReAR6CmI7nkWhP
+z5Kno9S+XhiSP+WTSpsoA4ubx0T94mL8NOVvSZA76TZ3ObVAP5VI/bwv6Grighor
+Kpsjt7dhSJRv+RHv95sAWBeW1Fgv8XOPSAZOmpJV2qc3x3Qmj0MXIR+7+3GlUr8+
+Dit3CE1hwtxgOW0tc8kuBTfQD+wNSa9r0eUyFscEBBljpEVbLjgjVdNv4Hc+fsbT
+g1JzZuUIDQZoEO2xLjxD+I7vLZKQa0J1JeZ7O+NqmSxsvSnwCWtJEWNMMxYNfwsP
+rdj1zPLqn3rzSBqhroNbaDGn86BTwIqfhr+AKbvevxS6bI8IbyKm9u3BFr9cuawx
+Sp1QM3NtqNStV67qR4A6U/ZyPUJdO1bxo8F3oRmJqOt7Jc93rFgkhBJ2+eMtrA75
+Om5tB9LBVSl5U5yLP0COO1QE5pqk5yuhJLT9Dyss8bWDRbSWKj83e4YXhPnq71Bm
+001czylLVNUlDc69Tf7FXjtIxh2yjvOT3zeLBPXOjU0it+gAma4vgrh8/mMXnNiq
+OLsVow8aKqm+Ofd6m13K5riDFgXgNI9lbvPKUSWlEqDMEqXk1oAqD4Nb5NTGSFpQ
+Q4G+cHAxJCu7vcXBaZnP8uMP5IAkdg5jIPvvMRwg/aqkl/KbL98oYZ5+1xrOMuKA
+LT1uCJ4MMB0lWsa1He4jPe8LneSupw7vAXlbo2VzcOI6oCSY5hV+cGQRY+LjW81q
+Cu5nLq8bwgnZMSlPmwr0YrKmvh8YKyGOrmTadxykC5IC+XbrLDsw2Jd9mLIjUQ/V
+4ibjeb+e0QGob22WOplCLnHGW/SnYei8KG1dxs/ahS+8vQdrI880ZJx2QJnrz0Ej
+ux6tKv4mvUkqYA5hlTFeT3PTr54yA+YLcCLMfBDx4ykPQnYUBj7ONHuNSUYt1CJy
+faZ7cWAbhgH+wlTFdVBVeW5D4FRbM8dMTPXyfC5ygwTJOiDu3vQKyyDkmiX7sEaC
+P1JN2V55uacyR8ZAG5+Mlc4ZMx83kAIZZXTCdqa1EX8yda31FI2rDHmvW/82bmjL
+pvI4Nnn9+zzJtDVCJ0B2VAZ3Edov5GzPikm3un4+mvyhUZpH4sbT0+VhPCsr1+zn
+bDJyNw4AswxaaJKh2+7wBiU6h+9TP/lI8SAJHtZL7zHBH8tD10ptksLRWDs9vYqp
+/3T86S2vxJL5DvLFJSAZrYOE3InS+keGmTMCdAl9I8zIworC/8uQp0N8ESebEVjA
+aHotBk59lj/OW4JZ3tQkcdQWkpnUfW/x9xE2wthacHlRzYDDsFByjEqkQr0MU8VF
+EGij9RCC97zyFrhv0xJm1C6wX0pcuEcuPTNBf38WyBTIfmVHHz/I5YKk5cdWG7Hq
+fmccV5GKrs2BseR683HM+/u50sq0km9UrqjgFR1DjfDoRKp0guP9PqkJAnwG2nv1
+hmNtXumzkF0otP5LDKLJ84MGP8Wnb006iEdD48Lra+clRAIIuLX4A0wRQjViDp7n
+OByI6ZcQd4DTMHnFPRvMkNMLYn13LghD6P9TTjQZ0KCOCwmc2TMCIhJlvzOYX6Cc
+wJZYLO1ltgfnHEuh8ijv0u3d/BUpsknYKBSJGUyMEZ9iUtbFPVfXBGSTi3gcWHtl
+IrM7wjswJwHWSvZKWUs+YWWJTwj0apG6ViGllwOAqR9C48uLKgFWPbMoTpolnp69
+eiij5ZHxB0i7SI80D+r65b+fqaFzVIJXVEI0zu/mIilbYBnGkhLI/Naw1m2e1qVJ
+mi1JBjXLAT3pEJDh8b3Lpgw=
+
diff --git a/test/corpora/protected-headers/smime-sign+enc.eml b/test/corpora/protected-headers/smime-sign+enc.eml
new file mode 100644 (file)
index 0000000..aa09d19
--- /dev/null
@@ -0,0 +1,95 @@
+Received: from localhost (localhost [127.0.0.1]); Wed, 27 Nov 2019
+ 01:15:28 -0700 (UTC-07:00)
+MIME-Version: 1.0
+Content-Transfer-Encoding: base64
+Content-Type: application/pkcs7-mime; name="smime.p7m";
+ smime-type="enveloped-data"
+From: Alice Lovelace <alice@smime.example>
+To: Bob Babbage <bob@smime.example>
+Date: Wed, 27 Nov 2019 01:15:00 -0700
+Message-ID: <smime-sign+enc@protected-headers.example>
+Subject: ...
+
+MIIPVQYJKoZIhvcNAQcDoIIPRjCCD0ICAQAxggLCMIIBXQIBADBFMC0xKzApBgNV
+BAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkCFCJT7jBtAgsf
+As31ycE+Ot95phvCMA0GCSqGSIb3DQEBAQUABIIBAKswTlBs+STeesZIYAf7Gqsj
+Za0rdUeDTSxt8RCa010EHb2lqKzHRwwPJkClLm6Glb09nYnQiFrEl6jbWTG3hMRD
+OSt9kyqeg+MxXr2g4LoXAT+8hg/qBoF//tX+bzxhx0gx8wjxBc3bvp4esCJro7Aq
+tx56BtVsIO6TA0NT0CaOcnMhIo09raR6JQX+DoPynKeXihny6TFDP7eopCgorCfR
+o59O3ZMvaui6Q9KixZy3Yae8fa0ZdJu3FahIZTPdBHzbmirLxcYgp+cbTpW+Yno2
+X5GJ8eq8Y0qcc/8r6Xd3REarUxO2YbO2D6cgDj+aNnnsoG1/9psaYl8W1MSc2/Qw
+ggFdAgEAMEUwLTErMCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1
+dGhvcml0eQIUZ4K0WXNSS8H0cUcZavD9EYqqTAswDQYJKoZIhvcNAQEBBQAEggEA
+RHhTarDqNLzXSaBokp2L3EwDv11KiGtMSMUQuPelNoC2nNYU1yzAF4jd+1UUo4Uu
+quiHg5Hn44a9MejrVmQRLd5IEJiZGD8m5JguuOjn0ooyA6EEWUpMn6hOAKlaCiXd
+kwTivKfhQFJe9Eb6TKqtvT2IEu3kXFfJKi+VyQw49+RXBmajDKJoHtumMJs8k4Ll
+kJah+wD+snwHg2LCiJeSVHmpf4RvSiIJSvk206IeTxN3JecNbBpKLtIoy/CjWEZv
+G3Pj/zkBbb+XhHbXo+Zk/e3aLToVG/cldx6Ti8zArOYNAzgt1G7dmJ3mnNPitEwN
+O4qIozhT2Qn8P95AEV5PsDCCDHUGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIUzdf
+vwulBs+AggxQMK121v6lO7W1r96RW0rsOHzsIvGyfyRTT1UuZRxVL09BQZstI5ss
+5Zv8BogoKA0mLaNBKM755joUbzF5f/jMYhkW3q0Het9/HRH0mOnCSnoT4i2yzNdi
+0tj8ixPT4sgPe9FOTkke9CzoJ967kj9D8u7Ik2goojttt3ViJkv3a1qrWDMiJRIJ
+gOTTA6ZaQep5L92vtCobhD+i7iaktEpmbYucXs8jjMmwyxCFxHXGD/fwDk3UDgeu
+8a5f66YepZdbLKB61A3rBwJMvQubuXEIEb04tG0Fgwx3Ao2NshN+XRk/y+uhQKdC
+5ZduTxk5sokA+H4nzVv0IUkAAI+8FwY5ZWFGlncKUM/wvrGHQq3R/utChFauOHxD
+7vZQLM91TcQzVWdHfJGPtp+ekjRlu9UqatQgc1ogObw3PGYlJc90Gl7AZHAsYncU
+jsMbdsweuFuYNHJ8lR5VMo6L4bCNMy+tQBOfYTF1el+i9S3r3SWdBP+uLiKgDQ52
+/o4shxoi+YOf9k8wRR0iDKqwzcJuABplpgA9qjsQNqBKF5t5p3l3ihH1mfh8FaPL
+ab0aDC7uunY5g44qXcG9YS+j5wUFuxgYyGkVcJq3xIit9YbEy8uPxJFz4g0vNC+r
+uUSsztbLyHkhv7vnCTAlmjgG9eDpW/tEC/85pLOV1HUooD05eRfkjU+1XsccX8DG
+iCax2C6W3cc1SC/d3a1+27OcgvPdDcb7zuL3v6qgqbN+7GDrcQHQRFMd2vd6+xGk
+NWZQMBZVHmdCcKGl9YaH0RgkGH5beTRKEV1wBafuVOwTEwl/FuZzD4oHrOaP3GLO
+cLxi44her/hNxtxDc2Lw0VQcxD8A55OkCt9+u9M5/YPj41FWyH6kdh86p958gzF5
+EpwCnQDe+s7OrwFVV00DEJhqtEcxRCSSW8dS4hVEhVxQJ56liJP+VZ+LTUJBelt4
+mfSpSqxeJnmyY0nmhEbZKVbK95a1WYMJCEpk2n1g/bQGqJKRryGwbEF9WqqHuvPo
+Bv/BfinoUL3Kd3g+hgSCR4mCg5EhEsCx21jEqEggzb2XMcA+knGUYxSWj322pZfW
+LDh50gkL3GQSmm9fOvjdK40GwZv8HUdLXuAQ/J19PafMaDkd4jzRi37VBqdDgLY3
+u6K+oFKhG4oqQYa/er+ZGAqqldTmu8HGCsjm6kGZvSAocJg0UnLPBNI0/iB0BYGf
+KJk302jy8kfAXGSiWrYDNbTuDzFMD0zsbHbM07AOOROGwKv5TxAF1EHHTxGb3IKI
+jRkVBL7QdRtDH03zlxv0lnFwiuCrzLrQdUuEG/0wt8RaNr+p8hAo0YEGbB9jmbax
+CSLLWeNbMOo8eIi3Mft4qmDXp3TEuHHru8kbvA36vQ8+dunSf2BcecyM6UAYBqaw
+SCcxQmEcyMuyjSLVerVfMl5lwlmM+qabxHq0hpJHnCR3Vl2qX3CiRWpVlNaBVyTf
+793bAm7DU7G+Tzt5gdgE4s41aZt8fFXyclhH1QLPNSnctxJjuW1gJJ0h51iCQJp2
+TgzDw35oqvBxbN3yqCFjScsQXPXYErGWkLrAkUurff4x/ZAizFkmjjdpyaIK9JBw
+QRyrYYQ8pJhXJe9BrP3OS6evFlsWZW1MaoQcOUMWsuVucE0e4AQRGlPixDjJWW7L
+I6AQ3KUW6ggzDJksaYHDiuEoBa7vcYoTar+/AhNjYMjkQX/3kptQryqy+xke0t8O
+EPQER0Wur2IpvM6YsvI/SoeFwxMb4Zm5AFvvibiCCmmoJc4A9E1tZ/sMstHyZ5iu
+tJqu1M5B0DIoFdB5pzbZYCkgN2n7EY23JS7E/ozOrzYuOIVUJVtB5awqmuSLmI+N
+R91g4FMEfLYC1HYKYlaknX2zmrx8+Z8MEJNM2K0q8wPBnm86OpGeJmlZhFwT2x0R
+eJpKcfLGroXYh2Gb6BxwIfKjOOTXCoIFP02JbTJ7clc/2ei0BN6JxywPkH4renaP
+SkuNBgbexfZGBhMTlR+CtKLEUmw5bxBTDwjjcvzWDPhy/VurLQxhOqYnbhZW21SV
+4qMrJ4uGXEhylnP0FD+HR4mB2epYcW3dFj4cGN3B2Y5NnOTw0Z7fi4S0BPdvYjP9
+LL5WZ6p90mII9wcunGCRnLUUYumRnIbhVHIBTTIRI5PUSVFfEuotrDZ9oZcwYkO7
+fQX21gJCzvJyp8ft01HX4Kc4mN/FMPgGcmq70N335yQ4mQ/eSvTNn7E+35ZGn9f8
+PI7QPJRhdUkBZCnwyv+OwK2VzySxnqNfPaZk168foGRd9eFCw80L4U+SuLDQH6ZT
+o++VKk4Ce2jx1khoig16wic0dVFwt4bmybNz4u/qdobYr5fs7dKPHHO02SBvAl60
+16foheiBtV2VA8mEBA1BhcNmKYegu+RGhmGfNDuZB8XdbPQ6M+N+ilEj/6rr+wgD
+gcmEyAGNwJkmWpbyrm9M4lDtzemv5N5V32ppGizEt6c0xlkiULllwGdWey3+YRez
+7b+Kl/uIpDuRbp5Tf43dyPsy/cx4DNm5kAB4CcyyVlXPaqXm0llEPYBmaMW3O+D2
+5v4Wj1qwIRO5qgI8FyVnX6sm/oucfg5l172edaCG8f42gIMNfQBgWVMsSG7Nt00x
+dJo/OGtACwnY47ohMFG0BejWueAksdnqVWCIto989iBHgegNx5jUCycB/YOm0xh0
+pfeNjA9PwZMUpjlqrjDFIan/UFYAZH5ISSV7G30oRKJ3TTEshShXP2K3cn7Fa9W+
+H/jyTEQGfCiTq7Xx5FrOIJBmKjylkF7oGlIBxJgKKRm0iD/sGNTaSJ6Pl8/K6dEz
+zsMwEFTawnWVq32Xn3d6/+FADZ9lGhC5WwVgaQHRb/9Ejt1mBdptmXjEj5w0YOib
+xFer54LrQgvBWEYRqDneh3bI53BudbTl7YitqULVGETe+k1T0NbcyElrr2Y/NKHk
+rPMarAfByookkJrDtVh3VrAm2ows7OwvKGyoNybjlyczjt7xosatZ1xkgb9mtR5i
+E2l9ajSR4SzQjHoboRyOCwl5ZgLV/+yp3jTkNcUkFDRtkVbGfascBIMe0ifUGfvP
+mJ9AQHZxdfm99KlQjCZzR8CBUvR+zsT43jr91CQKSSEvPMl6vVRV2thiWw3VGgP+
+c8i5zj6+zCnlEdSWiIeFwOJ9/ewKSdU9pGrA0OQtXbYQlDCKuGK1Vgy6jJCeglDH
+T6gVNy5ip593wWWfOVxVEWUygi6JCdS27b5+P/wlNjTrzpZ4yWDCpyogyrT1gf1/
+GgvdGuWWinKSLOyh1fJ1p9WoDWcqH98QhJXLV+X3OC+tmMofytmHgXN8jjVsWSRa
+VWrFUarMs2hZDWf6e6ncwvMC8QliiszrKXQNckxvBuh5hug9WKurVj4CIWnoqXFh
+OqlO+VbqZSj+TT5pCN//370vsIZIn5UbrpDmUP0rUvdTGz9iWQRUl6R2g2h286s6
+pAGHv9luXCoPJ5uPTwcbBSl/js6J+K5McyqRl4fucacfVFnMuDpET/tT1eAROP3F
+DOBKqV5YOO0rWMexzMLJUEQ/eGSwfp7wv8on7jeGxAexMqyWCrhRk9G2ZwiT4L7Q
+rX4NIDj6oujCCkeFUATs0pGKwEFGmpbEUfDOsioWoVYJZPsO9kAGq6bhbKACOkeZ
+v95ha/3CleYXGUUNtzLsCx+c9Zp/Wl+0PcT3ZSWhmRbXiIvz+ntHVe47PHxbvH6a
+ZG7YGc/9u3jTvJJyYtQO54uGET/eFWSxCUo5/VfsheOuLdXN7JnVi6ooF+c7WUZd
+61FwfDwNf8z0GWs3EotozrWyBgKS5VFP99vZM64nSqu9v5PSzmb0AY/Zc5KhVXVY
+zQqmO3keXq92Fejtgyd/O9ITZf5GkMQVU7+IT52JxFRQplkbTHJj4HRGtGHtIyPW
+Rmf9qSZz8QgVyAUKK1k+kLBJTHN3CWIB6S9hO42HWEFvLVl8wPWW5aLYTsVMGnMU
+aZ35M35odjrvY9B0INMpL53Hm7qH1w/h9QCv+xsFmanYsoylwbuKW2TcSnWB74C7
+Wy0NmCkaM+JweOgygffWicLGJ3jKWccykTUZtodz1ectNHh24puZICnvfzwjte+n
+eSQqJfHMsra6V8BcshpwmvPylHnkU+2KyhQ8430OR/qaXAYJ7EWRBEFe4EIpxzfL
+zQF0LwbhpAstpcjOlJfEHmQiWx8ASzE1LMSfZo148sXYEWsJL7t5tWs=
+
index 8423245ffefa86f08e5bd3e730f0cedba2c7289f..6eda0ebc913f9b3cd88193a5cc12dc65b8b18c33 100644 (file)
@@ -1,6 +1,6 @@
 (defun notmuch-test-address-cleaning-1 ()
   (notmuch-test-expect-equal (notmuch-show-clean-address "dme@dme.org")
-                       "dme@dme.org"))
+                            "dme@dme.org"))
 
 (defun notmuch-test-address-cleaning-2 ()
   (let* ((input '("foo@bar.com"
index 200ca7babd47eb767bc6ae3f6474c64fb389e516..8f4918ef60e114c3497f53709bbd85fed887a479 100644 (file)
@@ -1,3 +1,4 @@
+(require 'cl-lib)
 (require 'notmuch-mua)
 
 (defun attachment-check-test (&optional fn)
@@ -12,7 +13,8 @@ Return `t' if the message would be sent, otherwise `nil'"
       (condition-case nil
          ;; Force `y-or-n-p' to always return `nil', as if the user
          ;; pressed "n".
-         (letf (((symbol-function 'y-or-n-p) (lambda (&rest args) nil)))
+         (cl-letf (((symbol-function 'y-or-n-p)
+                    (lambda (&rest args) nil)))
            (notmuch-mua-attachment-check)
            t)
        ('error nil))
@@ -36,6 +38,12 @@ Return `t' if the message would be sent, otherwise `nil'"
           ;; fontification properties. For fontification to happen we need to
           ;; allow some time for redisplay.
           (sit-for 0.01)))
+    (t . (lambda ()
+          ;; "attach" is only mentioned in a forwarded message.
+          (insert "Hello\n")
+          (insert "<#mml type=message/rfc822 disposition=inline>\n")
+          (insert "X-Has-Attach:\n")
+          (insert "<#/mml>\n")))
 
     ;; These should not be okay:
     (nil . (lambda () (insert "Here is an attachment:\n")))
@@ -49,20 +57,25 @@ Return `t' if the message would be sent, otherwise `nil'"
             ;; looking at fontification properties. For fontification
             ;; to happen we need to allow some time for redisplay.
             (sit-for 0.01)))
+    (nil . (lambda ()
+          ;; "attachment" is mentioned before a forwarded message.
+          (insert "I also attach something.\n")
+          (insert "<#mml type=message/rfc822 disposition=inline>\n")
+          (insert "X-Has-Attach:\n")
+          (insert "<#/mml>\n")))
     ))
 
 (defun notmuch-test-attachment-warning-1 ()
   (let (output expected)
-    (mapcar (lambda (test)
-             (let* ((expect (car test))
-                    (body (cdr test))
-                    (result (attachment-check-test body)))
-               (push expect expected)
-               (push (if (eq result expect)
-                         result
-                       ;; In the case of a failure, include the test
-                       ;; details to make it simpler to debug.
-                       (format "%S <-- %S" result body))
-                     output)))
-           attachment-check-tests)
+    (dolist (test attachment-check-tests)
+      (let* ((expect (car test))
+            (body (cdr test))
+            (result (attachment-check-test body)))
+       (push expect expected)
+       (push (if (eq result expect)
+                 result
+               ;; In the case of a failure, include the test
+               ;; details to make it simpler to debug.
+               (format "%S <-- %S" result body))
+             output)))
     (notmuch-test-expect-equal output expected)))
index 1a06374d95a9f4ee4fd0b2e3a46d57ab45b5ed05..0bb58330bc1fbc8c8e79465b3ea4ea91843c7781 100644 (file)
@@ -31,8 +31,8 @@ Cheers,
 [ application/pgp-signature ]
 [ text/plain ]
 [ 4-line signature. Click/Enter to show. ]
- Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
-  Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
 Subject: Re: [notmuch] Working with Maildir storage?
 To: Mikhail Gusarov <dottedmag@dottedmag.net>
 Cc: notmuch@notmuchmail.org
@@ -57,9 +57,9 @@ It doesn't look like the patch is in git yet.
 [ application/pgp-signature ]
 [ text/plain ]
 [ 4-line signature. Click/Enter to show. ]
-   Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
-   Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
-    Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
 Subject: Re: [notmuch] Working with Maildir storage?
 To: Keith Packard <keithp@keithp.com>
 Cc: notmuch@notmuchmail.org
@@ -79,4 +79,4 @@ missing "#include <stdint.h>" (for the uint32_t on line 466).
 [ application/pgp-signature ]
 [ text/plain ]
 [ 4-line signature. Click/Enter to show. ]
- Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
+Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
index 3e1b07c60224777ad8d5f79c9d5a1427bbafffa8..fad9a71d9a14d99ad2a9f553b0ba134d21887b82 100644 (file)
@@ -2,13 +2,15 @@
 #include <cstdlib>
 #include <xapian.h>
 
-int main(int argc, char **argv) {
+int
+main (int argc, char **argv)
+{
 
     if (argc < 2) {
        std::cerr << "usage: ghost-report xapian-dir" << std::endl;
-       exit(1);
+       exit (1);
     }
 
-    Xapian::Database db(argv[1]);
-    std::cout << db.get_termfreq("Tghost") << std::endl;
+    Xapian::Database db (argv[1]);
+    std::cout << db.get_termfreq ("Tghost") << std::endl;
 }
index 33046e9a51785378d5f5003469941f1f428a2755..2e70f737fa1c2346ab59d628cd3f8645bf90789e 100644 (file)
@@ -26,13 +26,13 @@ xcode (void *ctx, enum direction dir, char *in, char **buf_p, size_t *size_p)
     if (dir == ENCODE)
        status = hex_encode (ctx, in, buf_p, size_p);
     else
-       if (inplace) {
-           status = hex_decode_inplace (in);
-           *buf_p = in;
-           *size_p = strlen(in);
-       } else {
-           status = hex_decode (ctx, in, buf_p, size_p);
-       }
+    if (inplace) {
+       status = hex_decode_inplace (in);
+       *buf_p = in;
+       *size_p = strlen (in);
+    } else {
+       status = hex_decode (ctx, in, buf_p, size_p);
+    }
 
     if (status == HEX_SUCCESS)
        fputs (*buf_p, stdout);
@@ -49,9 +49,9 @@ main (int argc, char **argv)
 
     notmuch_opt_desc_t options[] = {
        { .opt_keyword = &dir, .name = "direction", .keywords =
-         (notmuch_keyword_t []){ { "encode", ENCODE },
-                                 { "decode", DECODE },
-                                 { 0, 0 } } },
+             (notmuch_keyword_t []){ { "encode", ENCODE },
+                                     { "decode", DECODE },
+                                     { 0, 0 } } },
        { .opt_bool = &omit_newline, .name = "omit-newline" },
        { .opt_bool = &inplace, .name = "in-place" },
        { }
index fa80cac9d321993154e29bd5ea52ae8d43aa5809..78feaf7269fdf3c9c492e8ae15070260bd91a6e5 100644 (file)
@@ -8,7 +8,8 @@
 
 #include <xapian.h>
 
-int main(int argc, char **argv)
+int
+main (int argc, char **argv)
 {
     if (argc != 4) {
        fprintf (stderr, "Usage: %s mailpath version features\n", argv[0]);
index 45d03d67540a795540da7287e1212d460b6289af..34dbb8e0403feefc4be402178943d8576a8d9c01 100644 (file)
@@ -1,16 +1,17 @@
 #ifndef _NOTMUCH_TEST_H
 #define _NOTMUCH_TEST_H
 #include <stdio.h>
+#include <stdlib.h>
 #include <notmuch.h>
 
 inline static void
-expect0(int line, notmuch_status_t ret)
+expect0 (int line, notmuch_status_t ret)
 {
-   if (ret) {
+    if (ret) {
        fprintf (stderr, "line %d: %d\n", line, ret);
        exit (1);
-   }
+    }
 }
 
-#define EXPECT0(v)  expect0(__LINE__, v);
+#define EXPECT0(v)  expect0 (__LINE__, v);
 #endif
index 694761cf1742d1179d6e5e80df61ff2f856f306b..6aac89b9937cf65cefc1af6b372f4f77e05bd536 100644 (file)
 #define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
 
 static const char *parse_time_error_strings[] = {
-    [PARSE_TIME_OK]                    = "OK",
-    [PARSE_TIME_ERR]                   = "ERR",
-    [PARSE_TIME_ERR_LIB]               = "LIB",
-    [PARSE_TIME_ERR_ALREADYSET]                = "ALREADYSET",
-    [PARSE_TIME_ERR_FORMAT]            = "FORMAT",
-    [PARSE_TIME_ERR_DATEFORMAT]                = "DATEFORMAT",
-    [PARSE_TIME_ERR_TIMEFORMAT]                = "TIMEFORMAT",
-    [PARSE_TIME_ERR_INVALIDDATE]       = "INVALIDDATE",
-    [PARSE_TIME_ERR_INVALIDTIME]       = "INVALIDTIME",
-    [PARSE_TIME_ERR_KEYWORD]           = "KEYWORD",
+    [PARSE_TIME_OK] = "OK",
+    [PARSE_TIME_ERR] = "ERR",
+    [PARSE_TIME_ERR_LIB] = "LIB",
+    [PARSE_TIME_ERR_ALREADYSET] = "ALREADYSET",
+    [PARSE_TIME_ERR_FORMAT] = "FORMAT",
+    [PARSE_TIME_ERR_DATEFORMAT] = "DATEFORMAT",
+    [PARSE_TIME_ERR_TIMEFORMAT] = "TIMEFORMAT",
+    [PARSE_TIME_ERR_INVALIDDATE] = "INVALIDDATE",
+    [PARSE_TIME_ERR_INVALIDTIME] = "INVALIDTIME",
+    [PARSE_TIME_ERR_KEYWORD] = "KEYWORD",
 };
 
 static const char *
@@ -66,7 +66,7 @@ concat_args (int start, int end, char *argv[])
        len += strlen (argv[i]) + 1;
 
     p = malloc (len);
-    if (!p)
+    if (! p)
        return NULL;
 
     *p = 0;
@@ -111,10 +111,10 @@ struct {
     const char *operator;
     int round;
 } operators[] = {
-    { "==>",   PARSE_TIME_NO_ROUND },
-    { "==_>",  PARSE_TIME_ROUND_DOWN },
-    { "==^>",  PARSE_TIME_ROUND_UP_INCLUSIVE },
-    { "==^^>", PARSE_TIME_ROUND_UP },
+    { "==>",    PARSE_TIME_NO_ROUND },
+    { "==_>",   PARSE_TIME_ROUND_DOWN },
+    { "==^>",   PARSE_TIME_ROUND_UP_INCLUSIVE },
+    { "==^^>",  PARSE_TIME_ROUND_UP },
 };
 
 static const char *
@@ -145,7 +145,7 @@ get_operator (int round)
     const char *oper = NULL;
     unsigned int i;
 
-    for (i = 0; i < ARRAY_SIZE(operators); i++) {
+    for (i = 0; i < ARRAY_SIZE (operators); i++) {
        if (round == operators[i].round) {
            oper = operators[i].operator;
            break;
@@ -172,10 +172,10 @@ parse_stdin (FILE *infile, time_t *ref, int round, const char *format)
 
        /* trail is trailing whitespace and (optional) comment */
        trail = strchr (input, '#');
-       if (!trail)
+       if (! trail)
            trail = input + len;
 
-       while (trail > input && isspace ((unsigned char) *(trail-1)))
+       while (trail > input && isspace ((unsigned char) *(trail - 1)))
            trail--;
 
        if (trail == input) {
@@ -184,7 +184,7 @@ parse_stdin (FILE *infile, time_t *ref, int round, const char *format)
        }
 
        tmp = strdup (trail);
-       if (!tmp) {
+       if (! tmp) {
            fprintf (stderr, "strdup() failed\n");
            continue;
        }
@@ -201,8 +201,8 @@ parse_stdin (FILE *infile, time_t *ref, int round, const char *format)
        }
 
        r = parse_time_string (input, &t, ref, round);
-       if (!r) {
-           if (!localtime_r (&t, &tm)) {
+       if (! r) {
+           if (! localtime_r (&t, &tm)) {
                fprintf (stderr, "localtime_r() failed\n");
                free (trail);
                continue;
@@ -239,12 +239,12 @@ main (int argc, char *argv[])
     char buf[1024];
     const char *format = DEFAULT_FORMAT;
     struct option options[] = {
-       { "help",       no_argument,            NULL,   'h' },
-       { "^",          no_argument,            NULL,   'u' },
-       { "^^",         no_argument,            NULL,   'U' },
-       { "_",          no_argument,            NULL,   'd' },
-       { "format",     required_argument,      NULL,   'f' },
-       { "ref",        required_argument,      NULL,   'r' },
+       { "help",       no_argument,            NULL,   'h' },
+       { "^",          no_argument,            NULL,   'u' },
+       { "^^",         no_argument,            NULL,   'U' },
+       { "_",          no_argument,            NULL,   'd' },
+       { "format",     required_argument,      NULL,   'f' },
+       { "ref",        required_argument,      NULL,   'r' },
        { NULL, 0, NULL, 0 },
     };
 
@@ -287,7 +287,7 @@ main (int argc, char *argv[])
        return parse_stdin (stdin, nowp, round, format);
 
     argstr = concat_args (optind, argc, argv);
-    if (!argstr)
+    if (! argstr)
        return 1;
 
     r = parse_time_string (argstr, &result, nowp, round);
@@ -304,7 +304,7 @@ main (int argc, char *argv[])
        return r;
     }
 
-    if (!localtime_r (&result, &tm))
+    if (! localtime_r (&result, &tm))
        return 1;
 
     strftime (buf, sizeof (buf), format, &tm);
index 9272afda8bedab235293a870ed9de794943e6dc0..ff4132520982f5ddb9b82d476983488cef83c1f6 100644 (file)
@@ -50,7 +50,7 @@ typedef struct {
 
 /*
  *  Choose about half ascii as test characters, as ascii
- *  punctation and whitespace is the main cause of problems for
+ *  punctuation and whitespace is the main cause of problems for
  *  the (old) restore parser.
  *
  *  We then favour code points with 2 byte encodings. Note that
@@ -116,7 +116,7 @@ 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[] = {
-       { }
+    { }
 };
 
 const char *notmuch_requested_db_uuid = NULL;
index 92803c7745da33ef4b2a472098c870369ce3333c..6f276398840c4f9eb6c939ca3fa1d33ac7ad02ee 100644 (file)
@@ -2,6 +2,8 @@ test.crt: self signed certificated
     % gpgsm --gen-key # needs gpgsm 2.1
 
 key+cert.pem: cert + unencryped private
-    % gpsm --import test.crt
+    % gpgsm --import test.crt
     % gpgsm --export-private-key-p12 -out foo.p12  (no passphrase)
     % openssl pkcs12 -in ns.p12 -clcerts -nodes > key+cert.pem
+
+ca.crt: from https://tools.ietf.org/id/draft-dkg-lamps-samples-01.html#name-certificate-authority-certi
diff --git a/test/smime/bob.p12 b/test/smime/bob.p12
new file mode 100644 (file)
index 0000000..774c77d
--- /dev/null
@@ -0,0 +1,58 @@
+-----BEGIN PKCS12-----
+MIIKWAIBAzCCCh4GCSqGSIb3DQEHAaCCCg8EggoLMIIKBzCCBGcGCSqGSIb3DQEH
+BqCCBFgwggRUAgEAMIIETQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQICE8J
+3kMad9UCAggAgIIEIPvHjK0eRQrnowMUsz1z1x/IxslNvG6DjPZjNHCkNYYmiRsg
+Leu5nqKf4emWVvYpnlh+4Gql7pyJm3G3zSNhobPkW+P1Eh80tTBoUk7TIvvvmtrE
+YEc/nRR1p1MgjISq4Q/CM6ccCCw6YEiQcj/0mSS7gmHUegD5glcWbVuqAT8M/p1z
+98OP3z37G8ARRLNj1yyp0SVlt59Sx3WNbmYBqkQ96iukjMJvmjV7o6BFYUx46Llb
+tphhdRgKXbK2r1R0TUlvE659TUwlrpGlaFpaGj1kLdzVAnjh1ZWnWO2a2BSj0LzG
+qRyiLwqDFPLJLQEckfV+RPWiRrSewME8URNKdk6eewtHdhrehMo4ZJnOIum8qcSz
+giW61SSyZJsFvILpmMYghIxWmPd/8cNIHBrdFEa7z3QKh5jcJNTCxz6yO9f8F830
+d+WDK7DbGkUW4mVTGg/lEYnCFZDF6S1mr0hx+cew1FbKjLpxfQllIIrLf5d2BF8H
+0STpuylQDVVBFdTRHyeS6td5nulANgOProrRzy3aAKQmZ6iullKl+i2t/2TwfVP/
+gG+yszpOEf8U9txuvbiZ7j4XV158zdaaGiduDqMKLOvbdctwHAsR9ecx5C3NTRDl
+ZlttNoXN9zhT4CkWk1w4sFk2KUurjVraIcjWVT7yOreaaK+6N09M0tnLPDJDTrow
+8WwP/rZhA+t+CMrhqkFBxXsyo5VTM0jWJGO/NLpYXPhDPBsRq8rs1OCrUoVr34aR
+cpUTNhyXkvJUarWDHs88lg0ps0G9/1dXI1AbEsQQg8u+QT2ztGYrg2OQxQyi1Mo4
+u/FkAcEbtlYYLmJjj/S2qVRPJgBALVjw9k5hnYRdAXWVDCJ96PMn1SKORvlMxnZ7
+djlhaztOhTLsiDzywVDYWLvQElunWcAGeDZykWNytwcEagc0VjWKHMibc0JOZQ1T
+crGyOzTlt09xHj1NrItYefIwdtKuJfkAh03B5xI6rJ9ZbK9xidcVxyeRX0lEqdo9
+WHQrhHefAmeyo0TlfsN67kFDp5FLpwEtNaN0lyzpkl30aWZdtP5vkvtfmy5ugYIO
+bXoVa+tO6k5V/VfUFUKdaY7xAX7XRzUUg4jB0D0CuaX+YS+GL+5wuQwIY1y2ihBb
+CuCxlcP1lVEU4CVQba60VTudJtWyE7QpPhf+y81f1wRjwIihFvwzpUFWf8JVEppe
+v3Yot3OWGBmhEqLkC9LELth8o5gLfyYHaXTYNd9aRTiI+0ZC5U3O4wUwYLTG3exM
+rIDTzEMk/p4DYIHkNKVUiRJfGYdAwuRxf3IMcYWARTXlSzl1C3hWmZfvTPlKs1bB
+OHTHP/P+qdOFjxOh+fbyqXPJauBAhHvHgrp3iI6t834wJou26oWNihM7OnWuyQRt
+9DVxG4l+1VjtbQZfTDCCBZgGCSqGSIb3DQEHAaCCBYkEggWFMIIFgTCCBX0GCyqG
+SIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAjqo0x2p5SqLAICCAAE
+ggTIe6Ws+lu0CoNlCXGM2BEPV09wuRHTJe+KnesrmRbXPF9linG3d6G++tTkBHz/
+yr77/DV5aDYciV1pGAbLuX2lMwuqdxzJ4OBPBAjuX5H+IPRaTbxfHYYIwhG8oZzy
+aHyVhHr9j0h7lzW7xSTYJuBNEJ58L42dfzpNRw9dyRPmcuhZqW14Z3xyDm8yjHfB
+2p99y9/A4qSyJJSUM3O3nLdtIar3ktSTRAijgqq+s9wnsfozQRzWpYaqiRrdzwfO
+HqXk54l3/lMSyLpfPl9LW7er6JbGI4jEyQ3x8WijATM5h/lkZKejh/mOaWCvs6G6
+fGzV4P35EsToYbOk9GX4jl4SyDBt3iEHYm5teDUhJmTcR39lAQuAfxN6rOn/TkoO
+YLxtdD5DLiTfYZPCFyavLEsamr8A4p93torF6Rs7GsaHE6PmCcprzqx71KV0DZKv
+tMY86RoiWPKLFxZcYt1yz9/95c1SO1s4i1GvLpJTEgQxLM2OhfEwDNKd2rMJoq1I
+YIRPSP204dIVwwNdXN1vB2slhN2+/QMOqsEkWtTOpW2QoTGSze49hfmJGdu+91jd
+XZBBMJQfY4q066/eE4IOW7ZZId5uMYxDRnGdEQjJsxyW8YHWLRGQvBC8gMkdbj8e
+0wkXbe+jML7vG7t3hDhLEbj5sTquIMTWrTirPw4SxLCuGZAyJHFN3/nCaOSMFlCG
+wEZHrAozgQXPBYU7p+uIkJ4lDc2ZtW8NM8U15gKZLDFfAE6Vg0jAtfFMqvNnX630
+xfo1z4jBd7VXbBFrPzrmvlTnb1XxNFcPycowzW9tgtN4YnNroCq98VpMC914tdpJ
++C/PI0eJ7M2ir3ajN0RabSm02JO9Hdwoa5OgqLwPYDwiFyQvKFGKqAF8Ph6pSEiZ
+10OnH+DVgEY70A+Le+ZSDosMdrhZfHbCcIFitZJ3sYV/7Q118QckW3szcjmLHS5g
+M6Whl2HhjLsAfsmCnoRlIwjx4g0TiuZcb4hGysq8QjD3Z8qqFK28m6OMHbASQfWg
+U+Qg3vmEvVsnBxStFEIImS3QYQoaT0pk6zKUYsI/fOBnEgxsY0XwTfXzVw7hZDct
+yhNIQVWmfgVZwUw0wLoNu3A5hupjUwQzQr4TPnKkFPI8qHmRrJgP8EA0U0019y3W
+MlK0h/LAJEaUBS0goLJCJ8+1EWr6femjnyuU5hMizOm+3j0JexjWz5TQttioS7Q/
+vcxt5pA9yAWQdH9j72saKEoKmDi+kIPr4mimKJz99LhKp9A6Hj0f1P2V3As8JWyW
+ZKmJKW7qMMCFADlALolobqzA60j6Zeo5jiEj/j2lVlUPPz47WO+uKeb+rx+hgTUc
+Xrhq0+an5tvEXt/8wy3PJFqP+qqHGhOIuPLuhqPyzNowuXirIXsiWnI44/X48W91
+HPEoL3xaebQ6oyTP8dI4CCkkHgiLWL5mskjHMEXvcdR6k0ygmu8DGQCPfUweUZqZ
+wfkhD/jwbVpLR5Y3chpatW0cJ2bsAWdxwtuxF05+fVEePUsR0x+2/v/8eDEHKYwt
+aYlAhI48nyrKKVMmqvqcXnzmJlUaq05GnEcglFbv4MUExL7CxClls6QnVNiZFPrV
+ffVsYT2A300xrm4pan89n3nuavjJn7L1JJdmMXwwVQYJKoZIhvcNAQkUMUgeRgBH
+AG4AdQBQAEcAIABlAHgAcABvAHIAdABlAGQAIABjAGUAcgB0AGkAZgBpAGMAYQB0
+AGUAIAA0ADIAYgBiADIANAAwADYwIwYJKoZIhvcNAQkVMRYEFGaI9k+ZdE9/rxBZ
+4rSdH1BCuyQGMDEwITAJBgUrDgMCGgUABBRJfL4XyIHpXmjbziCGCbSAOK9jKgQI
+drOMeIgXcCYCAggA
+-----END PKCS12-----
diff --git a/test/smime/ca.crt b/test/smime/ca.crt
new file mode 100644 (file)
index 0000000..b33d087
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIULXcNXGI2bZp38sV7cF6VcQfnKDwwDQYJKoZIhvcNAQEN
+BQAwLTErMCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1dGhvcml0
+eTAgFw0xOTExMjAwNjU0MThaGA8yMDUyMDkyNzA2NTQxOFowLTErMCkGA1UEAxMi
+U2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAMUfZ8+NYSh6h36zQcXBo5B6ficAcBJ1f3aLxyN8
+QXB83XuP8aDRWQ9uJvJpQkWVH4zx96/E/zI0t0lDMYtZNqra16h+gxbHJgoq2pRw
+RCOiyYu/p2vzvvZ1dtFTMc/mIigjA/73kokui62j1EFy//fNVIihkVS3rAweq+fI
+8qJHSMhdc2aYa9wOP0eGe/HTiDYgT4L4f2HTGMGGwQgj1vub0gpR4YHmNqr0GyEA
+63mHUQUZpnmN1FEl+nVFA5Ntu4uF++qf/tkTji89/eXYBdKX2yUdTeTIKoCI65IL
+EXxezjTc8aFjf/8E0aWGVZR/DtCsjWOh/s/mV7n/YPyb4+ECAwEAAaNDMEEwDwYD
+VR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBS3Uk1zwIg9
+ssN6WgzzlPf3gKJ32zANBgkqhkiG9w0BAQ0FAAOCAQEALsU91Bmhc6EgCNr7inY2
+2gYPnosJ+kZ1eC0hvHIK9e0Tx74RmhTOe8M2C9YXQKehHpRaX+DLcjup6scoH/bT
+u0THbmzeOy29TTiFcyV9BK+SEKQWW4s98Fwdk9fPWcflHtYvqxjooAV3vHbt6Xmp
+KrKDz/jdg7t0ptI4zSqAf3wNppiJoswlOHBUnH2W1MIYkWQ4jYj5socblVlklHOr
+ykKUiEZAbjU+C1+0FhT4HgLjBB9R4H1H0JRKsggWiZBBJ6UpN0dTN4iD0mDVa0jy
+sJqqWnIViy/xaSDcNaWJmU3o2KmkMkdpinoJ5uLkAHQqXjFaujdU1PkufeA7v3uG
+Rw==
+-----END CERTIFICATE-----
index a7c1fe4fe822417d8bfb9819144627b522290a6e..1c89e9af81232add629fa49d3f818a096ae27d85 100644 (file)
@@ -275,7 +275,7 @@ main (int argc, char *argv[])
 
     do_smtp_to_file (peer_file, output);
 
- DONE:
 DONE:
     if (output)
        fclose (output);
     if (peer_file)
index 7454838bfd3125e2de08ad9f5fd9977363900e87..9d73a571a71e5f733ca09c0cca57725a4dda7de3 100644 (file)
@@ -3,7 +3,8 @@
 #include <xapian.h>
 #include <notmuch.h>
 
-int main (int argc, char** argv)
+int
+main (int argc, char **argv)
 {
     notmuch_database_t *notmuch;
     char *message = NULL;
@@ -22,7 +23,7 @@ int main (int argc, char** argv)
     try {
        (void) new Xapian::WritableDatabase (argv[2], Xapian::DB_OPEN);
     } catch (const Xapian::Error &error) {
-       printf("caught %s\n", error.get_msg().c_str());
+       printf ("caught %s\n", error.get_msg ().c_str ());
        return 0;
     }
 
diff --git a/test/test-databases/.gitignore b/test/test-databases/.gitignore
deleted file mode 100644 (file)
index 9452199..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/*.tar.xz
diff --git a/test/test-databases/Makefile b/test/test-databases/Makefile
deleted file mode 100644 (file)
index b250a8b..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# See Makefile.local for the list of files to be compiled in this
-# directory.
-all:
-       $(MAKE) -C ../.. all
-
-.DEFAULT:
-       $(MAKE) -C ../.. $@
diff --git a/test/test-databases/Makefile.local b/test/test-databases/Makefile.local
deleted file mode 100644 (file)
index 7aedff7..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- makefile -*-
-
-TEST_DATABASE_MIRROR=https://notmuchmail.org/releases/test-databases
-
-dir := test/test-databases
-
-test_databases := $(dir)/database-v1.tar.xz
-
-%.tar.xz:
-       @exec 1>&2 ;\
-       if command -v wget >/dev/null ;\
-       then set -x; wget -nv -O $@ ${TEST_DATABASE_MIRROR}/$(notdir $@) ;\
-       elif command -v curl >/dev/null ;\
-       then set -x; curl -L -s -o $@ ${TEST_DATABASE_MIRROR}/$(notdir $@) ;\
-       else echo Cannot fetch databases, no wget nor curl available; exit 1 ;\
-       fi
-
-download-test-databases: ${test_databases}
-
-DATACLEAN := $(DATACLEAN) ${test_databases}
diff --git a/test/test-databases/database-v1.tar.xz.sha256 b/test/test-databases/database-v1.tar.xz.sha256
deleted file mode 100644 (file)
index 2cc4f96..0000000
+++ /dev/null
@@ -1 +0,0 @@
-4299e051b10e1fa7b33ea2862790a09ebfe96859681804e5251e130f800e69d2  database-v1.tar.xz
index 9946010bfb9e37f1ff332493c3a55e3024233187..ec16c59c60fff25b0c2f0fafcb3a3f7ae520c851 100644 (file)
 ;;
 ;; Authors: Dmitry Kurochkin <dmitry.kurochkin@gmail.com>
 
-(require 'cl)  ;; This code is generally used uncompiled.
+(require 'cl-lib)
+
+;; Ensure that the dynamic variables that are defined by this library
+;; are defined by the time that we let-bind them.  This is needed
+;; because starting with Emacs 27 undeclared variables in evaluated
+;; interactive code (such as our tests) use lexical scope.
+(require 'smtpmail)
 
 ;; `read-file-name' by default uses `completing-read' function to read
 ;; user input.  It does not respect `standard-input' variable which we
 ;; `read' call.
 (setq read-file-name-function (lambda (&rest _) (read)))
 
-;; Work around a bug in emacs 23.1 and emacs 23.2 which prevents
-;; noninteractive (kill-emacs) from emacsclient.
-(if (and (= emacs-major-version 23) (< emacs-minor-version 3))
-  (defadvice kill-emacs (before disable-yes-or-no-p activate)
-    "Disable yes-or-no-p before executing kill-emacs"
-    (defun yes-or-no-p (prompt) t)))
-
-;; Emacs bug #2930:
-;;     23.0.92; `accept-process-output' and `sleep-for' do not run sentinels
-;; seems to be present in Emacs 23.1.
-;; Running `list-processes' after `accept-process-output' seems to work
-;; around this problem.
-(if (and (= emacs-major-version 23) (= emacs-minor-version 1))
-  (defadvice accept-process-output (after run-list-processes activate)
-    "run list-processes after executing accept-process-output"
-    (list-processes)))
-
 (defun notmuch-test-wait ()
   "Wait for process completion."
   (while (get-buffer-process (current-buffer))
@@ -75,7 +64,7 @@ invisible text."
   (let (str)
     (while (< start end)
       (let ((next-pos (next-char-property-change start end)))
-       (when (not (invisible-p start))
+       (unless (invisible-p start)
          (setq str (concat str (buffer-substring-no-properties
                                 start next-pos))))
        (setq start next-pos)))
@@ -91,35 +80,22 @@ invisible text."
 (defun orphan-watchdog-check (pid)
   "Periodically check that the process with id PID is still
 running, quit if it terminated."
-  (if (not (test-process-running pid))
-      (kill-emacs)))
+  (unless (test-process-running pid)
+    (kill-emacs)))
 
 (defun orphan-watchdog (pid)
   "Initiate orphan watchdog check."
   (run-at-time 60 60 'orphan-watchdog-check pid))
 
-(defun hook-counter (hook)
-  "Count how many times a hook is called.  Increments
-`hook'-counter variable value if it is bound, otherwise does
-nothing."
-  (let ((counter (intern (concat (symbol-name hook) "-counter"))))
-    (if (boundp counter)
-       (set counter (1+ (symbol-value counter))))))
-
-(defun add-hook-counter (hook)
-  "Add hook to count how many times `hook' is called."
-  (add-hook hook (apply-partially 'hook-counter hook)))
-
-(add-hook-counter 'notmuch-hello-mode-hook)
-(add-hook-counter 'notmuch-hello-refresh-hook)
-
-(defadvice notmuch-search-process-filter (around pessimal activate disable)
-  "Feed notmuch-search-process-filter one character at a time."
-  (let ((string (ad-get-arg 1)))
-    (loop for char across string
-         do (progn
-              (ad-set-arg 1 (char-to-string char))
-              ad-do-it))))
+(defvar notmuch-hello-mode-hook-counter -100
+  "Tests that care about this counter must let-bind it to 0.")
+(add-hook 'notmuch-hello-mode-hook
+         (lambda () (cl-incf notmuch-hello-mode-hook-counter)))
+
+(defvar notmuch-hello-refresh-hook-counter -100
+  "Tests that care about this counter must let-bind it to 0.")
+(add-hook 'notmuch-hello-refresh-hook
+         (lambda () (cl-incf notmuch-hello-refresh-hook-counter)))
 
 (defun notmuch-test-mark-links ()
   "Enclose links in the current buffer with << and >>."
@@ -152,23 +128,22 @@ nothing."
          "Output:\t" (prin1-to-string output) "\n"))
 
 (defun notmuch-test-expect-equal (output expected)
-  "Compare OUTPUT with EXPECTED. Report any discrepencies."
-  (if (equal output expected)
-      t
-    (cond
-     ((and (listp output)
-          (listp expected))
-      ;; Reporting the difference between two lists is done by
-      ;; reporting differing elements of OUTPUT and EXPECTED
-      ;; pairwise. This is expected to make analysis of failures
-      ;; simpler.
-      (apply #'concat (loop for o in output
-                           for e in expected
-                           if (not (equal o e))
-                           collect (notmuch-test-report-unexpected o e))))
-
-     (t
-      (notmuch-test-report-unexpected output expected)))))
+  "Compare OUTPUT with EXPECTED. Report any discrepancies."
+  (cond
+   ((equal output expected)
+    t)
+   ((and (listp output)
+        (listp expected))
+    ;; Reporting the difference between two lists is done by
+    ;; reporting differing elements of OUTPUT and EXPECTED
+    ;; pairwise. This is expected to make analysis of failures
+    ;; simpler.
+    (apply #'concat (cl-loop for o in output
+                            for e in expected
+                            if (not (equal o e))
+                            collect (notmuch-test-report-unexpected o e))))
+   (t
+    (notmuch-test-report-unexpected output expected))))
 
 (defun notmuch-post-command ()
   (run-hooks 'post-command-hook))
@@ -193,13 +168,3 @@ nothing."
 ;; environments
 
 (setq mm-text-html-renderer 'html2text)
-
-;; Set some variables for S/MIME tests.
-
-(setq smime-keys '(("" "test_suite.pem" nil)))
-
-(setq mml-smime-use 'openssl)
-
-;; all test keys are without passphrase
-(eval-after-load 'smime
-  '(defun smime-ask-passphrase (cache)  nil))
index 616cb674ccd9ee36df8026a87805e28a5730f890..c23a0d209186d7d04bb987614540a366181ac124 100644 (file)
@@ -95,6 +95,8 @@ TEST_EMACSCLIENT=${TEST_EMACSCLIENT:-emacsclient}
 TEST_GDB=${TEST_GDB:-gdb}
 TEST_CC=${TEST_CC:-cc}
 TEST_CFLAGS=${TEST_CFLAGS:-"-g -O0"}
+TEST_SHIM_CFLAGS=${TEST_SHIM_CFLAGS:-"-fpic -shared"}
+TEST_SHIM_LDFLAGS=${TEST_SHIM_LDFLAGS:-"-ldl"}
 
 # Protect ourselves from common misconfiguration to export
 # CDPATH into the environment
@@ -107,11 +109,10 @@ unset ALTERNATE_EDITOR
 
 add_gnupg_home ()
 {
-    local output
-    [ -d ${GNUPGHOME} ] && return
+    [ -e "${GNUPGHOME}/gpg.conf" ] && return
     _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
     at_exit_function _gnupg_exit
-    mkdir -m 0700 "$GNUPGHOME"
+    mkdir -p -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
@@ -127,6 +128,27 @@ add_gnupg_home ()
     printf '%s:6:\n' "$FINGERPRINT" | gpg --quiet --batch --no-tty --import-ownertrust
 }
 
+add_gpgsm_home ()
+{
+    local fpr
+    [ -e "$GNUPGHOME/gpgsm.conf" ] && return
+    _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
+    at_exit_function _gnupg_exit
+    mkdir -p -m 0700 "$GNUPGHOME"
+    openssl pkcs12 -export -passout pass: -inkey "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" \
+       < "$NOTMUCH_SRCDIR/test/smime/test.crt" | \
+       gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \
+             --disable-dirmngr --import  >"$GNUPGHOME"/import.log 2>&1 3<<<''
+    fpr=$(gpgsm --batch --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p')
+    echo "$fpr S relax" >> "$GNUPGHOME/trustlist.txt"
+    gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt
+    echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$GNUPGHOME/trustlist.txt"
+    printf '%s::1\n' include-certs disable-crl-checks | gpgconf --output /dev/null --change-options gpgsm
+    gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \
+             --disable-dirmngr --import "$NOTMUCH_SRCDIR/test/smime/bob.p12" >>"$GNUPGHOME"/import.log 2>&1 3<<<''
+    test_debug "cat $GNUPGHOME/import.log"
+}
+
 # Each test should start with something like this, after copyright notices:
 #
 # test_description='Description of this test...
@@ -322,13 +344,14 @@ trap 'trap_signal' HUP INT TERM
 # to the message and encrypting/signing.
 emacs_deliver_message ()
 {
-    local subject="$1"
-    local body="$2"
+    local subject body smtp_dummy_pid smtp_dummy_port
+    subject="$1"
+    body="$2"
     shift 2
     # before we can send a message, we have to prepare the FCC maildir
     mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp}
     # eval'ing smtp-dummy --background will set smtp_dummy_pid and -_port
-    local smtp_dummy_pid= smtp_dummy_port=
+    smtp_dummy_pid= smtp_dummy_port=
     eval `$TEST_DIRECTORY/smtp-dummy --background sent_message`
     test -n "$smtp_dummy_pid" || return 1
     test -n "$smtp_dummy_port" || return 1
@@ -368,20 +391,21 @@ emacs_deliver_message ()
 # new" after message delivery
 emacs_fcc_message ()
 {
-    local nmn_args=''
+    local nmn_args subject body
+    nmn_args=''
     while [[ "$1" =~ ^-- ]]; do
-        nmn_args="$nmn_args $1"
-        shift
+       nmn_args="$nmn_args $1"
+       shift
     done
-    local subject="$1"
-    local body="$2"
+    subject="$1"
+    body="$2"
     shift 2
     # before we can send a message, we have to prepare the FCC maildir
     mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp}
 
     test_emacs \
        "(let ((message-send-mail-function (lambda () t))
-               (mail-host-address \"example.com\"))
+              (mail-host-address \"example.com\"))
           (notmuch-mua-mail)
           (message-goto-to)
           (insert \"test_suite@notmuchmail.org\nDate: 01 Jan 2000 12:00:00 -0000\")
@@ -390,7 +414,9 @@ emacs_fcc_message ()
           (message-goto-body)
           (insert \"${body}\")
           $*
-          (notmuch-mua-send-and-exit))" || return 1
+          (let ((mml-secure-smime-sign-with-sender t)
+                (mml-secure-openpgp-sign-with-sender t))
+            (notmuch-mua-send-and-exit)))" || return 1
     notmuch new $nmn_args >/dev/null
 }
 
@@ -404,6 +430,7 @@ emacs_fcc_message ()
 # number of messages.
 add_email_corpus ()
 {
+    local corpus
     corpus=${1:-default}
 
     rm -rf ${MAIL_DIR}
@@ -434,6 +461,7 @@ test_begin_subtest ()
 # name.
 test_expect_equal ()
 {
+       local output expected testname
        exec 1>&6 2>&7          # Restore stdout and stderr
        if [ -z "$inside_subtest" ]; then
                error "bug in the test script: test_expect_equal without test_begin_subtest"
@@ -460,6 +488,7 @@ test_expect_equal ()
 # Like test_expect_equal, but takes two filenames.
 test_expect_equal_file ()
 {
+       local file1 file2 testname basename1 basename2
        exec 1>&6 2>&7          # Restore stdout and stderr
        if [ -z "$inside_subtest" ]; then
                error "bug in the test script: test_expect_equal_file without test_begin_subtest"
@@ -489,29 +518,37 @@ test_expect_equal_file ()
 # canonicalized before diff'ing.  If an argument cannot be parsed, it
 # is used unchanged so that there's something to diff against.
 test_expect_equal_json () {
+    local script output expected
     # 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.
-    local script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)'
+    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")
+       || echo "$1")
     expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \
-        || echo "$2")
+       || echo "$2")
     shift 2
     test_expect_equal "$output" "$expected" "$@"
 }
 
+# Ensure that the argument is valid JSON data.
+test_valid_json () {
+    PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "import sys, json; json.load(sys.stdin)" <<<"$1"
+    test_expect_equal "$?" 0
+}
+
 # Sort the top-level list of JSON data from stdin.
 test_sort_json () {
     PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c \
-        "import sys, json; json.dump(sorted(json.load(sys.stdin)),sys.stdout)"
+       "import sys, json; json.dump(sorted(json.load(sys.stdin)),sys.stdout)"
 }
 
 # test for json objects:
 # read the source of test/json_check_nodes.py (or the output when
 # invoking it without arguments) for an explanation of the syntax.
 test_json_nodes () {
-        exec 1>&6 2>&7         # Restore stdout and stderr
+       local output
+       exec 1>&6 2>&7          # Restore stdout and stderr
        if [ -z "$inside_subtest" ]; then
                error "bug in the test script: test_json_eval without test_begin_subtest"
        fi
@@ -521,7 +558,7 @@ test_json_nodes () {
 
        if ! test_skip "$test_subtest_name"
        then
-           output=$(PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON "$TEST_DIRECTORY"/json_check_nodes.py "$@")
+           output=$(PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -B "$NOTMUCH_SRCDIR"/test/json_check_nodes.py "$@")
                if [ "$?" = 0 ]
                then
                        test_ok_
@@ -532,6 +569,7 @@ test_json_nodes () {
 }
 
 test_emacs_expect_t () {
+       local result
        test "$#" = 1 ||
        error "bug in the test script: not 1 parameter to test_emacs_expect_t"
        if [ -z "$inside_subtest" ]; then
@@ -583,6 +621,11 @@ print(msg.as_string(False))
 ' "$@"
 }
 
+notmuch_exception_sanitize ()
+{
+    perl -pe 's/(A Xapian exception occurred at .*[.]cc?):([0-9]*)/\1:XXX/'
+}
+
 notmuch_search_sanitize ()
 {
     perl -pe 's/("?thread"?: ?)("?)................("?)/\1\2XXX\3/'
@@ -619,17 +662,18 @@ notmuch_json_show_sanitize ()
        -e 's|"filename": "signature.asc",||g' \
        -e 's|"filename": \["/[^"]*"\],|"filename": \["YYYYY"\],|g' \
        -e 's|"timestamp": 97.......|"timestamp": 42|g' \
-        -e 's|"content-length": [1-9][0-9]*|"content-length": "NONZERO"|g'
+       -e 's|"content-length": [1-9][0-9]*|"content-length": "NONZERO"|g'
 }
 
 notmuch_emacs_error_sanitize ()
 {
-    local command=$1
+    local command
+    command=$1
     shift
     for file in "$@"; do
        echo "=== $file ==="
        cat "$file"
-    done | sed  \
+    done | sed \
        -e 's/^\[.*\]$/[XXX]/' \
        -e "s|^\(command: \)\{0,1\}/.*/$command|\1YYY/$command|"
 }
@@ -688,6 +732,7 @@ declare -A test_subtest_missing_external_prereq_
 
 # declare prerequisite for the given external binary
 test_declare_external_prereq () {
+       local binary
        binary="$1"
        test "$#" = 2 && name=$2 || name="$binary(1)"
 
@@ -705,6 +750,7 @@ $binary () {
 # called indirectly (e.g. from emacs).
 # Returns success if dependency is available, failure otherwise.
 test_require_external_prereq () {
+       local binary
        binary="$1"
        if [[ ${test_missing_external_prereq_["${binary}"]} == t ]]; then
                # dependency is missing, call the replacement function to note it
@@ -883,8 +929,8 @@ test_expect_code () {
 # but is a prefix that can be used in the test script, like:
 #
 #      test_expect_success 'complain and die' '
-#           do something &&
-#           do something else &&
+#          do something &&
+#          do something else &&
 #          test_must_fail git checkout ../outerspace
 #      '
 #
@@ -974,15 +1020,15 @@ export NOTMUCH_CONFIG=$NOTMUCH_CONFIG
 
 # Here's what we are using here:
 #
-# --quick              Use minimal customization. This implies --no-init-file,
-#                     --no-site-file and (emacs 24) --no-site-lisp
+# --quick              Use minimal customization. This implies --no-init-file,
+#                      --no-site-file and (emacs 24) --no-site-lisp
 #
 # --directory          Ensure that the local elisp sources are found
 #
 # --load               Force loading of notmuch.el and test-lib.el
 
 exec ${TEST_EMACS} --quick \
-       --directory "$NOTMUCH_SRCDIR/emacs" --load notmuch.el \
+       --directory "$NOTMUCH_BUILDDIR/emacs" --load notmuch.el \
        --directory "$NOTMUCH_SRCDIR/test" --load test-lib.el \
        "\$@"
 EOF
@@ -1042,10 +1088,11 @@ test_python() {
 }
 
 test_ruby() {
-    MAIL_DIR=$MAIL_DIR $NOTMUCH_RUBY -I $NOTMUCH_SRCDIR/bindings/ruby> OUTPUT
+    MAIL_DIR=$MAIL_DIR $NOTMUCH_RUBY -I "$NOTMUCH_BUILDDIR/bindings/ruby"> OUTPUT
 }
 
 test_C () {
+    local exec_file test_file
     exec_file="test${test_count}"
     test_file="${exec_file}.c"
     cat > ${test_file}
@@ -1053,9 +1100,25 @@ test_C () {
     echo "== stdout ==" > OUTPUT.stdout
     echo "== stderr ==" > OUTPUT.stderr
     ./${exec_file} "$@" 1>>OUTPUT.stdout 2>>OUTPUT.stderr
-    notmuch_dir_sanitize OUTPUT.stdout OUTPUT.stderr > OUTPUT
+    notmuch_dir_sanitize OUTPUT.stdout OUTPUT.stderr | notmuch_exception_sanitize > OUTPUT
 }
 
+make_shim () {
+    local base_name test_file shim_file
+    base_name="$1"
+    test_file="${base_name}.c"
+    shim_file="${base_name}.so"
+    cat > ${test_file}
+    ${TEST_CC} ${TEST_CFLAGS} ${TEST_SHIM_CFLAGS} -I${NOTMUCH_SRCDIR}/test -I${NOTMUCH_SRCDIR}/lib -o ${shim_file} ${test_file} ${TEST_SHIM_LDFLAGS}
+}
+
+notmuch_with_shim () {
+    local base_name shim_file
+    base_name="$1"
+    shift
+    shim_file="${base_name}.so"
+    LD_PRELOAD=./${shim_file}${LD_PRELOAD:+:$LD_PRELOAD} notmuch-shared "$@"
+}
 
 # Creates a script that counts how much time it is executed and calls
 # notmuch.  $notmuch_counter_command is set to the path to the
@@ -1198,17 +1261,6 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON
 ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
 rm -f y
 
-# convert variable from configure to more convenient form
-case "$NOTMUCH_DEFAULT_XAPIAN_BACKEND" in
-    glass)
-       db_ending=glass
-    ;;
-    chert)
-       db_ending=DB
-    ;;
-    *)
-       error "Unknown Xapian backend $NOTMUCH_DEFAULT_XAPIAN_BACKEND"
-esac
 # declare prerequisites for external binaries used in tests
 test_declare_external_prereq dtach
 test_declare_external_prereq emacs
index 46f8af3a1c8e321207c14748548056e6f5509baf..7ef029a566d2067c357d72cb5f39da1537de7583 100644 (file)
@@ -1,4 +1,4 @@
-# -*- makefile -*-
+# -*- makefile-gmake -*-
 
 dir := util
 extra_cflags += -I$(srcdir)/$(dir)
@@ -6,6 +6,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)/crypto.c \
+               $(dir)/repair.c \
                $(dir)/unicode-util.c
 
 libnotmuch_util_modules := $(libnotmuch_util_c_srcs:.c=.o)
index 9e185e036573c90a5be0ee983d338d8fbf994c2f..c09f467b350b5d453e799d5b7941463544b411a0 100644 (file)
 
 #include "crypto.h"
 #include <strings.h>
+#include "error_util.h"
 #define unused(x) x __attribute__ ((unused))
 
 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
 
-void _notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto))
+void
+_notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto))
 {
 }
 
@@ -32,11 +34,12 @@ GMimeObject *
 _notmuch_crypto_decrypt (bool *attempted,
                         notmuch_decryption_policy_t decrypt,
                         notmuch_message_t *message,
-                        GMimeMultipartEncrypted *part,
+                        GMimeObject *part,
                         GMimeDecryptResult **decrypt_result,
                         GError **err)
 {
     GMimeObject *ret = NULL;
+
     if (decrypt == NOTMUCH_DECRYPT_FALSE)
        return NULL;
 
@@ -52,10 +55,21 @@ _notmuch_crypto_decrypt (bool *attempted,
            }
            if (attempted)
                *attempted = true;
-           ret = g_mime_multipart_encrypted_decrypt (part,
-                                                     GMIME_DECRYPT_NONE,
-                                                     notmuch_message_properties_value (list),
-                                                     decrypt_result, err);
+           if (GMIME_IS_MULTIPART_ENCRYPTED (part)) {
+               ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part),
+                                                         GMIME_DECRYPT_NONE,
+                                                         notmuch_message_properties_value (list),
+                                                         decrypt_result, err);
+           } else if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) {
+               GMimeApplicationPkcs7Mime *pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part);
+               GMimeSecureMimeType type = g_mime_application_pkcs7_mime_get_smime_type (pkcs7);
+               if (type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) {
+                   ret = g_mime_application_pkcs7_mime_decrypt (pkcs7,
+                                                                GMIME_DECRYPT_NONE,
+                                                                notmuch_message_properties_value (list),
+                                                                decrypt_result, err);
+               }
+           }
            if (ret)
                break;
        }
@@ -78,15 +92,24 @@ _notmuch_crypto_decrypt (bool *attempted,
     GMimeDecryptFlags flags = GMIME_DECRYPT_NONE;
     if (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result)
        flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY;
-    ret = g_mime_multipart_encrypted_decrypt(part, flags, NULL,
-                                            decrypt_result, err);
+    if (GMIME_IS_MULTIPART_ENCRYPTED (part)) {
+       ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part), flags, NULL,
+                                                 decrypt_result, err);
+    } else if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) {
+       GMimeApplicationPkcs7Mime *pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part);
+       GMimeSecureMimeType p7type = g_mime_application_pkcs7_mime_get_smime_type (pkcs7);
+       if (p7type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) {
+           ret = g_mime_application_pkcs7_mime_decrypt (pkcs7, flags, NULL,
+                                                        decrypt_result, err);
+       }
+    }
     return ret;
 }
 
 static int
 _notmuch_message_crypto_destructor (_notmuch_message_crypto_t *msg_crypto)
 {
-    if (!msg_crypto)
+    if (! msg_crypto)
        return 0;
     if (msg_crypto->sig_list)
        g_object_unref (msg_crypto->sig_list);
@@ -99,6 +122,7 @@ _notmuch_message_crypto_t *
 _notmuch_message_crypto_new (void *ctx)
 {
     _notmuch_message_crypto_t *ret = talloc_zero (ctx, _notmuch_message_crypto_t);
+
     talloc_set_destructor (ret, _notmuch_message_crypto_destructor);
     return ret;
 }
@@ -106,7 +130,7 @@ _notmuch_message_crypto_new (void *ctx)
 notmuch_status_t
 _notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypto, GMimeSignatureList *sigs)
 {
-    if (!msg_crypto)
+    if (! msg_crypto)
        return NOTMUCH_STATUS_NULL_POINTER;
 
     /* Signatures that arrive after a payload part during DFS are not
@@ -132,19 +156,20 @@ _notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypt
 }
 
 
-notmuch_status_t
-_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *payload, GMimeObject *parent, int childnum)
+bool
+_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part, GMimeObject *parent, int childnum)
 {
     const char *protected_headers = NULL;
     const char *forwarded = NULL;
     const char *subject = NULL;
 
-    if (!msg_crypto || !payload)
-       return NOTMUCH_STATUS_NULL_POINTER;
+    if ((! msg_crypto) || (! part))
+       INTERNAL_ERROR ("_notmuch_message_crypto_potential_payload() got NULL for %s\n",
+                       msg_crypto? "part" : "msg_crypto");
 
     /* only fire on the first payload part encountered */
     if (msg_crypto->payload_encountered)
-       return NOTMUCH_STATUS_SUCCESS;
+       return false;
 
     /* the first child of multipart/encrypted that matches the
      * encryption protocol should be "control information" metadata,
@@ -152,11 +177,11 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto
      * https://tools.ietf.org/html/rfc1847#page-8) */
     if (parent && GMIME_IS_MULTIPART_ENCRYPTED (parent) && childnum == GMIME_MULTIPART_ENCRYPTED_VERSION) {
        const char *enc_type = g_mime_object_get_content_type_parameter (parent, "protocol");
-       GMimeContentType *ct = g_mime_object_get_content_type (payload);
+       GMimeContentType *ct = g_mime_object_get_content_type (part);
        if (ct && enc_type) {
            const char *part_type = g_mime_content_type_get_mime_type (ct);
            if (part_type && strcmp (part_type, enc_type) == 0)
-               return NOTMUCH_STATUS_SUCCESS;
+               return false;
        }
     }
 
@@ -166,7 +191,7 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto
      * envelope: */
     if ((msg_crypto->decryption_status != NOTMUCH_MESSAGE_DECRYPTED_FULL) &&
        (msg_crypto->sig_list == NULL))
-       return NOTMUCH_STATUS_SUCCESS;
+       return false;
 
     /* Verify that this payload has headers that are intended to be
      * exported to the larger message: */
@@ -174,16 +199,16 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto
     /* Consider a payload that uses Alexei Melinkov's forwarded="no" for
      * message/global or message/rfc822:
      * https://tools.ietf.org/html/draft-melnikov-smime-header-signing-05#section-4 */
-    forwarded = g_mime_object_get_content_type_parameter (payload, "forwarded");
-    if (GMIME_IS_MESSAGE_PART (payload) && forwarded && strcmp (forwarded, "no") == 0) {
-       GMimeMessage *message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (payload));
+    forwarded = g_mime_object_get_content_type_parameter (part, "forwarded");
+    if (GMIME_IS_MESSAGE_PART (part) && forwarded && strcmp (forwarded, "no") == 0) {
+       GMimeMessage *message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
        subject = g_mime_message_get_subject (message);
        /* FIXME: handle more than just Subject: at some point */
     } else {
        /* Consider "memoryhole"-style protected headers as practiced by Enigmail and K-9 */
-       protected_headers = g_mime_object_get_content_type_parameter (payload, "protected-headers");
-       if (protected_headers && strcasecmp("v1", protected_headers) == 0)
-           subject = g_mime_object_get_header (payload, "Subject");
+       protected_headers = g_mime_object_get_content_type_parameter (part, "protected-headers");
+       if (protected_headers && strcasecmp ("v1", protected_headers) == 0)
+           subject = g_mime_object_get_header (part, "Subject");
        /* FIXME: handle more than just Subject: at some point */
     }
 
@@ -193,19 +218,19 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto
        msg_crypto->payload_subject = talloc_strdup (msg_crypto, subject);
     }
 
-    return NOTMUCH_STATUS_SUCCESS;
+    return true;
 }
 
 
 notmuch_status_t
 _notmuch_message_crypto_successful_decryption (_notmuch_message_crypto_t *msg_crypto)
 {
-    if (!msg_crypto)
+    if (! msg_crypto)
        return NOTMUCH_STATUS_NULL_POINTER;
 
     /* see the rationale for different values of
      * _notmuch_message_decryption_status_t in util/crypto.h */
-    if (!msg_crypto->payload_encountered)
+    if (! msg_crypto->payload_encountered)
        msg_crypto->decryption_status = NOTMUCH_MESSAGE_DECRYPTED_FULL;
     else if (msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_NONE)
        msg_crypto->decryption_status = NOTMUCH_MESSAGE_DECRYPTED_PARTIAL;
index fdbb5da5d49de736f2d991f7b8d6e24e6af78aa4..4fa5599c755cfedc8e27aaef4e59dc83df3bd997 100644 (file)
@@ -18,7 +18,7 @@ GMimeObject *
 _notmuch_crypto_decrypt (bool *attempted,
                         notmuch_decryption_policy_t decrypt,
                         notmuch_message_t *message,
-                        GMimeMultipartEncrypted *part,
+                        GMimeObject *part,
                         GMimeDecryptResult **decrypt_result,
                         GError **err);
 
@@ -50,7 +50,7 @@ typedef struct _notmuch_message_crypto {
     /* signature status of the whole message (either the whole message
      * is signed, or it is not) -- this means that partially-signed
      * messages will get no signature status. */
-    GMimeSignatureList * sig_list;
+    GMimeSignatureList *sig_list;
     /* if part of the message was signed, and the MUA is clever, it
      * can determine on its own exactly which part and try to make
      * more sense of it. */
@@ -62,7 +62,7 @@ typedef struct _notmuch_message_crypto {
     /* the value of any "Subject:" header in the cryptographic payload
      * (the top level part within the crypto envelope), converted to
      * UTF-8 */
-    char * payload_subject;
+    char *payload_subject;
 
     /* if both signed and encrypted, was the signature encrypted? */
     bool signature_encrypted;
@@ -90,9 +90,12 @@ _notmuch_message_crypto_successful_decryption (_notmuch_message_crypto_t *msg_cr
 
 /* call potential_payload during a depth-first-search on a message
  * when encountering a message part that is not part of the envelope.
+ *
+ * Returns true if part is the root of the cryptographic payload of
+ * this message.
  */
-notmuch_status_t
-_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *payload, GMimeObject *parent, int childnum);
+bool
+_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part, GMimeObject *parent, int childnum);
 
 
 #ifdef __cplusplus
index aa3b77c43fd92d95ac55babf345b977861644333..a51f001f449822d8d671516d2e435881442e1e31 100644 (file)
@@ -44,8 +44,8 @@ _internal_error (const char *format, ...) PRINTF_ATTRIBUTE (1, 2) NORETURN_ATTRI
  *
  * Note that __location__ comes from talloc.h.
  */
-#define INTERNAL_ERROR(format, ...)                    \
-    _internal_error (format " (%s).\n",                        \
+#define INTERNAL_ERROR(format, ...)                     \
+    _internal_error (format " (%s).\n",                 \
                     ##__VA_ARGS__, __location__)
 
 #ifdef __cplusplus
index d1bb1d47ea72930d756c0b89ee9fe5e1ec731530..04d8ed3d70a83b4b1bb7bab029f060227d2e3b70 100644 (file)
@@ -3,7 +3,8 @@
 
 static
 GMimeStream *
-_gzfile_maybe_filter (GMimeStream *file_stream) {
+_gzfile_maybe_filter (GMimeStream *file_stream)
+{
     char buf[4];
     int bytes_read;
 
@@ -14,7 +15,7 @@ _gzfile_maybe_filter (GMimeStream *file_stream) {
        return NULL;
 
     /* check for gzipped input */
-    if (bytes_read >= 2 && buf[0] == 0x1f && (unsigned char)buf[1] == 0x8b) {
+    if (bytes_read >= 2 && buf[0] == 0x1f && (unsigned char) buf[1] == 0x8b) {
        GMimeStream *gzstream;
        GMimeFilter *gzfilter;
 
@@ -61,13 +62,13 @@ g_mime_stream_gzfile_open (const char *filename)
 }
 
 GMimeStream *
-g_mime_stream_stdout_new()
+g_mime_stream_stdout_new ()
 {
     GMimeStream *stream_stdout = NULL;
     GMimeStream *stream_buffered = NULL;
 
     stream_stdout = g_mime_stream_pipe_new (STDOUT_FILENO);
-    if (!stream_stdout)
+    if (! stream_stdout)
        return NULL;
 
     g_mime_stream_pipe_set_owner (GMIME_STREAM_PIPE (stream_stdout), FALSE);
@@ -82,10 +83,11 @@ g_mime_stream_stdout_new()
 /**
  * copy a glib string into a talloc context, and free it.
  */
-static char*
+static char *
 g_string_talloc_strdup (void *ctx, char *g_string)
 {
     char *new_str = talloc_strdup (ctx, g_string);
+
     g_free (g_string);
     return new_str;
 }
@@ -95,6 +97,7 @@ g_mime_certificate_get_valid_userid (GMimeCertificate *cert)
 {
     /* output user id only if validity is FULL or ULTIMATE. */
     const char *uid = g_mime_certificate_get_user_id (cert);
+
     if (uid == NULL)
        return uid;
     GMimeValidity validity = g_mime_certificate_get_id_validity (cert);
@@ -103,10 +106,12 @@ g_mime_certificate_get_valid_userid (GMimeCertificate *cert)
     return NULL;
 }
 
-const char*
-g_mime_certificate_get_fpr16 (GMimeCertificate *cert) {
+const char *
+g_mime_certificate_get_fpr16 (GMimeCertificate *cert)
+{
     const char *fpr = g_mime_certificate_get_fingerprint (cert);
-    if (!fpr || strlen (fpr) < 16)
+
+    if (! fpr || strlen (fpr) < 16)
        return fpr;
 
     return fpr + (strlen (fpr) - 16);
@@ -116,23 +121,25 @@ char *
 g_mime_message_get_address_string (GMimeMessage *message, GMimeAddressType type)
 {
     InternetAddressList *list = g_mime_message_get_addresses (message, type);
+
     return internet_address_list_to_string (list, NULL, 0);
 }
 
 char *
 g_mime_message_get_date_string (void *ctx, GMimeMessage *message)
 {
-    GDateTime* parsed_date = g_mime_message_get_date (message);
+    GDateTime *parsed_date = g_mime_message_get_date (message);
+
     if (parsed_date) {
        char *date = g_mime_utils_header_format_date (parsed_date);
        return g_string_talloc_strdup (ctx, date);
     } else {
-       return talloc_strdup(ctx, "Thu, 01 Jan 1970 00:00:00 +0000");
+       return talloc_strdup (ctx, "Thu, 01 Jan 1970 00:00:00 +0000");
     }
 }
 
 InternetAddressList *
-g_mime_message_get_reply_to_list(GMimeMessage *message)
+g_mime_message_get_reply_to_list (GMimeMessage *message)
 {
     return g_mime_message_get_reply_to (message);
 }
@@ -147,6 +154,7 @@ char *
 g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message)
 {
     InternetAddressList *list = g_mime_message_get_reply_to (message);
+
     return g_string_talloc_strdup (ctx, internet_address_list_to_string (list, NULL, 0));
 }
 
@@ -163,23 +171,27 @@ g_mime_parser_set_scan_from (GMimeParser *parser, gboolean flag)
  */
 
 gboolean
-g_mime_signature_status_good (GMimeSignatureStatus status) {
-    return ((status  & (GMIME_SIGNATURE_STATUS_RED | GMIME_SIGNATURE_STATUS_ERROR_MASK)) == 0);
+g_mime_signature_status_good (GMimeSignatureStatus status)
+{
+    return ((status & (GMIME_SIGNATURE_STATUS_RED | GMIME_SIGNATURE_STATUS_ERROR_MASK)) == 0);
 }
 
 gboolean
-g_mime_signature_status_bad (GMimeSignatureStatus status) {
+g_mime_signature_status_bad (GMimeSignatureStatus status)
+{
     return (status & GMIME_SIGNATURE_STATUS_RED);
 }
 
 gboolean
-g_mime_signature_status_error (GMimeSignatureStatus status) {
+g_mime_signature_status_error (GMimeSignatureStatus status)
+{
     return (status & GMIME_SIGNATURE_STATUS_ERROR_MASK);
 }
 
 gint64
-g_mime_utils_header_decode_date_unix (const char *date) {
-    GDateTime* parsed_date = g_mime_utils_header_decode_date (date);
+g_mime_utils_header_decode_date_unix (const char *date)
+{
+    GDateTime *parsed_date = g_mime_utils_header_decode_date (date);
     time_t ret;
 
     if (parsed_date) {
index b0c8d3d84a07429a44d84c5c7f1c03780b3bee51..094309ec26041e4af3e6356087166a1873e594a6 100644 (file)
@@ -7,7 +7,7 @@
 extern "C" {
 #endif
 
-GMimeStream *g_mime_stream_stdout_new(void);
+GMimeStream *g_mime_stream_stdout_new (void);
 
 /* Return a GMime stream for this open file descriptor, un-gzipping if
  * necessary */
@@ -27,7 +27,7 @@ const char *g_mime_certificate_get_fpr16 (GMimeCertificate *cert);
  */
 char *g_mime_message_get_address_string (GMimeMessage *message, GMimeAddressType type);
 
-InternetAddressList * g_mime_message_get_addresses (GMimeMessage *message, GMimeAddressType type);
+InternetAddressList *g_mime_message_get_addresses (GMimeMessage *message, GMimeAddressType type);
 
 /**
  * return talloc allocated date string
@@ -39,21 +39,21 @@ char *g_mime_message_get_date_string (void *ctx, GMimeMessage *message);
  * glib allocated list of From: addresses
  */
 
-InternetAddressList * g_mime_message_get_from (GMimeMessage *message);
+InternetAddressList *g_mime_message_get_from (GMimeMessage *message);
 
 
 /**
  * return string for From: address
  * (owned by gmime)
  */
-const char * g_mime_message_get_from_string (GMimeMessage *message);
+const char *g_mime_message_get_from_string (GMimeMessage *message);
 
-InternetAddressList * g_mime_message_get_reply_to_list (GMimeMessage *message);
+InternetAddressList *g_mime_message_get_reply_to_list (GMimeMessage *message);
 
 /**
  * return talloc allocated reply-to string
  */
-char * g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message);
+char *g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message);
 
 void g_mime_parser_set_scan_from (GMimeParser *parser, gboolean flag);
 
@@ -68,7 +68,7 @@ gint64 g_mime_utils_header_decode_date_unix (const char *date);
 /**
  * Return string for valid User ID (or NULL if no valid User ID exists)
  */
-const char * g_mime_certificate_get_valid_userid (GMimeCertificate *cert);
+const char *g_mime_certificate_get_valid_userid (GMimeCertificate *cert);
 
 #ifdef __cplusplus
 }
index 8883ff903baff0d236e4a490a9957683d8b500c2..81534a8cb95f0faec19a3661f98e9bd7ecde9f00 100644 (file)
@@ -72,7 +72,7 @@ hex_encode (void *ctx, const char *in, char **out, size_t *out_size)
     if (*out == NULL)
        *out_size = 0;
 
-    if (!maybe_realloc (ctx, needed, out, out_size))
+    if (! maybe_realloc (ctx, needed, out, out_size))
        return HEX_OUT_OF_MEMORY;
 
     q = *out;
@@ -82,7 +82,7 @@ hex_encode (void *ctx, const char *in, char **out, size_t *out_size)
        if (is_output (*p)) {
            *q++ = *p++;
        } else {
-           sprintf (q, "%%%02x", (unsigned char)*p++);
+           sprintf (q, "%%%02x", (unsigned char) *p++);
            q += 3;
        }
     }
@@ -105,8 +105,8 @@ hex_decode_internal (const char *in, unsigned char *out)
            char *endp;
 
            /* This also handles unexpected end-of-string. */
-           if (!isxdigit ((unsigned char) in[1]) ||
-               !isxdigit ((unsigned char) in[2]))
+           if (! isxdigit ((unsigned char) in[1]) ||
+               ! isxdigit ((unsigned char) in[2]))
                return HEX_SYNTAX_ERROR;
 
            buf[0] = in[1];
@@ -139,10 +139,10 @@ hex_decode_inplace (char *s)
 }
 
 hex_status_t
-hex_decode (void *ctx, const char *in, char **out, size_t * out_size)
+hex_decode (void *ctx, const char *in, char **out, size_t *out_size)
 {
     const char *p;
-    size_t needed = 1; /* for the NUL */
+    size_t needed = 1;  /* for the NUL */
 
     assert (ctx); assert (in); assert (out); assert (out_size);
 
@@ -152,7 +152,7 @@ hex_decode (void *ctx, const char *in, char **out, size_t * out_size)
        else
            needed += 1;
 
-    if (!maybe_realloc (ctx, needed, out, out_size))
+    if (! maybe_realloc (ctx, needed, out, out_size))
        return HEX_OUT_OF_MEMORY;
 
     return hex_decode_internal (in, (unsigned char *) *out);
index 50d946eda4463b4bbf539625ed9336697ee41312..8703334cda8d4836660bed1f8cf41c65e2fec023 100644 (file)
@@ -29,11 +29,11 @@ typedef enum hex_status {
 
 hex_status_t
 hex_encode (void *talloc_ctx, const char *in, char **out,
-            size_t *out_size);
+           size_t *out_size);
 
 hex_status_t
 hex_decode (void *talloc_ctx, const char *in, char **out,
-            size_t *out_size);
+           size_t *out_size);
 
 /*
  * Non-allocating hex decode to decode 's' in-place. The length of the
diff --git a/util/repair.c b/util/repair.c
new file mode 100644 (file)
index 0000000..5a64e00
--- /dev/null
@@ -0,0 +1,156 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2019 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/ .
+ *
+ * Authors: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ */
+
+#include <stdbool.h>
+#include "repair.h"
+
+
+static bool
+_notmuch_crypto_payload_has_legacy_display (GMimeObject *payload)
+{
+    GMimeMultipart *mpayload;
+    const char *protected_header_parameter;
+    GMimeObject *first;
+
+    if (! g_mime_content_type_is_type (g_mime_object_get_content_type (payload),
+                                      "multipart", "mixed"))
+       return false;
+    protected_header_parameter = g_mime_object_get_content_type_parameter (payload, "protected-headers");
+    if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1"))
+       return false;
+    if (! GMIME_IS_MULTIPART (payload))
+       return false;
+    mpayload = GMIME_MULTIPART (payload);
+    if (mpayload == NULL)
+       return false;
+    if (g_mime_multipart_get_count (mpayload) != 2)
+       return false;
+    first = g_mime_multipart_get_part (mpayload, 0);
+    /* Early implementations that generated "Legacy Display" parts used
+       Content-Type: text/rfc822-headers, but text/plain is more widely
+       rendered, so it is now the standard choice.  We accept either as a
+       Legacy Display part. */
+    if (! (g_mime_content_type_is_type (g_mime_object_get_content_type (first),
+                                       "text", "plain") ||
+          g_mime_content_type_is_type (g_mime_object_get_content_type (first),
+                                       "text", "rfc822-headers")))
+       return false;
+    protected_header_parameter = g_mime_object_get_content_type_parameter (first, "protected-headers");
+    if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1"))
+       return false;
+    if (! GMIME_IS_TEXT_PART (first))
+       return false;
+
+    return true;
+}
+
+GMimeObject *
+_notmuch_repair_crypto_payload_skip_legacy_display (GMimeObject *payload)
+{
+    if (_notmuch_crypto_payload_has_legacy_display (payload)) {
+       return g_mime_multipart_get_part (GMIME_MULTIPART (payload), 1);
+    } else {
+       return payload;
+    }
+}
+
+/* see
+ * https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1.1 */
+static bool
+_notmuch_is_mixed_up_mangled (GMimeObject *part)
+{
+    GMimeMultipart *mpart = NULL;
+    GMimeObject *parts[3] = {NULL, NULL, NULL};
+    GMimeContentType *type = NULL;
+    char *prelude_string = NULL;
+    bool prelude_is_empty;
+
+    if (part == NULL)
+       return false;
+    type = g_mime_object_get_content_type (part);
+    if (type == NULL)
+       return false;
+    if (! g_mime_content_type_is_type (type, "multipart", "mixed"))
+       return false;
+    if (! GMIME_IS_MULTIPART (part)) /* probably impossible */
+       return false;
+    mpart = GMIME_MULTIPART (part);
+    if (mpart == NULL)
+       return false;
+    if (g_mime_multipart_get_count (mpart) != 3)
+       return false;
+    parts[0] = g_mime_multipart_get_part (mpart, 0);
+    if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[0]),
+                                      "text", "plain"))
+       return false;
+    if (! GMIME_IS_TEXT_PART (parts[0]))
+       return false;
+    parts[1] = g_mime_multipart_get_part (mpart, 1);
+    if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[1]),
+                                      "application", "pgp-encrypted"))
+       return false;
+    parts[2] = g_mime_multipart_get_part (mpart, 2);
+    if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[2]),
+                                      "application", "octet-stream"))
+       return false;
+
+    /* Is parts[0] length 0? */
+    prelude_string = g_mime_text_part_get_text (GMIME_TEXT_PART (parts[0]));
+    prelude_is_empty = (prelude_string[0] == '\0');
+    g_free (prelude_string);
+    if (! prelude_is_empty)
+       return false;
+
+    /* FIXME: after decoding and stripping whitespace, is parts[1]
+     * subpart just "Version: 1" ? */
+
+    /* FIXME: can we determine that parts[2] subpart is *only* PGP
+     * encrypted data?  I tried g_mime_part_get_openpgp_data () but
+     * found https://github.com/jstedfast/gmime/issues/60 */
+
+    return true;
+}
+
+
+/* see
+ * https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1.2 */
+GMimeObject *
+_notmuch_repair_mixed_up_mangled (GMimeObject *part)
+{
+    GMimeMultipart *mpart = NULL, *mpart_ret = NULL;
+    GMimeObject *ret = NULL;
+
+    if (! _notmuch_is_mixed_up_mangled (part))
+       return NULL;
+    mpart = GMIME_MULTIPART (part);
+    ret = GMIME_OBJECT (g_mime_multipart_encrypted_new ());
+    if (ret == NULL)
+       return NULL;
+    mpart_ret = GMIME_MULTIPART (ret);
+    if (mpart_ret == NULL) {
+       g_object_unref (ret);
+       return NULL;
+    }
+    g_mime_object_set_content_type_parameter (ret, "protocol", "application/pgp-encrypted");
+
+    g_mime_multipart_insert (mpart_ret, 0, g_mime_multipart_get_part (mpart, 1));
+    g_mime_multipart_insert (mpart_ret, 1, g_mime_multipart_get_part (mpart, 2));
+    return ret;
+}
diff --git a/util/repair.h b/util/repair.h
new file mode 100644 (file)
index 0000000..492f5a2
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef _REPAIR_H
+#define _REPAIR_H
+
+#include "gmime-extra.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is a collection of message structure and message format repair
+ * techniques that are designed to improve the user experience of
+ * notmuch */
+
+/* If payload is a cryptographic payload within an encrypted message, and
+ * it has a "legacy display" part, then we can skip over it and jump
+ * to the actual content, because notmuch already handles protected
+ * headers appropriately.
+ *
+ * This function either returns payload directly (if it does not have
+ * a "legacy display" part), or it returns a pointer to its
+ * content-bearing subpart, with the "legacy display" part and the
+ * surrounding multipart/mixed object bypassed.
+ *
+ * No new objects are created by calling this function, and the
+ * returned object will only be released when the original part is
+ * disposed of.
+ */
+
+GMimeObject *
+_notmuch_repair_crypto_payload_skip_legacy_display (GMimeObject *payload);
+
+/* Detecting and repairing "Mixed-Up MIME mangling". see
+ * https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1
+ * If this returns NULL, the message was probably not "Mixed up".  If
+ * it returns non-NULL, then there is a newly-allocated MIME part that
+ * represents the repaired version.  The caller is responsible for
+ * ensuring that any returned object is freed with g_object_unref. */
+GMimeObject *
+_notmuch_repair_mixed_up_mangled (GMimeObject *part);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
index fc2058e03483da596c72946f1fe29f6ecfa60b44..de8430b2add34dea3fdf52507cf442c420cd170f 100644 (file)
@@ -42,7 +42,7 @@ strtok_len_c (const char *s, const char *delim, size_t *len)
 {
     /* strtok_len is already const-safe, but we can't express both
      * versions in the C type system. */
-    return strtok_len ((char*)s, delim, len);
+    return strtok_len ((char *) s, delim, len);
 }
 
 char *
@@ -60,7 +60,7 @@ sanitize_string (const void *ctx, const char *str)
     for (loop = out; *loop; loop++) {
        if (*loop == '\t' || *loop == '\n')
            *loop = ' ';
-       else if ((unsigned char)(*loop) < 32)
+       else if ((unsigned char) (*loop) < 32)
            *loop = '?';
     }
 
@@ -87,9 +87,9 @@ make_boolean_term (void *ctx, const char *prefix, const char *term,
      * beginning, and anything containing non-ASCII text. */
     if (! term[0])
        need_quoting = 1;
-    for (in = term; *in && !need_quoting; in++)
+    for (in = term; *in && ! need_quoting; in++)
        if (is_unquoted_terminator (*in) || *in == '"' || *in == '('
-           || (unsigned char)*in > 127)
+           || (unsigned char) *in > 127)
            need_quoting = 1;
 
     if (need_quoting)
@@ -141,7 +141,7 @@ make_boolean_term (void *ctx, const char *prefix, const char *term,
     return 0;
 }
 
-const char*
+const char *
 skip_space (const char *str)
 {
     while (*str && isspace ((unsigned char) *str))
@@ -154,6 +154,7 @@ parse_boolean_term (void *ctx, const char *str,
                    char **prefix_out, char **term_out)
 {
     int err = EINVAL;
+
     *prefix_out = *term_out = NULL;
 
     /* Parse prefix */
@@ -193,7 +194,7 @@ parse_boolean_term (void *ctx, const char *str,
        }
        /* Did the term terminate without a closing quote or is there
         * trailing text after the closing quote? */
-       if (!closed || *pos)
+       if (! closed || *pos)
            goto FAIL;
        *out = '\0';
     } else {
@@ -215,7 +216,7 @@ parse_boolean_term (void *ctx, const char *str,
     }
     return 0;
 
- FAIL:
 FAIL:
     talloc_free (*prefix_out);
     talloc_free (*term_out);
     errno = err;
@@ -230,9 +231,9 @@ strcmp_null (const char *s1, const char *s2)
     else if (! s1 && ! s2)
        return 0;
     else if (s1)
-       return 1;       /* s1 (non-NULL) is greater than s2 (NULL) */
+       return 1;       /* s1 (non-NULL) is greater than s2 (NULL) */
     else
-       return -1;      /* s1 (NULL) is less than s2 (non-NULL) */
+       return -1;      /* s1 (NULL) is less than s2 (non-NULL) */
 }
 
 int
@@ -248,6 +249,7 @@ strcase_hash (const void *ptr)
 
     /* This is the djb2 hash. */
     unsigned int hash = 5381;
+
     while (s && *s) {
        hash = ((hash << 5) + hash) + tolower (*s);
        s++;
index 4c110a205ccfb5b2684183e79ca2147d1c891b69..fb95a7402c892ebd95374148073796701ecc3a7c 100644 (file)
@@ -77,7 +77,7 @@ unsigned int strcase_hash (const void *ptr);
 
 void strip_trailing (char *str, char ch);
 
-const charskip_space (const char *str);
+const char *skip_space (const char *str);
 
 #ifdef __cplusplus
 }
index 312e900f76c526b6dc25dd0329be68ab0207a977..ccb787e29f0d9be86bcdf2608e0d1f8ced6e29f0 100644 (file)
@@ -1,8 +1,8 @@
 #include "unicode-util.h"
 
 /* Based on Xapian::Unicode::is_wordchar, to avoid forcing clients to
  link directly to libxapian.
-*/
* link directly to libxapian.
+ */
 
 static bool
 unicode_is_wordchar (notmuch_unichar ch)
index 06659b3506b1f384a4affa58560cff8acbca779d..6abe2215a786866700425135c17ad050e63a1c33 100644 (file)
@@ -19,6 +19,6 @@ util_error_string (util_status_t errnum)
        /* we lack context to be more informative here */
        return "zlib error";
     default:
-       INTERNAL_ERROR("unexpected error status %d", errnum);
+       INTERNAL_ERROR ("unexpected error status %d", errnum);
     }
 }
index f211eaaaac5c4f6a8027fea66be985a6cf8edb7c..07a00343c3900f8c782c4c7778dee9abba1f3f1c 100644 (file)
@@ -111,7 +111,7 @@ xregcomp (regex_t *preg, const char *regex, int cflags)
 
        regerror (rerr, preg, error, error_size);
        fprintf (stderr, "compiling regex %s: %s\n",
-                       regex, error);
+                regex, error);
        free (error);
        return 1;
     }
index 2b2cd8f9bda2af8a7dd2590c0ee3dfb10b066c5d..3a75e50434b92b4ca1dd7d2fffed14e26fde40b8 100644 (file)
@@ -47,6 +47,7 @@ gz_getline (void *talloc_ctx, char **bufptr, ssize_t *bytes_read, gzFile stream)
            int zlib_status = 0;
            (void) gzerror (stream, &zlib_status);
            switch (zlib_status) {
+           case Z_STREAM_END:
            case Z_OK:
                /* no data read before EOF */
                if (offset == 0)
@@ -70,16 +71,24 @@ gz_getline (void *talloc_ctx, char **bufptr, ssize_t *bytes_read, gzFile stream)
        if (buf == NULL)
            return UTIL_OUT_OF_MEMORY;
     }
- SUCCESS:
 SUCCESS:
     *bufptr = buf;
     *bytes_read = offset;
     return UTIL_SUCCESS;
 }
 
-const char *gz_error_string (util_status_t status, gzFile file)
+const char *
+gz_error_string (util_status_t status, gzFile file)
 {
     if (status == UTIL_GZERROR)
-       return gzerror (file, NULL);
+       return gzerror_str (file);
     else
        return util_error_string (status);
 }
+
+const char *
+gzerror_str(gzFile file)
+{
+    int dummy;
+    return gzerror (file, &dummy);
+}
index 209fa9989437d8cf77bf9ac60c739cb9fcb47a8f..e9925c985330073398c01b53dffcf0d6e6fc208d 100644 (file)
@@ -27,6 +27,11 @@ gz_getline (void *ctx, char **lineptr, ssize_t *bytes_read, gzFile stream);
 const char *
 gz_error_string (util_status_t status, gzFile stream);
 
+/* Call gzerror with a dummy errno argument, the docs don't promise to
+ * support the NULL case */
+const char *
+gzerror_str(gzFile file);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/version b/version
deleted file mode 100644 (file)
index 5540b6e..0000000
--- a/version
+++ /dev/null
@@ -1 +0,0 @@
-0.29.3
diff --git a/version.txt b/version.txt
new file mode 100644 (file)
index 0000000..c415e1c
--- /dev/null
@@ -0,0 +1 @@
+0.31.2