diff options
| author | David Bremner <bremner@debian.org> | 2012-01-27 18:04:44 -0400 |
|---|---|---|
| committer | David Bremner <bremner@debian.org> | 2012-01-27 18:04:44 -0400 |
| commit | bcd44490684965082e5921d0fc71064af3170987 (patch) | |
| tree | 12506f79f6792236b95ef0d6a19ab5113bb4587d | |
| parent | bfb7feb1f51ae348503046823d72348621e34d08 (diff) | |
| parent | ffce9b7c25b9b44ec026b67d96e44cae09c99efe (diff) | |
Merge commit 'debian/0.11-1' into squeeze-backports
Conflicts:
debian/changelog
93 files changed, 2542 insertions, 1035 deletions
@@ -9,6 +9,7 @@ notmuch.sym notmuch-shared notmuch.1.gz libnotmuch.so* +libnotmuch*.dylib *.[ao] *~ .*.swp diff --git a/Makefile.local b/Makefile.local index d97fa618..97f397ff 100644 --- a/Makefile.local +++ b/Makefile.local @@ -12,16 +12,18 @@ PACKAGE=notmuch IS_GIT=$(shell if [ -d .git ] ; then echo yes ; else echo no; fi) +ifeq ($(IS_GIT),yes) +DATE:=$(shell git log --date=short -1 --pretty=format:%cd) +else +DATE:=$(shell date +%F) +endif + VERSION:=$(shell cat ${srcdir}/version) -ifneq ($(MAKECMDGOALS),release) -ifneq ($(MAKECMDGOALS),release-message) -ifneq ($(MAKECMDGOALS),pre-release) +ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),) ifeq ($(IS_GIT),yes) VERSION:=$(shell git describe --match '[0-9.]*'|sed -e s/_/~/ -e s/-/+/ -e s/-/~/) endif endif -endif -endif UPSTREAM_TAG=$(subst ~,_,$(VERSION)) DEB_TAG=debian/$(UPSTREAM_TAG)-1 @@ -91,6 +93,12 @@ $(GPG_FILE): $(SHA1_FILE) .PHONY: dist dist: $(TAR_FILE) +.PHONY: update-versions + +update-versions: + sed -i "s/^.TH NOTMUCH 1.*$$/.TH NOTMUCH 1 ${DATE} \"Notmuch ${VERSION}\"/" notmuch.1 + sed -i "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" $(PV_FILE) + # We invoke make recursively only to force ordering of our phony # targets in the case of parallel invocation of make (-j). # @@ -114,7 +122,7 @@ release: verify-source-tree-and-version ifeq ($(REALLY_UPLOAD),yes) git push origin $(VERSION) cd releases && scp $(TAR_FILE) $(SHA1_FILE) $(GPG_FILE) $(RELEASE_HOST):$(RELEASE_DIR) - ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-[0-9]* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(PACKAGE)-$(VERSION)" + ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(TAR_FILE)" endif @echo "Please send a release announcement using $(PACKAGE)-$(VERSION).announce as a template." @@ -131,15 +139,16 @@ pre-release: mv $(TAR_FILE) $(DEB_TAR_FILE) releases .PHONY: debian-snapshot -debian-snapshot: TMPFILE := $(shell mktemp) debian-snapshot: make VERSION=$(VERSION) clean - 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 + 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 .PHONY: release-message release-message: @@ -179,7 +188,7 @@ release-message: verify-source-tree-and-version: verify-no-dirty-code .PHONY: verify-no-dirty-code -verify-no-dirty-code: verify-version-debian verify-version-python +verify-no-dirty-code: verify-version-debian verify-version-python verify-version-manpage ifeq ($(IS_GIT),yes) @printf "Checking that source tree is clean..." ifneq ($(shell git ls-files -m),) @@ -199,28 +208,33 @@ endif .PHONY: verify-version-debian verify-version-debian: verify-version-components @echo -n "Checking that Debian package version is $(VERSION)-1..." - @if [ "$(VERSION)-1" != $$(dpkg-parsechangelog | grep ^Version | awk '{print $$2}') ] ; then \ + @[ "$(VERSION)-1" = $$(sed '1{ s/).*//; s/.*(//; q; }' debian/changelog) ] || \ (echo "No." && \ - echo "Please edit version and debian/changelog to have consistent versions." && false) \ - fi + echo "Please edit version and debian/changelog to have consistent versions." && false) @echo "Good." .PHONY: verify-version-python verify-version-python: verify-version-components @echo -n "Checking that python bindings version is $(VERSION)..." - @if [ "$(VERSION)" != $$(python -c "execfile('$(PV_FILE)'); print __VERSION__") ] ; then \ + @[ "$(VERSION)" = $$(python -c "execfile('$(PV_FILE)'); print __VERSION__") ] || \ + (echo "No." && \ + echo "Please edit version and $(PV_FILE) to have consistent versions." && false) + @echo "Good." + +.PHONY: verify-version-manpage +verify-version-manpage: verify-version-components + @echo -n "Checking that manual page version is $(VERSION)..." + @[ "$(VERSION)" = $$(sed -n '/^[.]TH NOTMUCH 1/{s/.*"Notmuch //;s/".*//p;}' notmuch.1) ] || \ (echo "No." && \ - echo "Please edit version and $(PV_FILE) to have consistent versions." && false) \ - fi + echo "Please edit version and notmuch.1 to have consistent versions." && false) @echo "Good." .PHONY: verify-version-components verify-version-components: @echo -n "Checking that $(VERSION) consists only of digits and periods..." - @if echo $(VERSION) | grep -q -v -x '[0-9.]*'; then \ + @echo $(VERSION) | grep -q -x '^[0-9.]*$$' || \ (echo "No." && \ - echo "Please follow the instructions in RELEASING to choose a version" && false) \ - else :; fi + echo "Please follow the instructions in RELEASING to choose a version" && false) @echo "Good." .PHONY: verify-newer @@ -282,9 +296,11 @@ clean: distclean: clean notmuch_client_srcs = \ + command-line-arguments.c\ debugger.c \ gmime-filter-reply.c \ gmime-filter-headers.c \ + hooks.c \ notmuch.c \ notmuch-config.c \ notmuch-count.c \ @@ -1,3 +1,107 @@ +Notmuch 0.11 (2012-01-13) +========================= + +Command-Line Interface +---------------------- + +Hooks + + Hooks have been introduced to notmuch. Hooks are scripts that notmuch + invokes before and after certain actions. Initially, "notmuch new" + supports "pre-new" and "post-new" hooks that are run before and after + importing new messages into the database. + +notmuch reply --decrypt bugfix + + The "notmuch reply" command with --decrypt argument had a rarely + occurring bug that caused an encrypted message not to be decrypted + sometimes. This is now fixed. + +Performance +----------- + +Automatic tag query optimization + + "notmuch tag" now automatically optimizes the user's query to + exclude messages whose tags won't change. In the past, we've + suggested that people do this by hand; this is no longer necessary. + +Don't sort messages when creating a dump file + + This speeds up tag dumps considerably, without any loss of + information. To replicate the old behavior of sorted output (for + example to compare two dump files), one can use e.g. sort(1). + +Memory Management +----------------- + +Reduction of memory leaks + + Two memory leaks when searching and showing messages were identified + and fixed in this release. + +Emacs Interface +--------------- + +Bug fixes + + notmuch-show-advance (bound to the spacebar in notmuch-show-mode) had + a bug that caused it to always jump to the next message, even if it + should have scrolled down to show more of the current message instead. + This is now fixed. + +Support "notmuch new" as a notmuch-poll-script + + It's now possible to use "notmuch new" as a notmuch-poll-script + directly. This is also the new default. This allows taking better + advantage of the "notmuch new" hooks from emacs without intermediate + scripts. + +Improvements in saved search management + + New saved searches are now appended to the list of saved searches, + not inserted in front. It's also possible to define a sort function + for displaying saved searches; alphabetical sort is provided. + +Hooks for notmuch-hello + + Two new hooks have been added: "notmuch-hello-mode-hook" (called after + entering notmuch-hello-mode) and "notmuch-hello-refresh-hook" (called + after updating a notmuch-hello buffer). + +New face for crypto parts headers + + Crypto parts used to be displayed with a hardcoded color. A new face + has been introduced to fix this: notmuch-crypto-part-header. It + defaults to the same value as before, but can be customized to match + other color themes. + +Use space as default thousands separator + + Large numbers in notmuch-hello are now displayed using a space as + thousands separator (e.g. "123 456" instead of "123,456"). This can be + changed by customizing "notmuch-hello-thousands-separator". + +Call notmuch-show instead of notmuch-search when clicking on +buttonized id: links. + +New function notmuch-show-advance + + This new function advances through just the current thread, and is + less invasive than notmuch-show-advance-and-archive. It can easily + be bound to SPC with: + + (define-key notmuch-show-mode-map " " 'notmuch-show-advance) + +Various performance improvements. + +New add-on tool +--------------- + +The tool contrib/notmuch-deliver helps with initial delivery and +tagging of mail (replacing running notmuch new). + + Notmuch 0.10.2 (2011-12-04) =========================== @@ -62,11 +62,11 @@ repository. From here, there are just a few steps to release: be "1.0.1" and a subsequent bug-fix release would be "1.0.2" etc. - Update bindings/python/notmuch/version.py to match version. + When you are happy with the file 'version', run - Update the version in notmuch.1 to match version. + make update-versions - XXX: Probably these last two steps should be (semi-)automated. + to propagate the version to the other places needed. Commit these changes. diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index 73d2a3b0..f7d3d605 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -138,10 +138,10 @@ More information on specific topics can be found on the following pages: .. method:: __len__() - .. warning:: :meth:`__len__` was removed in version 0.6 as it exhausted - the iterator and broke list(Messages()). Use the - :meth:`Query.count_messages` function or use - `len(list(msgs))`. + .. warning:: + + :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke + list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`. :class:`Message` -- A single message ---------------------------------------- @@ -205,10 +205,11 @@ More information on specific topics can be found on the following pages: .. method:: __len__ - .. warning:: :meth:`__len__` was removed in version 0.6 as it - exhausted the iterator and broke list(Tags()). Use - :meth:`len(list(msgs))` instead if you need to know the - number of tags. + .. warning:: + + :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke + list(Tags()). Use :meth:`len(list(msgs))` instead if you need to know the number of + tags. .. automethod:: __str__ diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py index 539afedf..f3ff9874 100644 --- a/bindings/python/notmuch/__init__.py +++ b/bindings/python/notmuch/__init__.py @@ -69,7 +69,7 @@ from notmuch.globals import ( TagTooLongError, UnbalancedFreezeThawError, UnbalancedAtomicError, - NotInitializedError + NotInitializedError, ) from notmuch.version import __VERSION__ __LICENSE__ = "GPL v3+" diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index f4bc53e0..7923f768 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -18,13 +18,16 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ import os -from ctypes import c_int, c_char_p, c_void_p, c_uint, c_long, byref +from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER from notmuch.globals import (nmlib, STATUS, NotmuchError, NotInitializedError, - NullPointerError, OutOfMemoryError, XapianError, Enum, _str) + NullPointerError, Enum, _str, + NotmuchDatabaseP, NotmuchDirectoryP, NotmuchMessageP, NotmuchTagsP, + NotmuchQueryP, NotmuchMessagesP, NotmuchThreadsP, NotmuchFilenamesP) from notmuch.thread import Threads from notmuch.message import Messages, Message from notmuch.tag import Tags + class Database(object): """The :class:`Database` is the highest-level object that notmuch provides. It references a notmuch database, and can be opened in @@ -37,16 +40,19 @@ class Database(object): :exc:`XapianError` as the underlying database has been modified. Close and reopen the database to continue working with it. - .. note:: Any function in this class can and will throw an - :exc:`NotInitializedError` if the database was not - intitialized properly. + .. note:: + + Any function in this class can and will throw an + :exc:`NotInitializedError` if the database was not intitialized + properly. + + .. note:: - .. note:: Do remember that as soon as we tear down (e.g. via `del - db`) this object, all underlying derived objects such as - queries, threads, messages, tags etc will be freed by the - underlying library as well. Accessing these objects will lead - to segfaults and other unexpected behavior. See above for - more details. + Do remember that as soon as we tear down (e.g. via `del db`) this + object, all underlying derived objects such as queries, threads, + messages, tags etc will be freed by the underlying library as well. + Accessing these objects will lead to segfaults and other unexpected + behavior. See above for more details. """ _std_db_path = None """Class attribute to cache user's default database""" @@ -56,37 +62,50 @@ class Database(object): """notmuch_database_get_directory""" _get_directory = nmlib.notmuch_database_get_directory - _get_directory.restype = c_void_p + _get_directory.argtypes = [NotmuchDatabaseP, c_char_p] + _get_directory.restype = NotmuchDirectoryP """notmuch_database_get_path""" _get_path = nmlib.notmuch_database_get_path + _get_path.argtypes = [NotmuchDatabaseP] _get_path.restype = c_char_p """notmuch_database_get_version""" _get_version = nmlib.notmuch_database_get_version + _get_version.argtypes = [NotmuchDatabaseP] _get_version.restype = c_uint """notmuch_database_open""" _open = nmlib.notmuch_database_open - _open.restype = c_void_p + _open.argtypes = [c_char_p, c_uint] + _open.restype = NotmuchDatabaseP """notmuch_database_upgrade""" _upgrade = nmlib.notmuch_database_upgrade - _upgrade.argtypes = [c_void_p, c_void_p, c_void_p] + _upgrade.argtypes = [NotmuchDatabaseP, c_void_p, c_void_p] + _upgrade.restype = c_uint """ notmuch_database_find_message""" _find_message = nmlib.notmuch_database_find_message + _find_message.argtypes = [NotmuchDatabaseP, c_char_p, + POINTER(NotmuchMessageP)] + _find_message.restype = c_uint """notmuch_database_find_message_by_filename""" _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename + _find_message_by_filename.argtypes = [NotmuchDatabaseP, c_char_p, + POINTER(NotmuchMessageP)] + _find_message_by_filename.restype = c_uint """notmuch_database_get_all_tags""" _get_all_tags = nmlib.notmuch_database_get_all_tags - _get_all_tags.restype = c_void_p + _get_all_tags.argtypes = [NotmuchDatabaseP] + _get_all_tags.restype = NotmuchTagsP """notmuch_database_create""" _create = nmlib.notmuch_database_create - _create.restype = c_void_p + _create.argtypes = [c_char_p] + _create.restype = NotmuchDatabaseP def __init__(self, path=None, create=False, mode=0): """If *path* is `None`, we will try to read a users notmuch @@ -164,8 +183,8 @@ class Database(object): :param status: Open the database in read-only or read-write mode :type status: :attr:`MODE` :returns: Nothing - :exception: Raises :exc:`NotmuchError` in case - of any failure (possibly after printing an error message on stderr). + :exception: Raises :exc:`NotmuchError` in case of any failure + (possibly after printing an error message on stderr). """ res = Database._open(_str(path), mode) @@ -186,6 +205,10 @@ class Database(object): self._assert_db_is_initialized() return Database._get_version(self._db) + _needs_upgrade = nmlib.notmuch_database_needs_upgrade + _needs_upgrade.argtypes = [NotmuchDatabaseP] + _needs_upgrade.restype = bool + def needs_upgrade(self): """Does this database need to be upgraded before writing to it? @@ -197,7 +220,7 @@ class Database(object): :returns: `True` or `False` """ self._assert_db_is_initialized() - return nmlib.notmuch_database_needs_upgrade(self._db) + return self._needs_upgrade(self._db) def upgrade(self): """Upgrades the current database @@ -219,6 +242,10 @@ class Database(object): #TODO: catch exceptions, document return values and etc return status + _begin_atomic = nmlib.notmuch_database_begin_atomic + _begin_atomic.argtypes = [NotmuchDatabaseP] + _begin_atomic.restype = c_uint + def begin_atomic(self): """Begin an atomic database operation @@ -229,18 +256,20 @@ class Database(object): neither begin nor end necessarily flush modifications to disk. :returns: :attr:`STATUS`.SUCCESS or raises - - :exception: :exc:`NotmuchError`: - :attr:`STATUS`.XAPIAN_EXCEPTION - Xapian exception occurred; atomic section not entered. + :exception: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION + Xapian exception occurred; atomic section not entered. *Added in notmuch 0.9*""" self._assert_db_is_initialized() - status = nmlib.notmuch_database_begin_atomic(self._db) + status = self._begin_atomic(self._db) if status != STATUS.SUCCESS: raise NotmuchError(status) return status + _end_atomic = nmlib.notmuch_database_end_atomic + _end_atomic.argtypes = [NotmuchDatabaseP] + _end_atomic.restype = c_uint + def end_atomic(self): """Indicate the end of an atomic database operation @@ -258,7 +287,7 @@ class Database(object): *Added in notmuch 0.9*""" self._assert_db_is_initialized() - status = nmlib.notmuch_database_end_atomic(self._db) + status = self._end_atomic(self._db) if status != STATUS.SUCCESS: raise NotmuchError(status) return status @@ -267,13 +296,15 @@ class Database(object): """Returns a :class:`Directory` of path, (creating it if it does not exist(?)) - .. warning:: This call needs a writeable database in - :attr:`Database.MODE`.READ_WRITE mode. The underlying library will exit the - program if this method is used on a read-only database! + .. warning:: + + This call needs a writeable database in + :attr:`Database.MODE`.READ_WRITE mode. The underlying library will + exit the program if this method is used on a read-only database! :param path: An unicode string containing the path relative to the path - of database (see :meth:`get_path`), or else should be an absolute path - with initial components that match the path of 'database'. + of database (see :meth:`get_path`), or else should be an absolute + path with initial components that match the path of 'database'. :returns: :class:`Directory` or raises an exception. :exception: :exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR @@ -299,6 +330,11 @@ class Database(object): # return the Directory, init it with the absolute path return Directory(_str(abs_dirpath), dir_p, self) + _add_message = nmlib.notmuch_database_add_message + _add_message.argtypes = [NotmuchDatabaseP, c_char_p, + POINTER(NotmuchMessageP)] + _add_message.restype = c_uint + def add_message(self, filename, sync_maildir_flags=False): """Adds a new message to the database @@ -349,10 +385,8 @@ class Database(object): be added. """ self._assert_db_is_initialized() - msg_p = c_void_p() - status = nmlib.notmuch_database_add_message(self._db, - _str(filename), - byref(msg_p)) + msg_p = NotmuchMessageP() + status = self._add_message(self._db, _str(filename), byref(msg_p)) if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]: raise NotmuchError(status) @@ -364,6 +398,10 @@ class Database(object): msg.maildir_flags_to_tags() return (msg, status) + _remove_message = nmlib.notmuch_database_remove_message + _remove_message.argtypes = [NotmuchDatabaseP, c_char_p] + _remove_message.restype = c_uint + def remove_message(self, filename): """Removes a message (filename) from the given notmuch database @@ -392,8 +430,7 @@ class Database(object): removed. """ self._assert_db_is_initialized() - return nmlib.notmuch_database_remove_message(self._db, - filename) + return self._remove_message(self._db, filename) def find_message(self, msgid): """Returns a :class:`Message` as identified by its message ID @@ -416,7 +453,7 @@ class Database(object): the database was not intitialized. """ self._assert_db_is_initialized() - msg_p = c_void_p() + msg_p = NotmuchMessageP() status = Database._find_message(self._db, _str(msgid), byref(msg_p)) if status != STATUS.SUCCESS: raise NotmuchError(status) @@ -425,10 +462,11 @@ class Database(object): def find_message_by_filename(self, filename): """Find a message with the given filename - .. warning:: This call needs a writeable database in - :attr:`Database.MODE`.READ_WRITE mode. The underlying library will - exit the program if this method is used on a read-only - database! + .. warning:: + + This call needs a writeable database in + :attr:`Database.MODE`.READ_WRITE mode. The underlying library will + exit the program if this method is used on a read-only database! :returns: If the database contains a message with the given filename, then a class:`Message:` is returned. This @@ -449,7 +487,7 @@ class Database(object): *Added in notmuch 0.9*""" self._assert_db_is_initialized() - msg_p = c_void_p() + msg_p = NotmuchMessageP() status = Database._find_message_by_filename(self._db, _str(filename), byref(msg_p)) if status != STATUS.SUCCESS: @@ -460,7 +498,8 @@ 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 on error + :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER + on error """ self._assert_db_is_initialized() tags_p = Database._get_all_tags(self._db) @@ -491,10 +530,14 @@ class Database(object): def __repr__(self): return "'Notmuch DB " + self.get_path() + "'" + _close = nmlib.notmuch_database_close + _close.argtypes = [NotmuchDatabaseP] + _close.restype = None + def __del__(self): """Close and free the notmuch database if needed""" if self._db is not None: - nmlib.notmuch_database_close(self._db) + self._close(self._db) def _get_user_default_db(self): """ Reads a user's notmuch config and returns his db location @@ -545,18 +588,22 @@ class Query(object): """notmuch_query_create""" _create = nmlib.notmuch_query_create - _create.restype = c_void_p + _create.argtypes = [NotmuchDatabaseP, c_char_p] + _create.restype = NotmuchQueryP """notmuch_query_search_threads""" _search_threads = nmlib.notmuch_query_search_threads - _search_threads.restype = c_void_p + _search_threads.argtypes = [NotmuchQueryP] + _search_threads.restype = NotmuchThreadsP """notmuch_query_search_messages""" _search_messages = nmlib.notmuch_query_search_messages - _search_messages.restype = c_void_p + _search_messages.argtypes = [NotmuchQueryP] + _search_messages.restype = NotmuchMessagesP """notmuch_query_count_messages""" _count_messages = nmlib.notmuch_query_count_messages + _count_messages.argtypes = [NotmuchQueryP] _count_messages.restype = c_uint def __init__(self, db, querystr): @@ -602,6 +649,10 @@ class Query(object): raise NullPointerError self._query = query_p + _set_sort = nmlib.notmuch_query_set_sort + _set_sort.argtypes = [NotmuchQueryP, c_uint] + _set_sort.argtypes = None + def set_sort(self, sort): """Set the sort order future results will be delivered in @@ -609,7 +660,7 @@ class Query(object): """ self._assert_query_is_initialized() self.sort = sort - nmlib.notmuch_query_set_sort(self._query, sort) + self._set_sort(self._query, sort) def search_threads(self): """Execute a query for threads @@ -661,10 +712,14 @@ class Query(object): self._assert_query_is_initialized() return Query._count_messages(self._query) + _destroy = nmlib.notmuch_query_destroy + _destroy.argtypes = [NotmuchQueryP] + _destroy.restype = None + def __del__(self): """Close and free the Query""" if self._query is not None: - nmlib.notmuch_query_destroy(self._query) + self._destroy(self._query) class Directory(object): @@ -683,22 +738,27 @@ class Directory(object): """notmuch_directory_get_mtime""" _get_mtime = nmlib.notmuch_directory_get_mtime + _get_mtime.argtypes = [NotmuchDirectoryP] _get_mtime.restype = c_long """notmuch_directory_set_mtime""" _set_mtime = nmlib.notmuch_directory_set_mtime - _set_mtime.argtypes = [c_char_p, c_long] + _set_mtime.argtypes = [NotmuchDirectoryP, c_long] + _set_mtime.restype = c_uint """notmuch_directory_get_child_files""" _get_child_files = nmlib.notmuch_directory_get_child_files - _get_child_files.restype = c_void_p + _get_child_files.argtypes = [NotmuchDirectoryP] + _get_child_files.restype = NotmuchFilenamesP """notmuch_directory_get_child_directories""" _get_child_directories = nmlib.notmuch_directory_get_child_directories - _get_child_directories.restype = c_void_p + _get_child_directories.argtypes = [NotmuchDirectoryP] + _get_child_directories.restype = NotmuchFilenamesP def _assert_dir_is_initialized(self): - """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) if dir_p is None""" + """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) + if dir_p is None""" if self._dir_p is None: raise NotmuchError(STATUS.NOT_INITIALIZED) @@ -735,10 +795,12 @@ class Directory(object): and know that it only needs to add files if the mtime of the directory and files are newer than the stored timestamp. - .. note:: :meth:`get_mtime` function does not allow the caller - to distinguish a timestamp of 0 from a non-existent - timestamp. So don't store a timestamp of 0 unless you are - comfortable with that. + .. note:: + + :meth:`get_mtime` function does not allow the caller to + distinguish a timestamp of 0 from a non-existent timestamp. So + don't store a timestamp of 0 unless you are comfortable with + that. :param mtime: A (time_t) timestamp :returns: Nothing on success, raising an exception on failure. @@ -815,10 +877,14 @@ class Directory(object): """Object representation""" return "<notmuch Directory object '%s'>" % self._path + _destroy = nmlib.notmuch_directory_destroy + _destroy.argtypes = [NotmuchDirectoryP] + _destroy.argtypes = None + def __del__(self): """Close and free the Directory""" if self._dir_p is not None: - nmlib.notmuch_directory_destroy(self._dir_p) + self._destroy(self._dir_p) class Filenames(object): @@ -826,6 +892,7 @@ class Filenames(object): #notmuch_filenames_get _get = nmlib.notmuch_filenames_get + _get.argtypes = [NotmuchFilenamesP] _get.restype = c_char_p def __init__(self, files_p, parent): @@ -844,41 +911,56 @@ class Filenames(object): """ Make Filenames an iterator """ return self + _valid = nmlib.notmuch_filenames_valid + _valid.argtypes = [NotmuchFilenamesP] + _valid.restype = bool + + _move_to_next = nmlib.notmuch_filenames_move_to_next + _move_to_next.argtypes = [NotmuchFilenamesP] + _move_to_next.restype = None + def next(self): if self._files_p is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - if not nmlib.notmuch_filenames_valid(self._files_p): + if not self._valid(self._files_p): self._files_p = None raise StopIteration file = Filenames._get(self._files_p) - nmlib.notmuch_filenames_move_to_next(self._files_p) + self._move_to_next(self._files_p) return file def __len__(self): """len(:class:`Filenames`) returns the number of contained files - .. note:: As this iterates over the files, we will not be able to - iterate over them again! So this will fail:: + .. note:: + + As this iterates over the files, we will not be able to + iterate over them again! So this will fail:: #THIS FAILS files = Database().get_directory('').get_child_files() - if len(files) > 0: #this 'exhausts' msgs - # next line raises NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)!!! + if len(files) > 0: # this 'exhausts' msgs + # next line raises + # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) for file in files: print file """ if self._files_p is None: raise NotmuchError(STATUS.NOT_INITIALIZED) i = 0 - while nmlib.notmuch_filenames_valid(self._files_p): - nmlib.notmuch_filenames_move_to_next(self._files_p) + while self._valid(self._files_p): + self._move_to_next(self._files_p) i += 1 self._files_p = None return i + _destroy = nmlib.notmuch_filenames_destroy + _destroy.argtypes = [NotmuchFilenamesP] + _destroy.restype = None + def __del__(self): """Close and free Filenames""" if self._files_p is not None: - nmlib.notmuch_filenames_destroy(self._files_p) + self._destroy(self._files_p) diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index de4d785a..a7cd7e63 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -17,7 +17,8 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ from ctypes import c_char_p -from notmuch.globals import nmlib, STATUS, NotmuchError +from notmuch.globals import (nmlib, STATUS, NotmuchError, + NotmuchFilenamesP, NotmuchMessageP) class Filenames(object): @@ -50,6 +51,7 @@ class Filenames(object): #notmuch_filenames_get _get = nmlib.notmuch_filenames_get + _get.argtypes = [NotmuchFilenamesP] _get.restype = c_char_p def __init__(self, files_p, parent): @@ -74,6 +76,14 @@ class Filenames(object): #save reference to parent object so we keep it alive self._parent = parent + _valid = nmlib.notmuch_filenames_valid + _valid.argtypes = [NotmuchFilenamesP] + _valid.restype = bool + + _move_to_next = nmlib.notmuch_filenames_move_to_next + _move_to_next.argtypes = [NotmuchFilenamesP] + _move_to_next.restype = None + def as_generator(self): """Return generator of Filenames @@ -82,13 +92,16 @@ class Filenames(object): if self._files is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - while nmlib.notmuch_filenames_valid(self._files): + while self._valid(self._files): yield Filenames._get(self._files) - nmlib.notmuch_filenames_move_to_next(self._files) + self._move_to_next(self._files) self._files = None def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): """Represent Filenames() as newline-separated list of full paths .. note:: As this iterates over the filenames, we will not be @@ -101,7 +114,11 @@ class Filenames(object): """ return "\n".join(self) + _destroy = nmlib.notmuch_filenames_destroy + _destroy.argtypes = [NotmuchMessageP] + _destroy.restype = None + def __del__(self): """Close and free the notmuch filenames""" if self._files is not None: - nmlib.notmuch_filenames_destroy(self._files) + self._destroy(self._files) diff --git a/bindings/python/notmuch/globals.py b/bindings/python/notmuch/globals.py index de1db161..54a49b2d 100644 --- a/bindings/python/notmuch/globals.py +++ b/bindings/python/notmuch/globals.py @@ -17,8 +17,7 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ -from ctypes import CDLL, c_char_p, c_int -from ctypes.util import find_library +from ctypes import CDLL, c_char_p, c_int, Structure, POINTER #----------------------------------------------------------------------------- #package-global instance of the notmuch library @@ -49,11 +48,11 @@ class Status(Enum): @classmethod def status2str(self, status): - """Get a string representation of a notmuch_status_t value.""" + """Get a (unicode) string representation of a notmuch_status_t value.""" # define strings for custom error messages if status == STATUS.NOT_INITIALIZED: - return "Operation on uninitialized object impossible." - return str(Status._status2str(status)) + return u"Operation on uninitialized object impossible." + return unicode(Status._status2str(status)) STATUS = Status(['SUCCESS', 'OUT_OF_MEMORY', @@ -89,6 +88,7 @@ Invoke the class method `notmuch.STATUS.status2str` with a status value as argument to receive a human readable string""" STATUS.__name__ = 'STATUS' + class NotmuchError(Exception): """Is initiated with a (notmuch.STATUS[, message=None]). It will not return an instance of the class NotmuchError, but a derived instance @@ -97,7 +97,8 @@ class NotmuchError(Exception): @classmethod def get_exc_subclass(cls, status): - """Returns a fine grained Exception() type,detailing the error status""" + """Returns a fine grained Exception() type, + detailing the error status""" subclasses = { STATUS.OUT_OF_MEMORY: OutOfMemoryError, STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError, @@ -109,7 +110,7 @@ class NotmuchError(Exception): STATUS.TAG_TOO_LONG: TagTooLongError, STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError, STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError, - STATUS.NOT_INITIALIZED: NotInitializedError + STATUS.NOT_INITIALIZED: NotInitializedError, } assert 0 < status <= len(subclasses) return subclasses[status] @@ -125,7 +126,7 @@ class NotmuchError(Exception): # no 'status' or cls is subclass already, return 'cls' instance if not status or cls != NotmuchError: return super(NotmuchError, cls).__new__(cls) - subclass = cls.get_exc_subclass(status) # which class to use? + subclass = cls.get_exc_subclass(status) # which class to use? return subclass.__new__(subclass, *args, **kwargs) def __init__(self, status=None, message=None): @@ -133,35 +134,59 @@ class NotmuchError(Exception): self.message = message def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): if self.message is not None: return self.message elif self.status is not None: return STATUS.status2str(self.status) else: - return 'Unknown error' + return u'Unknown error' + # List of Subclassed exceptions that correspond to STATUS values and are # subclasses of NotmuchError. class OutOfMemoryError(NotmuchError): status = STATUS.OUT_OF_MEMORY + + class ReadOnlyDatabaseError(NotmuchError): status = STATUS.READ_ONLY_DATABASE + + class XapianError(NotmuchError): status = STATUS.XAPIAN_EXCEPTION + + class FileError(NotmuchError): status = STATUS.FILE_ERROR + + class FileNotEmailError(NotmuchError): status = STATUS.FILE_NOT_EMAIL + + class DuplicateMessageIdError(NotmuchError): status = STATUS.DUPLICATE_MESSAGE_ID + + class NullPointerError(NotmuchError): status = STATUS.NULL_POINTER + + class TagTooLongError(NotmuchError): status = STATUS.TAG_TOO_LONG + + class UnbalancedFreezeThawError(NotmuchError): status = STATUS.UNBALANCED_FREEZE_THAW + + class UnbalancedAtomicError(NotmuchError): status = STATUS.UNBALANCED_ATOMIC + + class NotInitializedError(NotmuchError): """Derived from NotmuchError, this occurs if the underlying data structure (e.g. database is not initialized (yet) or an iterator has @@ -170,7 +195,6 @@ class NotInitializedError(NotmuchError): status = STATUS.NOT_INITIALIZED - def _str(value): """Ensure a nicely utf-8 encoded string to pass to libnotmuch @@ -182,3 +206,47 @@ def _str(value): return value.encode('UTF-8') return value + +class NotmuchDatabaseS(Structure): + pass +NotmuchDatabaseP = POINTER(NotmuchDatabaseS) + + +class NotmuchQueryS(Structure): + pass +NotmuchQueryP = POINTER(NotmuchQueryS) + + +class NotmuchThreadsS(Structure): + pass +NotmuchThreadsP = POINTER(NotmuchThreadsS) + + +class NotmuchThreadS(Structure): + pass +NotmuchThreadP = POINTER(NotmuchThreadS) + + +class NotmuchMessagesS(Structure): + pass +NotmuchMessagesP = POINTER(NotmuchMessagesS) + + +class NotmuchMessageS(Structure): + pass +NotmuchMessageP = POINTER(NotmuchMessageS) + + +class NotmuchTagsS(Structure): + pass +NotmuchTagsP = POINTER(NotmuchTagsS) + + +class NotmuchDirectoryS(Structure): + pass +NotmuchDirectoryP = POINTER(NotmuchDirectoryS) + + +class NotmuchFilenamesS(Structure): + pass +NotmuchFilenamesP = POINTER(NotmuchFilenamesS) diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index 4bf90c22..ce8e7181 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -19,14 +19,14 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ -from ctypes import c_char_p, c_void_p, c_long, c_uint, c_int +from ctypes import c_char_p, c_long, c_uint, c_int from datetime import date -from notmuch.globals import nmlib, STATUS, NotmuchError, Enum, _str +from notmuch.globals import (nmlib, STATUS, NotmuchError, Enum, _str, + NotmuchTagsP, NotmuchMessagesP, NotmuchMessageP, NotmuchFilenamesP) from notmuch.tag import Tags from notmuch.filename import Filenames import sys import email -import types try: import simplejson as json except ImportError: @@ -92,10 +92,12 @@ class Messages(object): #notmuch_messages_get _get = nmlib.notmuch_messages_get - _get.restype = c_void_p + _get.argtypes = [NotmuchMessagesP] + _get.restype = NotmuchMessageP _collect_tags = nmlib.notmuch_messages_collect_tags - _collect_tags.restype = c_void_p + _collect_tags.argtypes = [NotmuchMessagesP] + _collect_tags.restype = NotmuchTagsP def __init__(self, msgs_p, parent=None): """ @@ -125,10 +127,12 @@ class Messages(object): """Return the unique :class:`Tags` in the contained messages :returns: :class:`Tags` - :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited + :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not init'ed - .. note:: :meth:`collect_tags` will iterate over the messages and - therefore will not allow further iterations. + .. note:: + + :meth:`collect_tags` will iterate over the messages and therefore + will not allow further iterations. """ if self._msgs is None: raise NotmuchError(STATUS.NOT_INITIALIZED) @@ -146,16 +150,24 @@ class Messages(object): """ Make Messages an iterator """ return self + _valid = nmlib.notmuch_messages_valid + _valid.argtypes = [NotmuchMessagesP] + _valid.restype = bool + + _move_to_next = nmlib.notmuch_messages_move_to_next + _move_to_next.argtypes = [NotmuchMessagesP] + _move_to_next.restype = None + def next(self): if self._msgs is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - if not nmlib.notmuch_messages_valid(self._msgs): + if not self._valid(self._msgs): self._msgs = None raise StopIteration msg = Message(Messages._get(self._msgs), self) - nmlib.notmuch_messages_move_to_next(self._msgs) + self._move_to_next(self._msgs) return msg def __nonzero__(self): @@ -163,12 +175,16 @@ class Messages(object): :return: True if there is at least one more thread in the Iterator, False if not.""" return self._msgs is not None and \ - nmlib.notmuch_messages_valid(self._msgs) > 0 + self._valid(self._msgs) > 0 + + _destroy = nmlib.notmuch_messages_destroy + _destroy.argtypes = [NotmuchMessagesP] + _destroy.restype = None def __del__(self): """Close and free the notmuch Messages""" if self._msgs is not None: - nmlib.notmuch_messages_destroy(self._msgs) + self._destroy(self._msgs) def print_messages(self, format, indent=0, entire_thread=False): """Outputs messages as needed for 'notmuch show' to sys.stdout @@ -235,44 +251,60 @@ class Message(object): """notmuch_message_get_filename (notmuch_message_t *message)""" _get_filename = nmlib.notmuch_message_get_filename + _get_filename.argtypes = [NotmuchMessageP] _get_filename.restype = c_char_p """return all filenames for a message""" _get_filenames = nmlib.notmuch_message_get_filenames - _get_filenames.restype = c_void_p + _get_filenames.argtypes = [NotmuchMessageP] + _get_filenames.restype = NotmuchFilenamesP """notmuch_message_get_flag""" _get_flag = nmlib.notmuch_message_get_flag - _get_flag.restype = c_uint + _get_flag.argtypes = [NotmuchMessageP, c_uint] + _get_flag.restype = bool + + """notmuch_message_set_flag""" + _set_flag = nmlib.notmuch_message_set_flag + _set_flag.argtypes = [NotmuchMessageP, c_uint, c_int] + _set_flag.restype = None """notmuch_message_get_message_id (notmuch_message_t *message)""" _get_message_id = nmlib.notmuch_message_get_message_id + _get_message_id.argtypes = [NotmuchMessageP] _get_message_id.restype = c_char_p """notmuch_message_get_thread_id""" _get_thread_id = nmlib.notmuch_message_get_thread_id + _get_thread_id.argtypes = [NotmuchMessageP] _get_thread_id.restype = c_char_p """notmuch_message_get_replies""" _get_replies = nmlib.notmuch_message_get_replies - _get_replies.restype = c_void_p + _get_replies.argtypes = [NotmuchMessageP] + _get_replies.restype = NotmuchMessagesP """notmuch_message_get_tags (notmuch_message_t *message)""" _get_tags = nmlib.notmuch_message_get_tags - _get_tags.restype = c_void_p + _get_tags.argtypes = [NotmuchMessageP] + _get_tags.restype = NotmuchTagsP _get_date = nmlib.notmuch_message_get_date + _get_date.argtypes = [NotmuchMessageP] _get_date.restype = c_long _get_header = nmlib.notmuch_message_get_header + _get_header.argtypes = [NotmuchMessageP, c_char_p] _get_header.restype = c_char_p """notmuch_status_t ..._maildir_flags_to_tags (notmuch_message_t *)""" _tags_to_maildir_flags = nmlib.notmuch_message_tags_to_maildir_flags + _tags_to_maildir_flags.argtypes = [NotmuchMessageP] _tags_to_maildir_flags.restype = c_int """notmuch_status_t ..._tags_to_maildir_flags (notmuch_message_t *)""" _maildir_flags_to_tags = nmlib.notmuch_message_maildir_flags_to_tags + _maildir_flags_to_tags.argtypes = [NotmuchMessageP] _maildir_flags_to_tags.restype = c_int #Constants: Flags that can be set/get with set_flag @@ -328,14 +360,15 @@ class Message(object): """Gets all direct replies to this message as :class:`Messages` iterator - .. note:: This call only makes sense if 'message' was - ultimately obtained from a :class:`Thread` object, (such as - by coming directly from the result of calling - :meth:`Thread.get_toplevel_messages` or by any number of - subsequent calls to :meth:`get_replies`). If this message was - obtained through some non-thread means, (such as by a call - to :meth:`Query.search_messages`), then this function will - return `None`. + .. note:: + + This call only makes sense if 'message' was ultimately obtained from + a :class:`Thread` object, (such as by coming directly from the + result of calling :meth:`Thread.get_toplevel_messages` or by any + number of subsequent calls to :meth:`get_replies`). If this message + was obtained through some non-thread means, (such as by a call to + :meth:`Query.search_messages`), then this function will return + `None`. :returns: :class:`Messages` or `None` if there are no replies to this message. @@ -394,7 +427,7 @@ class Message(object): header = Message._get_header(self._msg, header) if header == None: raise NotmuchError(STATUS.NULL_POINTER) - return header.decode('UTF-8') + return header.decode('UTF-8', errors='ignore') def get_filename(self): """Returns the file path of the message file @@ -450,7 +483,7 @@ class Message(object): """ if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - nmlib.notmuch_message_set_flag(self._msg, flag, value) + self._set_flag(self._msg, flag, value) def get_tags(self): """Returns the message tags @@ -470,6 +503,10 @@ class Message(object): raise NotmuchError(STATUS.NULL_POINTER) return Tags(tags_p, self) + _add_tag = nmlib.notmuch_message_add_tag + _add_tag.argtypes = [NotmuchMessageP, c_char_p] + _add_tag.restype = c_uint + def add_tag(self, tag, sync_maildir_flags=False): """Adds a tag to the given message @@ -504,7 +541,7 @@ class Message(object): if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - status = nmlib.notmuch_message_add_tag(self._msg, _str(tag)) + status = self._add_tag(self._msg, _str(tag)) # bail out on failure if status != STATUS.SUCCESS: @@ -514,6 +551,10 @@ class Message(object): self.tags_to_maildir_flags() return STATUS.SUCCESS + _remove_tag = nmlib.notmuch_message_remove_tag + _remove_tag.argtypes = [NotmuchMessageP, c_char_p] + _remove_tag.restype = c_uint + def remove_tag(self, tag, sync_maildir_flags=False): """Removes a tag from the given message @@ -548,7 +589,7 @@ class Message(object): if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - status = nmlib.notmuch_message_remove_tag(self._msg, _str(tag)) + status = self._remove_tag(self._msg, _str(tag)) # bail out on error if status != STATUS.SUCCESS: raise NotmuchError(status) @@ -557,6 +598,10 @@ class Message(object): self.tags_to_maildir_flags() return STATUS.SUCCESS + _remove_all_tags = nmlib.notmuch_message_remove_all_tags + _remove_all_tags.argtypes = [NotmuchMessageP] + _remove_all_tags.restype = c_uint + def remove_all_tags(self, sync_maildir_flags=False): """Removes all tags from the given message. @@ -585,7 +630,7 @@ class Message(object): if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - status = nmlib.notmuch_message_remove_all_tags(self._msg) + status = self._remove_all_tags(self._msg) # bail out on error if status != STATUS.SUCCESS: @@ -595,12 +640,16 @@ class Message(object): self.tags_to_maildir_flags() return STATUS.SUCCESS + _freeze = nmlib.notmuch_message_freeze + _freeze.argtypes = [NotmuchMessageP] + _freeze.restype = c_uint + def freeze(self): """Freezes the current state of 'message' within the database This means that changes to the message state, (via :meth:`add_tag`, :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be - committed to the database until the message is :meth:`thaw`ed. + committed to the database until the message is :meth:`thaw` ed. Multiple calls to freeze/thaw are valid and these calls will "stack". That is there must be as many calls to thaw as to freeze @@ -639,7 +688,7 @@ class Message(object): if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - status = nmlib.notmuch_message_freeze(self._msg) + status = self._freeze(self._msg) if STATUS.SUCCESS == status: # return on success @@ -647,6 +696,10 @@ class Message(object): raise NotmuchError(status) + _thaw = nmlib.notmuch_message_thaw + _thaw.argtypes = [NotmuchMessageP] + _thaw.restype = c_uint + def thaw(self): """Thaws the current 'message' @@ -674,7 +727,7 @@ class Message(object): if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - status = nmlib.notmuch_message_thaw(self._msg) + status = self._thaw(self._msg) if STATUS.SUCCESS == status: # return on success @@ -705,11 +758,11 @@ class Message(object): not work yet, as the modified tags have not been committed yet to the database. - :returns: a :class:`STATUS`. In short, you want to see + :returns: a :class:`STATUS` value. In short, you want to see notmuch.STATUS.SUCCESS here. See there for details.""" if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - status = Message._tags_to_maildir_flags(self._msg) + return Message._tags_to_maildir_flags(self._msg) def maildir_flags_to_tags(self): """Synchronize file Maildir flags to notmuch tags @@ -736,19 +789,21 @@ class Message(object): notmuch.STATUS.SUCCESS here. See there for details.""" if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - status = Message._tags_to_maildir_flags(self._msg) + return Message._tags_to_maildir_flags(self._msg) def __repr__(self): """Represent a Message() object by str()""" return self.__str__() def __str__(self): - """A message() is represented by a 1-line summary""" - msg = {} - msg['from'] = self.get_header('from') - msg['tags'] = self.get_tags() - msg['date'] = date.fromtimestamp(self.get_date()) - return "%(from)s (%(date)s) (%(tags)s)" % (msg) + return unicode(self).encode('utf-8') + + def __unicode__(self): + format = "%s (%s) (%s)" + return format % (self.get_header('from'), + self.get_tags(), + date.fromtimestamp(self.get_date()), + ) def get_message_parts(self): """Output like notmuch show""" @@ -896,7 +951,11 @@ class Message(object): res = cmp(list(self.get_filenames()), list(other.get_filenames())) return res + _destroy = nmlib.notmuch_message_destroy + _destroy.argtypes = [NotmuchMessageP] + _destroy.restype = None + def __del__(self): """Close and free the notmuch Message""" if self._msg is not None: - nmlib.notmuch_message_destroy(self._msg) + self._destroy(self._msg) diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py index 50e3686b..2fb7d328 100644 --- a/bindings/python/notmuch/tag.py +++ b/bindings/python/notmuch/tag.py @@ -17,7 +17,7 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ from ctypes import c_char_p -from notmuch.globals import nmlib, STATUS, NotmuchError +from notmuch.globals import nmlib, STATUS, NotmuchError, NotmuchTagsP class Tags(object): @@ -50,6 +50,7 @@ class Tags(object): #notmuch_tags_get _get = nmlib.notmuch_tags_get + _get.argtypes = [NotmuchTagsP] _get.restype = c_char_p def __init__(self, tags_p, parent=None): @@ -80,14 +81,22 @@ class Tags(object): """ Make Tags an iterator """ return self + _valid = nmlib.notmuch_tags_valid + _valid.argtypes = [NotmuchTagsP] + _valid.restype = bool + + _move_to_next = nmlib.notmuch_tags_move_to_next + _move_to_next.argtypes = [NotmuchTagsP] + _move_to_next.restype = None + def next(self): if self._tags is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - if not nmlib.notmuch_tags_valid(self._tags): + if not self._valid(self._tags): self._tags = None raise StopIteration tag = Tags._get(self._tags).decode('UTF-8') - nmlib.notmuch_tags_move_to_next(self._tags) + self._move_to_next(self._tags) return tag def __nonzero__(self): @@ -99,20 +108,28 @@ class Tags(object): :returns: True if the Tags() iterator has at least one more Tag left.""" - return nmlib.notmuch_tags_valid(self._tags) > 0 + return self._valid(self._tags) > 0 def __str__(self): - """The str() representation of Tags() is a space separated list of tags + return unicode(self).encode('utf-8') + + def __unicode__(self): + """string representation of :class:`Tags`: a space separated list of tags - .. note:: As this iterates over the tags, we will not be able - to iterate over them again (as in retrieve them)! If - the tags have been exhausted already, this will raise a - :exc:`NotmuchError` STATUS.NOT_INITIALIZED on - subsequent attempts. + .. note:: + + As this iterates over the tags, we will not be able to iterate over + them again (as in retrieve them)! If the tags have been exhausted + already, this will raise a :exc:`NotmuchError` + STATUS.NOT_INITIALIZED on subsequent attempts. """ return " ".join(self) + _destroy = nmlib.notmuch_tags_destroy + _destroy.argtypes = [NotmuchTagsP] + _destroy.restype = None + def __del__(self): """Close and free the notmuch tags""" if self._tags is not None: - nmlib.notmuch_tags_destroy(self._tags) + self._destroy(self._tags) diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index 5e08eb31..5058846d 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -17,8 +17,10 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ -from ctypes import c_char_p, c_void_p, c_long -from notmuch.globals import nmlib, STATUS, NotmuchError +from ctypes import c_char_p, c_long, c_int +from notmuch.globals import (nmlib, STATUS, + NotmuchError, NotmuchThreadP, NotmuchThreadsP, NotmuchMessagesP, + NotmuchTagsP,) from notmuch.message import Messages from notmuch.tag import Tags from datetime import date @@ -75,7 +77,8 @@ class Threads(object): #notmuch_threads_get _get = nmlib.notmuch_threads_get - _get.restype = c_void_p + _get.argtypes = [NotmuchThreadsP] + _get.restype = NotmuchThreadP def __init__(self, threads_p, parent=None): """ @@ -105,16 +108,24 @@ class Threads(object): """ Make Threads an iterator """ return self + _valid = nmlib.notmuch_threads_valid + _valid.argtypes = [NotmuchThreadsP] + _valid.restype = bool + + _move_to_next = nmlib.notmuch_threads_move_to_next + _move_to_next.argtypes = [NotmuchThreadsP] + _move_to_next.restype = None + def next(self): if self._threads is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - if not nmlib.notmuch_threads_valid(self._threads): + if not self._valid(self._threads): self._threads = None raise StopIteration thread = Thread(Threads._get(self._threads), self) - nmlib.notmuch_threads_move_to_next(self._threads) + self._move_to_next(self._threads) return thread def __len__(self): @@ -134,8 +145,8 @@ class Threads(object): i = 0 # returns 'bool'. On out-of-memory it returns None - while nmlib.notmuch_threads_valid(self._threads): - nmlib.notmuch_threads_move_to_next(self._threads) + while self._valid(self._threads): + self._move_to_next(self._threads) i += 1 # reset self._threads to mark as "exhausted" self._threads = None @@ -153,12 +164,16 @@ class Threads(object): Iterator, False if not. None on a "Out-of-memory" error. """ return self._threads is not None and \ - nmlib.notmuch_threads_valid(self._threads) > 0 + self._valid(self._threads) > 0 + + _destroy = nmlib.notmuch_threads_destroy + _destroy.argtypes = [NotmuchThreadsP] + _destroy.argtypes = None def __del__(self): """Close and free the notmuch Threads""" if self._threads is not None: - nmlib.notmuch_messages_destroy(self._threads) + self._destroy(self._threads) class Thread(object): @@ -166,29 +181,36 @@ class Thread(object): """notmuch_thread_get_thread_id""" _get_thread_id = nmlib.notmuch_thread_get_thread_id + _get_thread_id.argtypes = [NotmuchThreadP] _get_thread_id.restype = c_char_p """notmuch_thread_get_authors""" _get_authors = nmlib.notmuch_thread_get_authors + _get_authors.argtypes = [NotmuchThreadP] _get_authors.restype = c_char_p """notmuch_thread_get_subject""" _get_subject = nmlib.notmuch_thread_get_subject + _get_subject.argtypes = [NotmuchThreadP] _get_subject.restype = c_char_p """notmuch_thread_get_toplevel_messages""" _get_toplevel_messages = nmlib.notmuch_thread_get_toplevel_messages - _get_toplevel_messages.restype = c_void_p + _get_toplevel_messages.argtypes = [NotmuchThreadP] + _get_toplevel_messages.restype = NotmuchMessagesP _get_newest_date = nmlib.notmuch_thread_get_newest_date + _get_newest_date.argtypes = [NotmuchThreadP] _get_newest_date.restype = c_long _get_oldest_date = nmlib.notmuch_thread_get_oldest_date + _get_oldest_date.argtypes = [NotmuchThreadP] _get_oldest_date.restype = c_long """notmuch_thread_get_tags""" _get_tags = nmlib.notmuch_thread_get_tags - _get_tags.restype = c_void_p + _get_tags.argtypes = [NotmuchThreadP] + _get_tags.restype = NotmuchTagsP def __init__(self, thread_p, parent=None): """ @@ -225,6 +247,10 @@ class Thread(object): raise NotmuchError(STATUS.NOT_INITIALIZED) return Thread._get_thread_id(self._thread) + _get_total_messages = nmlib.notmuch_thread_get_total_messages + _get_total_messages.argtypes = [NotmuchThreadP] + _get_total_messages.restype = c_int + def get_total_messages(self): """Get the total number of messages in 'thread' @@ -236,7 +262,7 @@ class Thread(object): """ if self._thread is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - return nmlib.notmuch_thread_get_total_messages(self._thread) + return self._get_total_messages(self._thread) def get_toplevel_messages(self): """Returns a :class:`Messages` iterator for the top-level messages in @@ -267,6 +293,10 @@ class Thread(object): return Messages(msgs_p, self) + _get_matched_messages = nmlib.notmuch_thread_get_matched_messages + _get_matched_messages.argtypes = [NotmuchThreadP] + _get_matched_messages.restype = c_int + def get_matched_messages(self): """Returns the number of messages in 'thread' that matched the query @@ -278,7 +308,7 @@ class Thread(object): """ if self._thread is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - return nmlib.notmuch_thread_get_matched_messages(self._thread) + return self._get_matched_messages(self._thread) def get_authors(self): """Returns the authors of 'thread' @@ -295,7 +325,7 @@ class Thread(object): authors = Thread._get_authors(self._thread) if authors is None: return None - return authors.decode('UTF-8') + return authors.decode('UTF-8', errors='ignore') def get_subject(self): """Returns the Subject of 'thread' @@ -308,7 +338,7 @@ class Thread(object): subject = Thread._get_subject(self._thread) if subject is None: return None - return subject.decode('UTF-8') + return subject.decode('UTF-8', errors='ignore') def get_newest_date(self): """Returns time_t of the newest message date @@ -362,32 +392,25 @@ class Thread(object): return Tags(tags_p, self) def __str__(self): - """A str(Thread()) is represented by a 1-line summary""" - thread = {} - thread['id'] = self.get_thread_id() + return unicode(self).encode('utf-8') + + def __unicode__(self): + frm = "thread:%s %12s [%d/%d] %s; %s (%s)" - ###TODO: How do we find out the current sort order of Threads? - ###Add a "sort" attribute to the Threads() object? - #if (sort == NOTMUCH_SORT_OLDEST_FIRST) - # date = notmuch_thread_get_oldest_date (thread); - #else - # date = notmuch_thread_get_newest_date (thread); - thread['date'] = date.fromtimestamp(self.get_newest_date()) - thread['matched'] = self.get_matched_messages() - thread['total'] = self.get_total_messages() - thread['authors'] = self.get_authors() - thread['subject'] = self.get_subject() - thread['tags'] = self.get_tags() + return frm % (self.get_thread_id(), + date.fromtimestamp(self.get_newest_date()), + self.get_matched_messages(), + self.get_total_messages(), + self.get_authors(), + self.get_subject(), + self.get_tags(), + ) - return "thread:%s %12s [%d/%d] %s; %s (%s)" % (thread['id'], - thread['date'], - thread['matched'], - thread['total'], - thread['authors'], - thread['subject'], - thread['tags']) + _destroy = nmlib.notmuch_thread_destroy + _destroy.argtypes = [NotmuchThreadP] + _destroy.restype = None def __del__(self): """Close and free the notmuch Thread""" if self._thread is not None: - nmlib.notmuch_thread_destroy(self._thread) + self._destroy(self._thread) diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index fd414b0a..59c396fe 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,2 +1,2 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.10.2' +__VERSION__ = '0.11' diff --git a/command-line-arguments.c b/command-line-arguments.c new file mode 100644 index 00000000..e7114143 --- /dev/null +++ b/command-line-arguments.c @@ -0,0 +1,155 @@ +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include "error_util.h" +#include "command-line-arguments.h" + +/* + Search the array of keywords for a given argument, assigning the + output variable to the corresponding value. Return FALSE if nothing + matches. +*/ + +static notmuch_bool_t +_process_keyword_arg (const notmuch_opt_desc_t *arg_desc, const char *arg_str) { + + const notmuch_keyword_t *keywords = arg_desc->keywords; + + while (keywords->name) { + if (strcmp (arg_str, keywords->name) == 0) { + if (arg_desc->output_var) { + *((int *)arg_desc->output_var) = keywords->value; + } + return TRUE; + } + keywords++; + } + fprintf (stderr, "unknown keyword: %s\n", arg_str); + return FALSE; +} + +/* + Search for the {pos_arg_index}th position argument, return FALSE if + that does not exist. +*/ + +notmuch_bool_t +parse_position_arg (const char *arg_str, int pos_arg_index, + const notmuch_opt_desc_t *arg_desc) { + + int pos_arg_counter = 0; + while (arg_desc->opt_type != NOTMUCH_OPT_END){ + if (arg_desc->opt_type == NOTMUCH_OPT_POSITION) { + if (pos_arg_counter == pos_arg_index) { + if (arg_desc->output_var) { + *((const char **)arg_desc->output_var) = arg_str; + } + return TRUE; + } + pos_arg_counter++; + } + arg_desc++; + } + return FALSE; +} + +/* + * Search for a non-positional (i.e. starting with --) argument matching arg, + * parse a possible value, and assign to *output_var + */ + +notmuch_bool_t +parse_option (const char *arg, + const notmuch_opt_desc_t *options) { + + assert(arg); + assert(options); + + arg += 2; + + const notmuch_opt_desc_t *try = options; + while (try->opt_type != NOTMUCH_OPT_END) { + if (try->name && strncmp (arg, try->name, strlen (try->name)) == 0) { + char next = arg[strlen (try->name)]; + const char *value= arg+strlen(try->name)+1; + + char *endptr; + + /* Everything but boolean arguments (switches) needs a + * delimiter, and a non-zero length value + */ + + if (try->opt_type != NOTMUCH_OPT_BOOLEAN) { + if (next != '=' && next != ':') return FALSE; + if (value[0] == 0) return FALSE; + } else { + if (next != 0) return FALSE; + } + + if (try->output_var == NULL) + INTERNAL_ERROR ("output pointer NULL for option %s", try->name); + + switch (try->opt_type) { + case NOTMUCH_OPT_KEYWORD: + return _process_keyword_arg (try, value); + break; + case NOTMUCH_OPT_BOOLEAN: + *((notmuch_bool_t *)try->output_var) = TRUE; + return TRUE; + break; + case NOTMUCH_OPT_INT: + *((int *)try->output_var) = strtol (value, &endptr, 10); + return (*endptr == 0); + break; + case NOTMUCH_OPT_STRING: + *((const char **)try->output_var) = value; + return TRUE; + break; + case NOTMUCH_OPT_POSITION: + case NOTMUCH_OPT_END: + default: + INTERNAL_ERROR ("unknown or unhandled option type %d", try->opt_type); + /*UNREACHED*/ + } + } + try++; + } + fprintf (stderr, "Unrecognized option: --%s\n", arg); + return FALSE; +} + +/* See command-line-arguments.h for description */ +int +parse_arguments (int argc, char **argv, + const notmuch_opt_desc_t *options, int opt_index) { + + int pos_arg_index = 0; + notmuch_bool_t more_args = TRUE; + + while (more_args && opt_index < argc) { + if (strncmp (argv[opt_index],"--",2) != 0) { + + more_args = parse_position_arg (argv[opt_index], pos_arg_index, options); + + if (more_args) { + pos_arg_index++; + opt_index++; + } + + } else { + + if (strlen (argv[opt_index]) == 2) + return opt_index+1; + + more_args = parse_option (argv[opt_index], options); + if (more_args) { + opt_index++; + } else { + opt_index = -1; + } + + } + } + + return opt_index; +} diff --git a/command-line-arguments.h b/command-line-arguments.h new file mode 100644 index 00000000..de1734ad --- /dev/null +++ b/command-line-arguments.h @@ -0,0 +1,80 @@ +#ifndef NOTMUCH_OPTS_H +#define NOTMUCH_OPTS_H + +#include "notmuch.h" + +enum notmuch_opt_type { + NOTMUCH_OPT_END = 0, + NOTMUCH_OPT_BOOLEAN, /* --verbose */ + NOTMUCH_OPT_INT, /* --frob=8 */ + NOTMUCH_OPT_KEYWORD, /* --format=raw|json|text */ + NOTMUCH_OPT_STRING, /* --file=/tmp/gnarf.txt */ + NOTMUCH_OPT_POSITION /* notmuch dump pos_arg */ +}; + +/* + * Describe one of the possibilities for a keyword option + * 'value' will be copied to the output variable + */ + +typedef struct notmuch_keyword { + const char *name; + int value; +} notmuch_keyword_t; + +/* + * Describe one option. + * + * First two parameters are mandatory. + * + * name is mandatory _except_ for positional arguments. + * + * arg_id is currently unused, but could define short arguments. + * + * keywords is a (possibly NULL) pointer to an array of keywords + */ +typedef struct notmuch_opt_desc { + enum notmuch_opt_type opt_type; + void *output_var; + const char *name; + int arg_id; + const struct notmuch_keyword *keywords; +} notmuch_opt_desc_t; + + +/* + 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); + +/* + * If the argument parsing loop provided by parse_arguments is not + * flexible enough, then the user might be interested in the following + * routines, but note that the API to parse_option might have to + * change. See command-line-arguments.c for descriptions of these + * functions. + */ + +notmuch_bool_t +parse_option (const char *arg, const notmuch_opt_desc_t* options); + +notmuch_bool_t +parse_position_arg (const char *arg, + int position_arg_index, + const notmuch_opt_desc_t* options); + + +#endif diff --git a/contrib/.gitattributes b/contrib/.gitattributes deleted file mode 100644 index 3d57a081..00000000 --- a/contrib/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -notmuch-deliver export-ignore diff --git a/contrib/notmuch-deliver/COPYING b/contrib/notmuch-deliver/COPYING deleted file mode 100644 index 3912109b..00000000 --- a/contrib/notmuch-deliver/COPYING +++ /dev/null @@ -1,340 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - <one line to give the program's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - 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 2 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, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - <signature of Ty Coon>, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff --git a/contrib/notmuch-deliver/README.mkd b/contrib/notmuch-deliver/README.mkd index f1f2144f..06268bd8 100644 --- a/contrib/notmuch-deliver/README.mkd +++ b/contrib/notmuch-deliver/README.mkd @@ -38,12 +38,6 @@ My personal e-mail address is [alip@exherbo.org](mailto:alip@exherbo.org). I'm available on IRC as `alip` on [Freenode](http://freenode.net) and [OFTC](http://www.oftc.net). ## License -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 of the License. - -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 may redistribute this under the same terms as notmuch itself. <!-- vim: set ft=mkd spell spelllang=en sw=4 sts=4 et : --> diff --git a/contrib/notmuch-deliver/maildrop/maildir/Makefile.am b/contrib/notmuch-deliver/maildrop/maildir/Makefile.am index 2495a83b..6fd0ae49 100644 --- a/contrib/notmuch-deliver/maildrop/maildir/Makefile.am +++ b/contrib/notmuch-deliver/maildrop/maildir/Makefile.am @@ -1,4 +1,3 @@ -#$Id: Makefile.am,v 1.59 2009/06/27 16:32:38 mrsam Exp $ # # Copyright 1998 - 2005 Double Precision, Inc. See COPYING for # distribution information. diff --git a/contrib/notmuch-deliver/maildrop/maildir/configure.in b/contrib/notmuch-deliver/maildrop/maildir/configure.in index 1e724443..dfdd22a3 100644 --- a/contrib/notmuch-deliver/maildrop/maildir/configure.in +++ b/contrib/notmuch-deliver/maildrop/maildir/configure.in @@ -1,4 +1,3 @@ -dnl $Id: configure.in,v 1.37 2008/11/27 21:51:16 mrsam Exp $ dnl Process this file with autoconf to produce a configure script. dnl dnl Copyright 1998 - 2001 Double Precision, Inc. See COPYING for @@ -67,6 +66,7 @@ AC_TYPE_OFF_T AC_TYPE_SIZE_T AC_TYPE_UID_T AC_STRUCT_TM +AC_SYS_LARGEFILE dnl Checks for library functions. AC_CHECK_HEADER(fam.h, :, :) diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c b/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c index 74030f41..5efc0afb 100644 --- a/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c +++ b/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c @@ -35,7 +35,6 @@ #include "numlib/numlib.h" -static const char rcsid[]="$Id: maildircreate.c,v 1.6 2003/01/26 04:07:03 mrsam Exp $"; FILE *maildir_tmpcreate_fp(struct maildir_tmpcreate_info *info) { diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h b/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h index ea1c71ac..85391454 100644 --- a/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h +++ b/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h @@ -16,7 +16,6 @@ extern "C" { #endif -static const char maildircreate_h_rcsid[]="$Id: maildircreate.h,v 1.10 2006/10/29 00:03:53 mrsam Exp $"; /* Create messages in maildirs */ diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h b/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h index 545d11e1..e1e7c19a 100644 --- a/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h +++ b/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h @@ -18,7 +18,6 @@ extern "C" { #endif -static const char maildirmisc_h_rcsid[]="$Id: maildirmisc.h,v 1.18 2006/07/22 02:48:15 mrsam Exp $"; /* ** diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c b/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c index 754b2c70..28a3ac21 100644 --- a/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c +++ b/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c @@ -18,7 +18,6 @@ #include "maildirmisc.h" -static const char rcsid[]="$Id: maildirmkdir.c,v 1.2 2002/03/15 03:09:21 mrsam Exp $"; int maildir_mkdir(const char *dir) { diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c b/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c index 5071df76..25428737 100644 --- a/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c +++ b/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c @@ -22,7 +22,6 @@ #include "maildirmisc.h" -static const char rcsid[]="$Id: maildiropen.c,v 1.8 2003/01/19 16:39:52 mrsam Exp $"; char *maildir_getlink(const char *filename) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/Makefile.am b/contrib/notmuch-deliver/maildrop/numlib/Makefile.am index c0f129fa..0a5f103d 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/Makefile.am +++ b/contrib/notmuch-deliver/maildrop/numlib/Makefile.am @@ -1,4 +1,3 @@ -# $Id: Makefile.am,v 1.12 2007/06/30 15:40:53 mrsam Exp $ # # Copyright 1998 - 2004 Double Precision, Inc. See COPYING for # distribution information. diff --git a/contrib/notmuch-deliver/maildrop/numlib/atotimet.c b/contrib/notmuch-deliver/maildrop/numlib/atotimet.c index d494fd22..0360b5b2 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/atotimet.c +++ b/contrib/notmuch-deliver/maildrop/numlib/atotimet.c @@ -9,6 +9,5 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: atotimet.c,v 1.1 2003/08/03 03:09:19 mrsam Exp $"; LIBMAIL_STRIMPL(time_t, libmail_strtotime_t, libmail_atotime_t) diff --git a/contrib/notmuch-deliver/maildrop/numlib/atouidt.c b/contrib/notmuch-deliver/maildrop/numlib/atouidt.c index 3c01ecf5..6f869d54 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/atouidt.c +++ b/contrib/notmuch-deliver/maildrop/numlib/atouidt.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: atouidt.c,v 1.1 2004/01/11 02:47:33 mrsam Exp $"; LIBMAIL_STRIMPL(uid_t, libmail_strtouid_t, libmail_atouid_t) LIBMAIL_STRIMPL(gid_t, libmail_strtogid_t, libmail_atogid_t) diff --git a/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c b/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c index 56793927..adaee406 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c +++ b/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c @@ -19,7 +19,6 @@ #include "numlib.h" -static const char rcsid[]="$Id: changeuidgid.c,v 1.2 2003/01/05 04:01:17 mrsam Exp $"; void libmail_changegroup(gid_t gid) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/configure.in b/contrib/notmuch-deliver/maildrop/numlib/configure.in index fc977aa9..7479725b 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/configure.in +++ b/contrib/notmuch-deliver/maildrop/numlib/configure.in @@ -1,5 +1,4 @@ dnl Process this file with autoconf to produce a configure script. -dnl $Id: configure.in,v 1.7 2010/03/19 01:09:26 mrsam Exp $ dnl dnl Copyright 1998 - 2010 Double Precision, Inc. See COPYING for dnl distribution information. @@ -39,6 +38,7 @@ AC_CHECK_TYPE(int64_t, [ : ], dnl Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UID_T AC_TYPE_PID_T +AC_SYS_LARGEFILE dnl Checks for library functions. diff --git a/contrib/notmuch-deliver/maildrop/numlib/numlib.h b/contrib/notmuch-deliver/maildrop/numlib/numlib.h index 7928aa7e..c31d9b1a 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/numlib.h +++ b/contrib/notmuch-deliver/maildrop/numlib/numlib.h @@ -10,7 +10,6 @@ extern "C" { #endif -static const char numlib_h_rcsid[]="$Id: numlib.h,v 1.11 2010/03/19 01:09:26 mrsam Exp $"; #if HAVE_CONFIG_H #include "../numlib/config.h" /* VPATH build */ diff --git a/contrib/notmuch-deliver/maildrop/numlib/strdevt.c b/contrib/notmuch-deliver/maildrop/numlib/strdevt.c index 2e542d54..aa8b0282 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strdevt.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strdevt.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strdevt.c,v 1.1 2003/01/26 03:22:40 mrsam Exp $"; char *libmail_str_dev_t(dev_t t, char *arg) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/strgidt.c b/contrib/notmuch-deliver/maildrop/numlib/strgidt.c index 89472e3b..828a4550 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strgidt.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strgidt.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strgidt.c,v 1.4 2003/01/05 04:01:17 mrsam Exp $"; char *libmail_str_gid_t(gid_t t, char *arg) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c b/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c index 98e25e53..9ff45f28 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strhdevt.c,v 1.2 2003/03/12 02:45:56 mrsam Exp $"; static const char xdigit[]="0123456789ABCDEF"; diff --git a/contrib/notmuch-deliver/maildrop/numlib/strhinot.c b/contrib/notmuch-deliver/maildrop/numlib/strhinot.c index fa640915..c3cdc10e 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strhinot.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strhinot.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strhinot.c,v 1.5 2003/03/12 02:45:56 mrsam Exp $"; static const char xdigit[]="0123456789ABCDEF"; diff --git a/contrib/notmuch-deliver/maildrop/numlib/strhpidt.c b/contrib/notmuch-deliver/maildrop/numlib/strhpidt.c index 2723af0b..5a440f5d 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strhpidt.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strhpidt.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strhpidt.c,v 1.5 2003/03/12 02:45:56 mrsam Exp $"; static const char xdigit[]="0123456789ABCDEF"; diff --git a/contrib/notmuch-deliver/maildrop/numlib/strhtimet.c b/contrib/notmuch-deliver/maildrop/numlib/strhtimet.c index b86b05a2..cb9e6a9f 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strhtimet.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strhtimet.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strhtimet.c,v 1.5 2003/03/12 02:45:56 mrsam Exp $"; static const char xdigit[]="0123456789ABCDEF"; diff --git a/contrib/notmuch-deliver/maildrop/numlib/strinot.c b/contrib/notmuch-deliver/maildrop/numlib/strinot.c index eb544c3c..314b2f38 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strinot.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strinot.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strinot.c,v 1.4 2003/01/05 04:01:17 mrsam Exp $"; char *libmail_str_ino_t(ino_t t, char *arg) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/strofft.c b/contrib/notmuch-deliver/maildrop/numlib/strofft.c index 3435148e..567f912b 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strofft.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strofft.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strofft.c,v 1.6 2010/03/19 01:09:26 mrsam Exp $"; char *libmail_str_off_t(off_t t, char *arg) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/strpidt.c b/contrib/notmuch-deliver/maildrop/numlib/strpidt.c index 12ee9ce1..0c29b07e 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strpidt.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strpidt.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strpidt.c,v 1.4 2003/01/05 04:01:17 mrsam Exp $"; char *libmail_str_pid_t(pid_t t, char *arg) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/strsize.c b/contrib/notmuch-deliver/maildrop/numlib/strsize.c index 0a7dcaa8..1c903122 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strsize.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strsize.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strsize.c,v 1.2 2003/01/05 04:01:17 mrsam Exp $"; static void cat_n(char *buf, unsigned long n) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/strsizet.c b/contrib/notmuch-deliver/maildrop/numlib/strsizet.c index d4ec92d3..fd9d1d1a 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strsizet.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strsizet.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strsizet.c,v 1.4 2003/01/05 04:01:18 mrsam Exp $"; char *libmail_str_size_t(size_t t, char *arg) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/strtimet.c b/contrib/notmuch-deliver/maildrop/numlib/strtimet.c index be7e051b..63307f22 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/strtimet.c +++ b/contrib/notmuch-deliver/maildrop/numlib/strtimet.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: strtimet.c,v 1.4 2003/01/05 04:01:18 mrsam Exp $"; char *libmail_str_time_t(time_t t, char *arg) { diff --git a/contrib/notmuch-deliver/maildrop/numlib/struidt.c b/contrib/notmuch-deliver/maildrop/numlib/struidt.c index 50f3f742..5843ed24 100644 --- a/contrib/notmuch-deliver/maildrop/numlib/struidt.c +++ b/contrib/notmuch-deliver/maildrop/numlib/struidt.c @@ -9,7 +9,6 @@ #include "numlib.h" #include <string.h> -static const char rcsid[]="$Id: struidt.c,v 1.4 2003/01/05 04:01:18 mrsam Exp $"; char *libmail_str_uid_t(uid_t t, char *arg) { diff --git a/contrib/notmuch-deliver/src/main.c b/contrib/notmuch-deliver/src/main.c index f7a4eaa6..6f32f73d 100644 --- a/contrib/notmuch-deliver/src/main.c +++ b/contrib/notmuch-deliver/src/main.c @@ -1,22 +1,23 @@ -/* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ - -/* - * Copyright (c) 2010 Ali Polatel <alip@exherbo.org> +/* notmuch-deliver - If you make the user a promise... make sure you deliver it! + * + * Copyright © 2010 Ali Polatel * Based in part upon deliverquota of maildrop which is: * Copyright 1998 - 2009 Double Precision, Inc. * - * This file is part of the notmuch-deliver. notmuch-deliver is free software; - * you can redistribute it and/or modify it under the terms of the GNU General - * Public License version 2, as published by the Free Software Foundation. + * 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. * - * notmuch-deliver is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/ . * - * 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., 59 Temple - * Place, Suite 330, Boston, MA 02111-1307 USA + * Author: Ali Polatel <polatel@gmail.com> */ #ifdef HAVE_CONFIG_H diff --git a/debian/changelog b/debian/changelog index 4f90a134..28f56587 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,32 @@ +notmuch (0.11-1) unstable; urgency=low + + * New upstream release. + - New 'hook' feature for notmuch-new + - performance and memory use improvements + - new add-on tool notmuch-deliver + + -- David Bremner <bremner@debian.org> Fri, 13 Jan 2012 19:59:23 -0400 + +notmuch (0.11~rc3-1) experimental; urgency=low + + * New upstream release candidate + - Fix for uninitialized variable(s) in notmuch-reply + + -- David Bremner <bremner@debian.org> Mon, 09 Jan 2012 07:07:46 -0400 + +notmuch (0.11~rc2-1) experimental; urgency=low + + * New upstream release candidate. + - Includes fix for one python bindings segfault. + + -- David Bremner <bremner@debian.org> Mon, 02 Jan 2012 06:57:29 -0400 + +notmuch (0.11~rc1-1) experimental; urgency=low + + * New upstream release candidate. + + -- David Bremner <bremner@debian.org> Sun, 25 Dec 2011 23:07:08 -0400 + notmuch (0.10.2-1~bpo60+1) squeeze-backports; urgency=low * Rebuild for squeeze-backports. diff --git a/debian/control b/debian/control index 3252f32c..f6f415ed 100644 --- a/debian/control +++ b/debian/control @@ -16,7 +16,7 @@ Build-Depends: python-all (>= 2.6.6-3~), emacs23-nox | emacs23 (>=23~) | emacs23-lucid (>=23~), gdb, - dtach + dtach (>= 0.8) Standards-Version: 3.9.2 Homepage: http://notmuchmail.org/ Vcs-Git: git://notmuchmail.org/git/notmuch diff --git a/debian/gbp.conf b/debian/gbp.conf index dba526f6..6cb451a4 100644 --- a/debian/gbp.conf +++ b/debian/gbp.conf @@ -10,5 +10,8 @@ debian-branch = master # Directory for performing the build export-dir = ./debian-build +# Format for upstream tags +upstream-tag = %(version)s + # Format for the debian tag -debian-tag = debian-%(version)s +debian-tag = debian/%(version)s diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 1a7c5771..8eba7a0b 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -54,15 +54,22 @@ line." (completion-ignore-case t) (options (notmuch-address-options orig)) (num-options (length options)) - (chosen (if (eq num-options 1) - (car options) + (chosen (cond + ((eq num-options 0) + nil) + ((eq num-options 1) + (car options)) + (t (completing-read (format "Address (%s matches): " num-options) (cdr options) nil nil (car options) - 'notmuch-address-history)))) - (when chosen - (push chosen notmuch-address-history) - (delete-region beg end) - (insert chosen)))) + 'notmuch-address-history))))) + (if chosen + (progn + (push chosen notmuch-address-history) + (delete-region beg end) + (insert chosen)) + (message "No matches.") + (ding)))) ;; Copied from `w3m-which-command'. (defun notmuch-address-locate-command (command) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 44fccae0..ac300987 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -37,6 +37,11 @@ mode." :group 'notmuch :type 'boolean) +(defface notmuch-crypto-part-header + '((t (:foreground "blue"))) + "Face used for crypto parts headers." + :group 'notmuch) + (defface notmuch-crypto-signature-good '((t (:background "green" :foreground "black"))) "Face used for good signatures." @@ -63,7 +68,7 @@ mode." :group 'notmuch) (define-button-type 'notmuch-crypto-status-button-type - 'action '(lambda (button) (message (button-get button 'help-echo))) + 'action (lambda (button) (message (button-get button 'help-echo))) 'follow-link t 'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts.") @@ -72,7 +77,7 @@ mode." (help-msg nil) (label "Signature not processed") (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))))) (cond ((string= status "good") (let ((fingerprint (concat "0x" (plist-get sigstatus :fingerprint)))) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 1a76c30a..333d4c1e 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -42,6 +42,26 @@ :type 'boolean :group 'notmuch) +(defun notmuch-sort-saved-searches (alist) + "Generate an alphabetically sorted saved searches alist." + (sort alist (lambda (a b) (string< (car a) (car b))))) + +(defcustom notmuch-saved-search-sort-function nil + "Function used to sort the saved searches for the notmuch-hello view. + +This variable controls how saved searches should be sorted. No +sorting (nil) displays the saved searches in the order they are +stored in `notmuch-saved-searches'. Sort alphabetically sorts the +saved searches in alphabetical order. Custom sort function should +be a function or a lambda expression that takes the saved +searches alist as a parameter, and returns a new saved searches +alist to be used." + :type '(choice (const :tag "No sorting" nil) + (const :tag "Sort alphabetically" notmuch-sort-saved-searches) + (function :tag "Custom sort function" + :value notmuch-sort-saved-searches)) + :group 'notmuch) + (defvar notmuch-hello-indent 4 "How much to indent non-headers.") @@ -66,8 +86,9 @@ Finally this can be a function that will be called for each tag and should return a filter for that tag, or nil to hide the tag." :type '(choice (const :tag "All messages" nil) (const :tag "Unread messages" "tag:unread") - (const :tag "Custom filter" string) - (const :tag "Custom filter function" function)) + (string :tag "Custom filter" + :value "tag:unread") + (function :tag "Custom filter function")) :group 'notmuch) (defcustom notmuch-hello-hide-tags nil @@ -111,21 +132,34 @@ So: (integer :tag "Number of characters") (float :tag "Fraction of window"))) -(defcustom notmuch-decimal-separator "," - "The string used as a decimal separator. +(defcustom notmuch-hello-thousands-separator " " + "The string used as a thousands separator. -Typically \",\" in the US and UK and \".\" in Europe." +Typically \",\" in the US and UK and \".\" or \" \" in Europe. +The latter is recommended in the SI/ISO 31-0 standard and by the +International Bureau of Weights and Measures." :group 'notmuch :type 'string) +(defcustom notmuch-hello-mode-hook nil + "Functions called after entering `notmuch-hello-mode'." + :group 'notmuch + :type 'hook) + +(defcustom notmuch-hello-refresh-hook nil + "Functions called after updating a `notmuch-hello' buffer." + :type 'hook + :group 'notmuch) + (defvar notmuch-hello-url "http://notmuchmail.org" "The `notmuch' web site.") (defvar notmuch-hello-recent-searches nil) (defun notmuch-hello-remember-search (search) - (if (not (member search notmuch-hello-recent-searches)) - (push search notmuch-hello-recent-searches)) + (setq notmuch-hello-recent-searches + (delete search notmuch-hello-recent-searches)) + (push search notmuch-hello-recent-searches) (if (> (length notmuch-hello-recent-searches) notmuch-recent-searches-max) (setq notmuch-hello-recent-searches (butlast notmuch-hello-recent-searches)))) @@ -139,7 +173,7 @@ Typically \",\" in the US and UK and \".\" in Europe." (apply #'concat (number-to-string (car result)) (mapcar (lambda (elem) - (format "%s%03d" notmuch-decimal-separator elem)) + (format "%s%03d" notmuch-hello-thousands-separator elem)) (cdr result))))) (defun notmuch-hello-trim (search) @@ -168,8 +202,8 @@ Typically \",\" in the US and UK and \".\" in Europe." collect elem)) ;; Add the new one. (customize-save-variable 'notmuch-saved-searches - (push (cons name search) - notmuch-saved-searches)) + (add-to-list 'notmuch-saved-searches + (cons name search) t)) (message "Saved '%s' as '%s'." search name) (notmuch-hello-update))) @@ -311,8 +345,8 @@ should be. Returns a cons cell `(tags-per-line width)'." (defvar notmuch-hello-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map widget-keymap) - (define-key map "v" '(lambda () "Display the notmuch version" (interactive) - (message "notmuch version %s" (notmuch-version)))) + (define-key map "v" (lambda () "Display the notmuch version" (interactive) + (message "notmuch version %s" (notmuch-version)))) (define-key map "?" 'notmuch-help) (define-key map "q" 'notmuch-kill-this-buffer) (define-key map "=" 'notmuch-hello-update) @@ -335,6 +369,7 @@ Complete list of currently available key bindings: (use-local-map notmuch-hello-mode-map) (setq major-mode 'notmuch-hello-mode mode-name "notmuch-hello") + (run-mode-hooks 'notmuch-hello-mode-hook) ;;(setq buffer-read-only t) ) @@ -377,11 +412,16 @@ Complete list of currently available key bindings: (progn (widget-forward 1) (widget-value (widget-at))) - (error nil))))) + (error nil)))) + (inhibit-read-only t)) - (kill-all-local-variables) - (let ((inhibit-read-only t)) - (erase-buffer)) + ;; 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)) @@ -440,6 +480,10 @@ Complete list of currently available key bindings: (widest (max saved-widest alltags-widest))) (when saved-alist + ;; Sort saved searches if required. + (when notmuch-saved-search-sort-function + (setq saved-alist + (funcall notmuch-saved-search-sort-function saved-alist))) (widget-insert "\nSaved searches: ") (widget-create 'push-button :notify (lambda (&rest ignore) @@ -478,36 +522,36 @@ Complete list of currently available key bindings: (widget-insert "\n\n") (let ((start (point)) (nth 0)) - (mapc '(lambda (search) - (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) - (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)) - :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 "\n") - (setq nth (1+ nth))) + (mapc (lambda (search) + (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) + (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)) + :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 "\n") + (setq nth (1+ nth))) notmuch-hello-recent-searches) (indent-rigidly start (point) notmuch-hello-indent))) @@ -555,7 +599,9 @@ Complete list of currently available key bindings: (widget-forward 1))) (unless (widget-at) - (notmuch-hello-goto-search))))) + (notmuch-hello-goto-search)))) + + (run-hooks 'notmuch-hello-refresh-hook)) (defun notmuch-folder () "Deprecated function for invoking notmuch---calling `notmuch' is preferred now." diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index e6788685..6fbf82d2 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -65,8 +65,8 @@ yet when sending a mail." ;; Set up the message-fcc-handler to move mails to the maildir in Fcc ;; The parameter is set to mark messages as "seen" (setq message-fcc-handler-function - '(lambda (destdir) - (notmuch-maildir-fcc-write-buffer-to-maildir destdir t))) + (lambda (destdir) + (notmuch-maildir-fcc-write-buffer-to-maildir destdir t))) ;; add a hook to actually insert the Fcc header when sending (add-hook 'message-header-setup-hook 'notmuch-fcc-header-setup)) @@ -131,10 +131,10 @@ will NOT be removed or replaced." (defun notmuch-maildir-fcc-host-fixer (hostname) (replace-regexp-in-string "/\\|:" - '(lambda (s) - (cond ((string-equal s "/") "\\057") - ((string-equal s ":") "\\072") - (t s))) + (lambda (s) + (cond ((string-equal s "/") "\\057") + ((string-equal s ":") "\\072") + (t s))) hostname t t)) diff --git a/emacs/notmuch-message.el b/emacs/notmuch-message.el index aefd3fbc..08e5b174 100644 --- a/emacs/notmuch-message.el +++ b/emacs/notmuch-message.el @@ -39,10 +39,10 @@ the \"inbox\" and \"todo\", you would set (when (and notmuch-message-replied-tags rep) ;; add a "+" to any tag that is doesn't already begin with a "+" ;; or "-" - (let ((tags (mapcar '(lambda (str) - (if (not (string-match "^[+-]" str)) - (concat "+" str) - str)) + (let ((tags (mapcar (lambda (str) + (if (not (string-match "^[+-]" str)) + (concat "+" str) + str)) notmuch-message-replied-tags))) (apply 'notmuch-tag (concat "id:" (car (car rep))) tags))))) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 8824b080..7114e48a 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -35,10 +35,12 @@ "Function used to generate a `User-Agent:' string. If this is `nil' then no `User-Agent:' will be generated." :group 'notmuch - :type 'function - :options '(notmuch-mua-user-agent-full - notmuch-mua-user-agent-notmuch - notmuch-mua-user-agent-emacs)) + :type '(choice (const :tag "No user agent string" nil) + (const :tag "Full" notmuch-mua-user-agent-full) + (const :tag "Notmuch" notmuch-mua-user-agent-notmuch) + (const :tag "Emacs" notmuch-mua-user-agent-emacs) + (function :tag "Custom user agent function" + :value notmuch-mua-user-agent-full))) (defcustom notmuch-mua-hidden-headers '("^User-Agent:") "Headers that are added to the `message-mode' hidden headers @@ -124,9 +126,10 @@ list." (message-goto-to)) -(defun notmuch-mua-mail (&optional to subject other-headers continue - switch-function yank-action send-actions) - "Invoke the notmuch mail composition window." +(defun notmuch-mua-mail (&optional to subject other-headers &rest other-args) + "Invoke the notmuch mail composition window. + +OTHER-ARGS are passed through to `message-mail'." (interactive) (when notmuch-mua-user-agent-function @@ -138,8 +141,7 @@ list." (push (cons "From" (concat (notmuch-user-name) " <" (notmuch-user-primary-email) ">")) other-headers)) - (message-mail to subject other-headers continue - switch-function yank-action send-actions) + (apply #'message-mail to subject other-headers other-args) (message-sort-headers) (message-hide-headers) (set-buffer-modified-p nil) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index d5c95d80..82d11c92 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -91,6 +91,16 @@ any given message." :group 'notmuch :type 'boolean) +(defcustom notmuch-show-indent-messages-width 1 + "Width of message indentation in threads. + +Messages are shown indented according to their depth in a thread. +This variable determines the width of this indentation measured +in number of blanks. Defaults to `1', choose `0' to disable +indentation." + :group 'notmuch + :type 'integer) + (defcustom notmuch-show-indent-multipart nil "Should the sub-parts of a multipart/* part be indented?" ;; dme: Not sure which is a good default. @@ -238,7 +248,7 @@ unchanged ADDRESS if parsing fails." "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 depth) + (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth)) (notmuch-show-clean-address (plist-get headers :From)) " (" date @@ -255,12 +265,12 @@ message at DEPTH in the current thread." (defun notmuch-show-insert-headers (headers) "Insert the headers of the current message." (let ((start (point))) - (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)))) + (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)))) notmuch-message-headers) (save-excursion (save-restriction @@ -310,17 +320,17 @@ message at DEPTH in the current thread." ;; ange-ftp, which is reasonable to use here. (mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t))))) -(defun notmuch-show-mm-display-part-inline (msg part content-type content) +(defun notmuch-show-mm-display-part-inline (msg part nth content-type) "Use the mm-decode/mm-view functions to display a part in the current buffer, if possible." (let ((display-buffer (current-buffer))) (with-temp-buffer - (insert content) (let ((handle (mm-make-handle (current-buffer) (list content-type)))) - (set-buffer display-buffer) (if (and (mm-inlinable-p handle) (mm-inlined-p handle)) - (progn + (let ((content (notmuch-show-get-bodypart-content msg part nth))) + (insert content) + (set-buffer display-buffer) (mm-display-part handle) t) nil))))) @@ -334,7 +344,7 @@ current buffer, if possible." )) (defun notmuch-show-multipart/*-to-list (part) - (mapcar '(lambda (inner-part) (plist-get inner-part :content-type)) + (mapcar (lambda (inner-part) (plist-get inner-part :content-type)) (plist-get part :content))) (defun notmuch-show-multipart/alternative-choose (types) @@ -447,11 +457,10 @@ current buffer, if possible." (defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth declared-type) (let ((button (notmuch-show-insert-part-header nth declared-type content-type nil))) - (button-put button 'face '(:foreground "blue")) + (button-put button 'face 'notmuch-crypto-part-header) ;; add signature status button if sigstatus provided (if (plist-member part :sigstatus) - (let* ((headers (plist-get msg :headers)) - (from (plist-get headers :From)) + (let* ((from (notmuch-show-get-header :From msg)) (sigstatus (car (plist-get part :sigstatus)))) (notmuch-crypto-insert-sigstatus-button sigstatus from)) ;; if we're not adding sigstatus, tell the user how they can get it @@ -470,15 +479,14 @@ current buffer, if possible." (defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth declared-type) (let ((button (notmuch-show-insert-part-header nth declared-type content-type nil))) - (button-put button 'face '(:foreground "blue")) + (button-put button 'face 'notmuch-crypto-part-header) ;; add encryption status button if encstatus specified (if (plist-member part :encstatus) (let ((encstatus (car (plist-get part :encstatus)))) (notmuch-crypto-insert-encstatus-button encstatus) ;; add signature status button if sigstatus specified (if (plist-member part :sigstatus) - (let* ((headers (plist-get msg :headers)) - (from (plist-get headers :From)) + (let* ((from (notmuch-show-get-header :From msg)) (sigstatus (car (plist-get part :sigstatus)))) (notmuch-crypto-insert-sigstatus-button sigstatus from)))) ;; if we're not adding encstatus, tell the user how they can get it @@ -511,7 +519,6 @@ current buffer, if possible." (defun notmuch-show-insert-part-message/rfc822 (msg part content-type nth depth declared-type) (notmuch-show-insert-part-header nth declared-type content-type nil) (let* ((message (car (plist-get part :content))) - (headers (plist-get message :headers)) (body (car (plist-get message :body))) (start (point))) @@ -578,17 +585,10 @@ current buffer, if possible." nil)) nil)))) -(defun notmuch-show-insert-part-application/* (msg part content-type nth depth declared-type -) - ;; do not render random "application" parts - (notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename))) - (defun notmuch-show-insert-part-*/* (msg part content-type nth depth declared-type) ;; This handler _must_ succeed - it is the handler of last resort. (notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename)) - (let ((content (notmuch-show-get-bodypart-content msg part nth))) - (if content - (notmuch-show-mm-display-part-inline msg part content-type content))) + (notmuch-show-mm-display-part-inline msg part nth content-type) t) ;; Functions for determining how to handle MIME parts. @@ -657,7 +657,7 @@ current buffer, if possible." (defun notmuch-show-insert-body (msg body depth) "Insert the body BODY at depth DEPTH in the current thread." - (mapc '(lambda (part) (notmuch-show-insert-bodypart msg part depth)) body)) + (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body)) (defun notmuch-show-make-symbol (type) (make-symbol (concat "notmuch-show-" type))) @@ -739,7 +739,7 @@ current buffer, if possible." (setq content-end (point-marker)) ;; Indent according to the depth in the thread. - (indent-rigidly content-start content-end depth) + (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth)) (setq message-end (point-max-marker)) @@ -775,11 +775,11 @@ current buffer, if possible." (defun notmuch-show-insert-thread (thread depth) "Insert the thread THREAD at depth DEPTH in the current forest." - (mapc '(lambda (tree) (notmuch-show-insert-tree tree depth)) thread)) + (mapc (lambda (tree) (notmuch-show-insert-tree tree depth)) thread)) (defun notmuch-show-insert-forest (forest) "Insert the forest of threads FOREST." - (mapc '(lambda (thread) (notmuch-show-insert-thread thread 0)) forest)) + (mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest)) (defvar notmuch-show-thread-id nil) (make-variable-buffer-local 'notmuch-show-thread-id) @@ -803,7 +803,7 @@ a corresponding notmuch search." (remove-overlays (match-beginning 0) (match-end 0) 'goto-address t) (make-text-button (match-beginning 0) (match-end 0) 'action `(lambda (arg) - (notmuch-search ,(match-string-no-properties 0))) + (notmuch-show ,(match-string-no-properties 0))) 'follow-link t 'help-echo "Mouse-1, RET: search for this message" 'face goto-address-mail-face)))) @@ -843,6 +843,8 @@ buffer." (inhibit-read-only t)) (switch-to-buffer buffer) (notmuch-show-mode) + ;; Don't track undo information for this buffer + (set 'buffer-undo-list t) (setq notmuch-show-thread-id thread-id) (setq notmuch-show-parent-buffer parent-buffer) @@ -1051,6 +1053,12 @@ All currently available key bindings: (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. + +Some useful entries are: +:headers - Property list containing the headers :Date, :Subject, :From, etc. +:body - Body of the message +:tags - Tags for this message" (save-excursion (notmuch-show-move-to-message-top) (get-text-property (point) :notmuch-message-properties))) @@ -1077,9 +1085,9 @@ All currently available key bindings: "Return the filename of the current message." (notmuch-show-get-prop :filename)) -(defun notmuch-show-get-header (header) +(defun notmuch-show-get-header (header &optional props) "Return the named header of the current message, if any." - (plist-get (notmuch-show-get-prop :headers) header)) + (plist-get (notmuch-show-get-prop :headers props) header)) (defun notmuch-show-get-cc () (notmuch-show-get-header :Cc)) @@ -1135,30 +1143,23 @@ All currently available key bindings: ;; Commands typically bound to keys. -(defun notmuch-show-advance-and-archive () - "Advance through thread and archive. - -This command is intended to be one of the simplest ways to -process a thread of email. It does the following: +(defun notmuch-show-advance () + "Advance through thread. If the current message in the thread is not yet fully visible, scroll by a near screenful to read more of the message. Otherwise, (the end of the current message is already within the -current window), advance to the next open message. - -Finally, if there is no further message to advance to, and this -last message is already read, then archive the entire current -thread, (remove the \"inbox\" tag from each message). Also kill -this buffer, and display the next thread from the search from -which this thread was originally shown." +current window), advance to the next open message." (interactive) (let* ((end-of-this-message (notmuch-show-message-bottom)) - (visible-end-of-this-message (1- end-of-this-message))) + (visible-end-of-this-message (1- end-of-this-message)) + (ret nil)) (while (invisible-p visible-end-of-this-message) (setq visible-end-of-this-message - (previous-single-char-property-change visible-end-of-this-message - 'invisible))) + (max (point-min) + (1- (previous-single-char-property-change + visible-end-of-this-message 'invisible))))) (cond ;; Ideally we would test `end-of-this-message' against the result ;; of `window-end', but that doesn't account for the fact that @@ -1173,8 +1174,24 @@ which this thread was originally shown." (notmuch-show-next-open-message)) (t - ;; This is the last message - archive the thread. - (notmuch-show-archive-thread))))) + ;; This is the last message - change the return value + (setq ret t))) + ret)) + +(defun notmuch-show-advance-and-archive () + "Advance through thread and archive. + +This command is intended to be one of the simplest ways to +process a thread of email. It works exactly like +notmuch-show-advance, in that it scrolls through messages in a +show buffer, except that when it gets to the end of the buffer it +archives the entire current thread, (remove the \"inbox\" tag +from each message), kills the buffer, and displays the next +thread from the search from which this thread was originally +shown." + (interactive) + (if (notmuch-show-advance) + (notmuch-show-archive-thread))) (defun notmuch-show-rewind () "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]). diff --git a/emacs/notmuch.el b/emacs/notmuch.el index c1827cc2..fde23779 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -646,9 +646,9 @@ This function advances the next thread when finished." 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 '((\"delete\" . '(:foreground \"red\" - :background \"blue\")) - (\"unread\" . '(:foreground \"green\")))) + (setq notmuch-search-line-faces '((\"delete\" . (:foreground \"red\" + :background \"blue\")) + (\"unread\" . (:foreground \"green\")))) The attributes defined for matching tags are merged, with later attributes overriding earlier. A message having both \"delete\" @@ -662,16 +662,16 @@ foreground and blue background." ;; Create the overlay only if the message has tags which match one ;; of those specified in `notmuch-search-line-faces'. (let (overlay) - (mapc '(lambda (elem) - (let ((tag (car elem)) - (attributes (cdr elem))) - (when (member tag line-tag-list) - (when (not overlay) - (setq overlay (make-overlay start end))) - ;; Merge the specified properties with any already - ;; applied from an earlier match. - (overlay-put overlay 'face - (append (overlay-get overlay 'face) attributes))))) + (mapc (lambda (elem) + (let ((tag (car elem)) + (attributes (cdr elem))) + (when (member tag line-tag-list) + (when (not overlay) + (setq overlay (make-overlay start end))) + ;; Merge the specified properties with any already + ;; applied from an earlier match. + (overlay-put overlay 'face + (append (overlay-get overlay 'face) attributes))))) notmuch-search-line-faces))) (defun notmuch-search-author-propertize (authors) @@ -805,12 +805,12 @@ non-authors is found, assume that all of the authors match." (goto-char (point-max)) (if (/= (match-beginning 1) line) (insert (concat "Error: Unexpected output from notmuch search:\n" (substring string line (match-beginning 1)) "\n"))) - (let ((beg (point-marker))) + (let ((beg (point))) (notmuch-search-show-result date count authors subject tags) - (notmuch-search-color-line beg (point-marker) tag-list) - (put-text-property beg (point-marker) 'notmuch-search-thread-id thread-id) - (put-text-property beg (point-marker) 'notmuch-search-authors authors) - (put-text-property beg (point-marker) 'notmuch-search-subject subject) + (notmuch-search-color-line beg (point) tag-list) + (put-text-property beg (point) 'notmuch-search-thread-id thread-id) + (put-text-property beg (point) 'notmuch-search-authors authors) + (put-text-property beg (point) 'notmuch-search-subject subject) (if (string= thread-id notmuch-search-target-thread) (progn (set 'found-target beg) @@ -885,7 +885,7 @@ PROMPT is the string to prompt with." "subject:" "attachment:") (mapcar (lambda (tag) (concat "tag:" tag)) - (process-lines "notmuch" "search" "--output=tags" "*"))))) + (process-lines notmuch-command "search" "--output=tags" "*"))))) (let ((keymap (copy-keymap minibuffer-local-map)) (minibuffer-completion-table (completion-table-dynamic @@ -920,6 +920,8 @@ The optional parameters are used as follows: (let ((buffer (get-buffer-create (notmuch-search-buffer-title query)))) (switch-to-buffer buffer) (notmuch-search-mode) + ;; Don't track undo information for this buffer + (set 'buffer-undo-list t) (set 'notmuch-search-query-string query) (set 'notmuch-search-oldest-first oldest-first) (set 'notmuch-search-target-thread target-thread) @@ -963,28 +965,43 @@ same relative position within the new buffer." (notmuch-search query oldest-first target-thread target-line continuation) (goto-char (point-min)))) -(defcustom notmuch-poll-script "" +(defcustom notmuch-poll-script nil "An external script to incorporate new mail into the notmuch database. -If this variable is non empty, then it should name a script to be -invoked by `notmuch-search-poll-and-refresh-view' and +This variable controls the action invoked by +`notmuch-search-poll-and-refresh-view' and `notmuch-hello-poll-and-update' (each have a default keybinding -of 'G'). The script could do any of the following depending on +of 'G') to incorporate new mail into the notmuch database. + +If set to nil (the default), new mail is processed by invoking +\"notmuch new\". Otherwise, this should be set to a string that +gives the name of an external script that processes new mail. If +set to the empty string, no command will be run. + +The external script could do any of the following depending on the user's needs: 1. Invoke a program to transfer mail to the local mail store 2. Invoke \"notmuch new\" to incorporate the new mail -3. Invoke one or more \"notmuch tag\" commands to classify the mail" - :type 'string +3. Invoke one or more \"notmuch tag\" commands to classify the mail + +Note that the recommended way of achieving the same is using +\"notmuch new\" hooks." + :type '(choice (const :tag "notmuch new" nil) + (const :tag "Disabled" "") + (string :tag "Custom script")) :group 'notmuch) (defun notmuch-poll () - "Run external script to import mail. + "Run \"notmuch new\" or an external script to import mail. -Invokes `notmuch-poll-script' if it is not set to an empty string." +Invokes `notmuch-poll-script', \"notmuch new\", or does nothing +depending on the value of `notmuch-poll-script'." (interactive) - (if (not (string= notmuch-poll-script "")) - (call-process notmuch-poll-script nil nil))) + (if (stringp notmuch-poll-script) + (if (not (string= notmuch-poll-script "")) + (call-process notmuch-poll-script nil nil)) + (call-process notmuch-command nil nil nil "new"))) (defun notmuch-search-poll-and-refresh-view () "Invoke `notmuch-poll' to import mail, then refresh the current view." @@ -1038,6 +1055,23 @@ current search results AND that are tagged with the given tag." (interactive) (notmuch-hello)) +;;;###autoload +(defun notmuch-jump-to-recent-buffer () + "Jump to the most recent notmuch buffer (search, show or hello). + +If no recent buffer is found, run `notmuch'." + (interactive) + (let ((last + (loop for buffer in (buffer-list) + if (with-current-buffer buffer + (memq major-mode '(notmuch-show-mode + notmuch-search-mode + notmuch-hello-mode))) + return buffer))) + (if last + (switch-to-buffer last) + (notmuch)))) + (setq mail-user-agent 'notmuch-user-agent) (provide 'notmuch) diff --git a/hooks.c b/hooks.c new file mode 100644 index 00000000..44ee4198 --- /dev/null +++ b/hooks.c @@ -0,0 +1,93 @@ +/* notmuch - Not much of an email program, (just index and search) + * + * This file is part of notmuch. + * + * Copyright © 2011 Jani Nikula + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/ . + * + * Author: Jani Nikula <jani@nikula.org> + */ + +#include "notmuch-client.h" +#include <sys/wait.h> + +int +notmuch_run_hook (const char *db_path, const char *hook) +{ + char *hook_path; + int status = 0; + pid_t pid; + + hook_path = talloc_asprintf (NULL, "%s/%s/%s/%s", db_path, ".notmuch", + "hooks", hook); + if (hook_path == NULL) { + fprintf (stderr, "Out of memory\n"); + return 1; + } + + /* Check access before fork() for speed and simplicity of error handling. */ + if (access (hook_path, X_OK) == -1) { + /* Ignore ENOENT. It's okay not to have a hook, hook dir, or even + * notmuch dir. Dangling symbolic links also result in ENOENT, but + * we'll ignore that too for simplicity. */ + if (errno != ENOENT) { + fprintf (stderr, "Error: %s hook access failed: %s\n", hook, + strerror (errno)); + status = 1; + } + goto DONE; + } + + pid = fork(); + if (pid == -1) { + fprintf (stderr, "Error: %s hook fork failed: %s\n", hook, + strerror (errno)); + status = 1; + goto DONE; + } else if (pid == 0) { + execl (hook_path, hook_path, NULL); + /* Same as above for ENOENT, but unlikely now. Indicate all other errors + * to parent through non-zero exit status. */ + if (errno != ENOENT) { + fprintf (stderr, "Error: %s hook execution failed: %s\n", hook, + strerror (errno)); + status = 1; + } + exit (status); + } + + if (waitpid (pid, &status, 0) == -1) { + fprintf (stderr, "Error: %s hook wait failed: %s\n", hook, + strerror (errno)); + status = 1; + goto DONE; + } + + if (!WIFEXITED (status) || WEXITSTATUS (status)) { + if (WIFEXITED (status)) { + fprintf (stderr, "Error: %s hook failed with status %d\n", + hook, WEXITSTATUS (status)); + } else if (WIFSIGNALED (status)) { + fprintf (stderr, "Error: %s hook terminated with signal %d\n", + hook, WTERMSIG (status)); + } + status = 1; + } + + DONE: + talloc_free (hook_path); + + return status; +} diff --git a/lib/Makefile.local b/lib/Makefile.local index 57dca702..54c4dea4 100644 --- a/lib/Makefile.local +++ b/lib/Makefile.local @@ -30,7 +30,7 @@ LIBRARY_SUFFIX = so LINKER_NAME = libnotmuch.$(LIBRARY_SUFFIX) SONAME = $(LINKER_NAME).$(LIBNOTMUCH_VERSION_MAJOR) LIBNAME = $(SONAME).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE) -LIBRARY_LINK_FLAG = -shared -Wl,--version-script=notmuch.sym,-soname=$(SONAME) +LIBRARY_LINK_FLAG = -shared -Wl,--version-script=notmuch.sym,-soname=$(SONAME) -Wl,--no-undefined ifeq ($(LIBDIR_IN_LDCONFIG),1) ifeq ($(DESTDIR),) LIBRARY_INSTALL_POST_COMMAND=ldconfig diff --git a/lib/database.cc b/lib/database.cc index 98f101e6..8103bd96 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -28,6 +28,8 @@ #include <glib.h> /* g_free, GPtrArray, GHashTable */ #include <glib-object.h> /* g_type_init */ +#include <gmime/gmime.h> /* g_mime_init */ + using namespace std; #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0])) @@ -81,13 +83,17 @@ typedef struct { * STRING is the name of a file within that * directory for this mail message. * - * A mail document also has two values: + * A mail document also has four values: * * TIMESTAMP: The time_t value corresponding to the message's * Date header. * * MESSAGE_ID: The unique ID of the mail mess (see "id" above) * + * FROM: The value of the "From" header + * + * SUBJECT: The value of the "Subject" header + * * In addition, terms from the content of the message are added with * "from", "to", "attachment", and "subject" prefixes for use by the * user in searching. Similarly, terms from the path of the mail @@ -581,6 +587,7 @@ notmuch_database_open (const char *path, struct stat st; int err; unsigned int i, version; + static int initialized = 0; if (asprintf (¬much_path, "%s/%s", path, ".notmuch") == -1) { notmuch_path = NULL; @@ -604,6 +611,12 @@ notmuch_database_open (const char *path, /* Initialize the GLib type system and threads */ g_type_init (); + /* Initialize gmime */ + if (! initialized) { + g_mime_init (0); + initialized = 1; + } + notmuch = talloc (NULL, notmuch_database_t); notmuch->exception_reported = FALSE; notmuch->path = talloc_strdup (notmuch, path); @@ -1447,7 +1460,7 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch, keys = g_hash_table_get_keys (parents); for (l = keys; l; l = l->next) { char *parent_message_id; - const char *parent_thread_id; + const char *parent_thread_id = NULL; parent_message_id = (char *) l->data; diff --git a/lib/message.cc b/lib/message.cc index ca7fbf21..00754254 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -49,16 +49,16 @@ struct visible _notmuch_message { struct maildir_flag_tag { char flag; const char *tag; - bool inverse; + notmuch_bool_t inverse; }; /* ASCII ordered table of Maildir flags and associated tags */ static struct maildir_flag_tag flag2tag[] = { - { 'D', "draft", false}, - { 'F', "flagged", false}, - { 'P', "passed", false}, - { 'R', "replied", false}, - { 'S', "unread", true } + { 'D', "draft", FALSE}, + { 'F', "flagged", FALSE}, + { 'P', "passed", FALSE}, + { 'R', "replied", FALSE}, + { 'S', "unread", TRUE } }; /* We end up having to call the destructor explicitly because we had @@ -1217,8 +1217,6 @@ _new_maildir_filename (void *ctx, if (info == NULL) { info = filename + strlen(filename); } else { - flags = info + 3; - /* Loop through existing flags in filename. */ for (flags = info + 3, last_flag = 0; *flags; diff --git a/notmuch-client.h b/notmuch-client.h index b50cb38b..c602e2e0 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -235,7 +235,11 @@ void notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config, notmuch_bool_t synchronize_flags); +int +notmuch_run_hook (const char *db_path, const char *hook); + notmuch_bool_t debugger_is_active (void); +#include "command-line-arguments.h" #endif diff --git a/notmuch-config.c b/notmuch-config.c index 1a7ed580..d697138a 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -22,6 +22,7 @@ #include <pwd.h> #include <netdb.h> +#include <assert.h> static const char toplevel_config_comment[] = " .notmuch-config - Configuration file for the notmuch mail system\n" @@ -520,92 +521,80 @@ notmuch_config_set_user_primary_email (notmuch_config_t *config, config->user_primary_email = NULL; } -const char ** -notmuch_config_get_user_other_email (notmuch_config_t *config, - size_t *length) +static const char ** +_config_get_list (notmuch_config_t *config, + const char *section, const char *key, + const char ***outlist, size_t *list_length, size_t *ret_length) { - char **emails; - size_t emails_length; - unsigned int i; + assert(outlist); + + if (*outlist == NULL) { + + char **inlist = g_key_file_get_string_list (config->key_file, + section, key, list_length, NULL); + if (inlist) { + unsigned int i; + + *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1)); - if (config->user_other_email == NULL) { - emails = g_key_file_get_string_list (config->key_file, - "user", "other_email", - &emails_length, NULL); - if (emails) { - config->user_other_email = talloc_size (config, - sizeof (char *) * - (emails_length + 1)); - for (i = 0; i < emails_length; i++) - config->user_other_email[i] = talloc_strdup (config->user_other_email, - emails[i]); - config->user_other_email[i] = NULL; + for (i = 0; i < *list_length; i++) + (*outlist)[i] = talloc_strdup (*outlist, inlist[i]); - g_strfreev (emails); + (*outlist)[i] = NULL; - config->user_other_email_length = emails_length; + g_strfreev (inlist); } } - *length = config->user_other_email_length; - return config->user_other_email; + if (ret_length) + *ret_length = *list_length; + + return *outlist; } -void -notmuch_config_set_user_other_email (notmuch_config_t *config, - const char *other_email[], - size_t length) +const char ** +notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length) { - g_key_file_set_string_list (config->key_file, - "user", "other_email", - other_email, length); - - talloc_free (config->user_other_email); - config->user_other_email = NULL; + return _config_get_list (config, "user", "other_email", + &(config->user_other_email), + &(config->user_other_email_length), length); } const char ** -notmuch_config_get_new_tags (notmuch_config_t *config, - size_t *length) +notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length) { - char **tags; - size_t tags_length; - unsigned int i; - - if (config->new_tags == NULL) { - tags = g_key_file_get_string_list (config->key_file, - "new", "tags", - &tags_length, NULL); - if (tags) { - config->new_tags = talloc_size (config, - sizeof (char *) * - (tags_length + 1)); - for (i = 0; i < tags_length; i++) - config->new_tags[i] = talloc_strdup (config->new_tags, - tags[i]); - config->new_tags[i] = NULL; - - g_strfreev (tags); + return _config_get_list (config, "new", "tags", + &(config->new_tags), + &(config->new_tags_length), length); +} - config->new_tags_length = tags_length; - } - } +static void +_config_set_list (notmuch_config_t *config, + const char *group, const char *name, + const char *list[], + size_t length, const char ***config_var ) +{ + g_key_file_set_string_list (config->key_file, group, name, list, length); + talloc_free (*config_var); + *config_var = NULL; +} - *length = config->new_tags_length; - return config->new_tags; +void +notmuch_config_set_user_other_email (notmuch_config_t *config, + const char *list[], + size_t length) +{ + _config_set_list (config, "user", "other_email", list, length, + &(config->user_other_email)); } void notmuch_config_set_new_tags (notmuch_config_t *config, - const char *new_tags[], - size_t length) + const char *list[], + size_t length) { - g_key_file_set_string_list (config->key_file, - "new", "tags", - new_tags, length); - - talloc_free (config->new_tags); - config->new_tags = NULL; + _config_set_list (config, "new", "tags", list, length, + &(config->new_tags)); } /* Given a configuration item of the form <group>.<key> return the diff --git a/notmuch-dump.c b/notmuch-dump.c index 126593d1..a7358756 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -41,39 +41,49 @@ notmuch_dump_command (unused (void *ctx), int argc, char *argv[]) if (notmuch == NULL) return 1; - argc--; argv++; /* skip subcommand argument */ + char *output_file_name = NULL; + int opt_index; - if (argc && strcmp (argv[0], "--") != 0) { + notmuch_opt_desc_t options[] = { + { NOTMUCH_OPT_POSITION, &output_file_name, 0, 0, 0 }, + { 0, 0, 0, 0, 0 } + }; + + opt_index = parse_arguments (argc, argv, options, 1); + + if (opt_index < 0) { + /* diagnostics already printed */ + return 1; + } + + if (output_file_name) { fprintf (stderr, "Warning: the output file argument of dump is deprecated.\n"); - output = fopen (argv[0], "w"); + output = fopen (output_file_name, "w"); if (output == NULL) { fprintf (stderr, "Error opening %s for writing: %s\n", - argv[0], strerror (errno)); + output_file_name, strerror (errno)); return 1; } - argc--; - argv++; } - if (argc && strcmp (argv[0], "--") == 0){ - argc--; - argv++; - } - if (argc) { - query_str = query_string_from_args (notmuch, argc, argv); + if (opt_index < argc) { + query_str = query_string_from_args (notmuch, argc-opt_index, argv+opt_index); if (query_str == NULL) { fprintf (stderr, "Out of memory.\n"); return 1; } } - + query = notmuch_query_create (notmuch, query_str); if (query == NULL) { fprintf (stderr, "Out of memory\n"); return 1; } - notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID); + /* Don't ask xapian to sort by Message-ID. Xapian optimizes returning the + * first results quickly at the expense of total time. + */ + notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED); for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); diff --git a/notmuch-new.c b/notmuch-new.c index 81a93500..3512de72 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -67,7 +67,7 @@ handle_sigint (unused (int sig)) { static char msg[] = "Stopping... \n"; - write(2, msg, sizeof(msg)-1); + (void) write(2, msg, sizeof(msg)-1); interrupted = 1; } @@ -811,6 +811,7 @@ notmuch_new_command (void *ctx, int argc, char *argv[]) _filename_node_t *f; int i; notmuch_bool_t timer_is_active = FALSE; + notmuch_bool_t run_hooks = TRUE; add_files_state.verbose = 0; add_files_state.output_is_a_tty = isatty (fileno (stdout)); @@ -820,6 +821,8 @@ notmuch_new_command (void *ctx, int argc, char *argv[]) for (i = 0; i < argc && argv[i][0] == '-'; i++) { if (STRNCMP_LITERAL (argv[i], "--verbose") == 0) { add_files_state.verbose = 1; + } else if (strcmp (argv[i], "--no-hooks") == 0) { + run_hooks = FALSE; } else { fprintf (stderr, "Unrecognized option: %s\n", argv[i]); return 1; @@ -833,6 +836,12 @@ notmuch_new_command (void *ctx, int argc, char *argv[]) add_files_state.synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config); db_path = notmuch_config_get_database_path (config); + if (run_hooks) { + ret = notmuch_run_hook (db_path, "pre-new"); + if (ret) + return ret; + } + dot_notmuch_path = talloc_asprintf (ctx, "%s/%s", db_path, ".notmuch"); if (stat (dot_notmuch_path, &st)) { @@ -981,5 +990,8 @@ notmuch_new_command (void *ctx, int argc, char *argv[]) notmuch_database_close (notmuch); + if (run_hooks && !ret && !interrupted) + ret = notmuch_run_hook (db_path, "post-new"); + return ret || interrupted; } diff --git a/notmuch-reply.c b/notmuch-reply.c index 7ac879f9..7242310a 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -622,11 +622,9 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) char *opt, *query_string; int i, ret = 0; int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params); - notmuch_show_params_t params; + notmuch_show_params_t params = { .part = -1 }; reply_format_func = notmuch_reply_format_default; - params.part = -1; - params.cryptoctx = NULL; argc--; argv++; /* skip subcommand argument */ @@ -648,10 +646,12 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) } else if ((STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { if (params.cryptoctx == NULL) { GMimeSession* session = g_object_new(g_mime_session_get_type(), NULL); - if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) + if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) { fprintf (stderr, "Failed to construct gpg context.\n"); - else + } else { + params.decrypt = TRUE; g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE); + } g_object_unref (session); session = NULL; } diff --git a/notmuch-restore.c b/notmuch-restore.c index 13b4325a..87d9772b 100644 --- a/notmuch-restore.c +++ b/notmuch-restore.c @@ -18,8 +18,6 @@ * Author: Carl Worth <cworth@cworth.org> */ -#include <getopt.h> - #include "notmuch-client.h" int @@ -29,12 +27,14 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[]) notmuch_database_t *notmuch; notmuch_bool_t synchronize_flags; notmuch_bool_t accumulate = FALSE; + char *input_file_name = NULL; FILE *input = stdin; char *line = NULL; size_t line_size; ssize_t line_len; regex_t regex; int rerr; + int opt_index; config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) @@ -47,37 +47,30 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[]) synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config); - struct option options[] = { - { "accumulate", no_argument, 0, 'a' }, - { 0, 0, 0, 0} + notmuch_opt_desc_t options[] = { + { NOTMUCH_OPT_POSITION, &input_file_name, 0, 0, 0 }, + { NOTMUCH_OPT_BOOLEAN, &accumulate, "accumulate", 'a', 0 }, + { 0, 0, 0, 0, 0 } }; - int opt; - do { - opt = getopt_long (argc, argv, "", options, NULL); - - switch (opt) { - case 'a': - accumulate = 1; - break; - case '?': - return 1; - break; - } + opt_index = parse_arguments (argc, argv, options, 1); - } while (opt != -1); + if (opt_index < 0) { + /* diagnostics already printed */ + return 1; + } - if (optind < argc) { - input = fopen (argv[optind], "r"); + if (input_file_name) { + input = fopen (input_file_name, "r"); if (input == NULL) { fprintf (stderr, "Error opening %s for reading: %s\n", - argv[optind], strerror (errno)); + input_file_name, strerror (errno)); return 1; } optind++; } - if (optind < argc) { + if (opt_index < argc) { fprintf (stderr, "Cannot read dump from more than one file: %s\n", argv[optind]); diff --git a/notmuch-search.c b/notmuch-search.c index 36686d19..4baab561 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -176,12 +176,14 @@ format_thread_json (const void *ctx, printf ("\"thread\": %s,\n" "\"timestamp\": %ld,\n" + "\"date_relative\": \"%s\",\n" "\"matched\": %d,\n" "\"total\": %d,\n" "\"authors\": %s,\n" "\"subject\": %s,\n", json_quote_str (ctx_quote, thread_id), date, + notmuch_time_relative_date (ctx, date), matched, total, json_quote_str (ctx_quote, authors), @@ -415,81 +417,51 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) notmuch_database_t *notmuch; notmuch_query_t *query; char *query_str; - char *opt; notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST; const search_format_t *format = &format_text; - int i, ret; + int opt_index, ret; output_t output = OUTPUT_SUMMARY; int offset = 0; int limit = -1; /* unlimited */ - argc--; argv++; /* skip subcommand argument */ + enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT } + format_sel = NOTMUCH_FORMAT_TEXT; - for (i = 0; i < argc && argv[i][0] == '-'; i++) { - if (strcmp (argv[i], "--") == 0) { - i++; - break; - } - if (STRNCMP_LITERAL (argv[i], "--sort=") == 0) { - opt = argv[i] + sizeof ("--sort=") - 1; - if (strcmp (opt, "oldest-first") == 0) { - sort = NOTMUCH_SORT_OLDEST_FIRST; - } else if (strcmp (opt, "newest-first") == 0) { - sort = NOTMUCH_SORT_NEWEST_FIRST; - } else { - fprintf (stderr, "Invalid value for --sort: %s\n", opt); - return 1; - } - } else if (STRNCMP_LITERAL (argv[i], "--offset=") == 0) { - char *p; - opt = argv[i] + sizeof ("--offset=") - 1; - offset = strtol (opt, &p, 10); - if (*opt == '\0' || p == opt || *p != '\0') { - fprintf (stderr, "Invalid value for --offset: %s\n", opt); - return 1; - } - } else if (STRNCMP_LITERAL (argv[i], "--limit=") == 0) { - char *p; - opt = argv[i] + sizeof ("--limit=") - 1; - limit = strtoul (opt, &p, 10); - if (*opt == '\0' || p == opt || *p != '\0') { - fprintf (stderr, "Invalid value for --limit: %s\n", opt); - return 1; - } - } else if (STRNCMP_LITERAL (argv[i], "--format=") == 0) { - opt = argv[i] + sizeof ("--format=") - 1; - if (strcmp (opt, "text") == 0) { - format = &format_text; - } else if (strcmp (opt, "json") == 0) { - format = &format_json; - } else { - fprintf (stderr, "Invalid value for --format: %s\n", opt); - return 1; - } - } else if (STRNCMP_LITERAL (argv[i], "--output=") == 0) { - opt = argv[i] + sizeof ("--output=") - 1; - if (strcmp (opt, "summary") == 0) { - output = OUTPUT_SUMMARY; - } else if (strcmp (opt, "threads") == 0) { - output = OUTPUT_THREADS; - } else if (strcmp (opt, "messages") == 0) { - output = OUTPUT_MESSAGES; - } else if (strcmp (opt, "files") == 0) { - output = OUTPUT_FILES; - } else if (strcmp (opt, "tags") == 0) { - output = OUTPUT_TAGS; - } else { - fprintf (stderr, "Invalid value for --output: %s\n", opt); - return 1; - } - } else { - fprintf (stderr, "Unrecognized option: %s\n", argv[i]); - return 1; - } + notmuch_opt_desc_t options[] = { + { NOTMUCH_OPT_KEYWORD, &sort, "sort", 's', + (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST }, + { "newest-first", NOTMUCH_SORT_NEWEST_FIRST }, + { 0, 0 } } }, + { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f', + (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON }, + { "text", NOTMUCH_FORMAT_TEXT }, + { 0, 0 } } }, + { NOTMUCH_OPT_KEYWORD, &output, "output", 'o', + (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY }, + { "threads", OUTPUT_THREADS }, + { "messages", OUTPUT_MESSAGES }, + { "files", OUTPUT_FILES }, + { "tags", OUTPUT_TAGS }, + { 0, 0 } } }, + { NOTMUCH_OPT_INT, &offset, "offset", 'O', 0 }, + { NOTMUCH_OPT_INT, &limit, "limit", 'L', 0 }, + { 0, 0, 0, 0, 0 } + }; + + opt_index = parse_arguments (argc, argv, options, 1); + + if (opt_index < 0) { + return 1; } - argc -= i; - argv += i; + switch (format_sel) { + case NOTMUCH_FORMAT_TEXT: + format = &format_text; + break; + case NOTMUCH_FORMAT_JSON: + format = &format_json; + break; + } config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) @@ -500,7 +472,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) if (notmuch == NULL) return 1; - query_str = query_string_from_args (notmuch, argc, argv); + query_str = query_string_from_args (notmuch, argc-opt_index, argv+opt_index); if (query_str == NULL) { fprintf (stderr, "Out of memory.\n"); return 1; diff --git a/notmuch-show.c b/notmuch-show.c index 603992a6..19fb49f2 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -255,7 +255,9 @@ _extract_email_address (const void *ctx, const char *from) email = talloc_strdup (ctx, email); DONE: - /* XXX: How to free addresses here? */ + if (addresses) + g_object_unref (addresses); + return email; } @@ -469,9 +471,12 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out) /* This result can be NULL for things like "unknown-8bit". * Don't set a NULL filter as that makes GMime print * annoying assertion-failure messages on stderr. */ - if (charset_filter) + if (charset_filter) { g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter), charset_filter); + g_object_unref (charset_filter); + } + } wrapper = g_mime_part_get_content_object (GMIME_PART (part)); @@ -861,7 +866,7 @@ do_show_single (void *ctx, while (!feof (file)) { size = fread (buf, 1, sizeof (buf), file); - fwrite (buf, size, 1, stdout); + (void) fwrite (buf, size, 1, stdout); } fclose (file); diff --git a/notmuch-tag.c b/notmuch-tag.c index dded39ea..292c5da3 100644 --- a/notmuch-tag.c +++ b/notmuch-tag.c @@ -26,10 +26,85 @@ static void handle_sigint (unused (int sig)) { static char msg[] = "Stopping... \n"; - write(2, msg, sizeof(msg)-1); + (void) write(2, msg, sizeof(msg)-1); interrupted = 1; } +static char * +_escape_tag (char *buf, const char *tag) +{ + const char *in = tag; + char *out = buf; + /* Boolean terms surrounded by double quotes can contain any + * character. Double quotes are quoted by doubling them. */ + *out++ = '"'; + while (*in) { + if (*in == '"') + *out++ = '"'; + *out++ = *in++; + } + *out++ = '"'; + *out = 0; + return buf; +} + +static char * +_optimize_tag_query (void *ctx, const char *orig_query_string, char *argv[], + int *add_tags, int add_tags_count, + int *remove_tags, int remove_tags_count) +{ + /* This is subtler than it looks. Xapian ignores the '-' operator + * at the beginning both queries and parenthesized groups and, + * furthermore, the presence of a '-' operator at the beginning of + * a group can inhibit parsing of the previous operator. Hence, + * the user-provided query MUST appear first, but it is safe to + * parenthesize and the exclusion part of the query must not use + * the '-' operator (though the NOT operator is fine). */ + + char *escaped, *query_string; + const char *join = ""; + int i; + unsigned int max_tag_len = 0; + + /* Allocate a buffer for escaping tags. This is large enough to + * hold a fully escaped tag with every character doubled plus + * enclosing quotes and a NUL. */ + for (i = 0; i < add_tags_count; i++) + if (strlen (argv[add_tags[i]] + 1) > max_tag_len) + max_tag_len = strlen (argv[add_tags[i]] + 1); + for (i = 0; i < remove_tags_count; i++) + if (strlen (argv[remove_tags[i]] + 1) > max_tag_len) + max_tag_len = strlen (argv[remove_tags[i]] + 1); + escaped = talloc_array(ctx, char, max_tag_len * 2 + 3); + if (!escaped) + return NULL; + + /* Build the new query string */ + if (strcmp (orig_query_string, "*") == 0) + query_string = talloc_strdup (ctx, "("); + else + query_string = talloc_asprintf (ctx, "( %s ) and (", orig_query_string); + + for (i = 0; i < add_tags_count && query_string; i++) { + query_string = talloc_asprintf_append_buffer ( + query_string, "%snot tag:%s", join, + _escape_tag (escaped, argv[add_tags[i]] + 1)); + join = " or "; + } + for (i = 0; i < remove_tags_count && query_string; i++) { + query_string = talloc_asprintf_append_buffer ( + query_string, "%stag:%s", join, + _escape_tag (escaped, argv[remove_tags[i]] + 1)); + join = " or "; + } + + if (query_string) + query_string = talloc_strdup_append_buffer (query_string, ")"); + + talloc_free (escaped); + return query_string; +} + int notmuch_tag_command (void *ctx, unused (int argc), unused (char *argv[])) { @@ -93,6 +168,16 @@ notmuch_tag_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } + /* Optimize the query so it excludes messages that already have + * the specified set of tags. */ + query_string = _optimize_tag_query (ctx, query_string, argv, + add_tags, add_tags_count, + remove_tags, remove_tags_count); + if (query_string == NULL) { + fprintf (stderr, "Out of memory.\n"); + return 1; + } + config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) return 1; @@ -16,7 +16,7 @@ .\" along with this program. If not, see http://www.gnu.org/licenses/ . .\" .\" Author: Carl Worth <cworth@cworth.org> -.TH NOTMUCH 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH 1 2012-01-13 "Notmuch 0.11" .SH NAME notmuch \- thread-based email index, search, and tagging .SH SYNOPSIS @@ -85,7 +85,7 @@ The command is used to incorporate new mail into the notmuch database. .RS 4 .TP 4 -.B new +.BR new " [options...]" Find and import any new messages to the database. @@ -118,6 +118,22 @@ if has previously been completed, but .B "notmuch new" has not previously been run. + +The +.B new +command supports hooks. See the +.B "HOOKS" +section below for more details on hooks. + +Supported options for +.B new +include +.RS 4 +.TP 4 +.BR \-\-no\-hooks + +Prevents hooks from being run. +.RE .RE Several of the notmuch commands accept search terms with a common @@ -705,6 +721,38 @@ specify a date range to return messages from 2009\-10\-01 until the current time: $(date +%s \-d 2009\-10\-01)..$(date +%s) +.SH HOOKS +Hooks are scripts (or arbitrary executables or symlinks to such) that notmuch +invokes before and after certain actions. These scripts reside in +the .notmuch/hooks directory within the database directory and must have +executable permissions. + +The currently available hooks are described below. +.RS 4 +.TP 4 +.B pre\-new +This hook is invoked by the +.B new +command before scanning or importing new messages into the database. If this +hook exits with a non-zero status, notmuch will abort further processing of the +.B new +command. + +Typically this hook is used for fetching or delivering new mail to be imported +into the database. +.RE +.RS 4 +.TP 4 +.B post\-new +This hook is invoked by the +.B new +command after new messages have been imported into the database and initial tags +have been applied. The hook will not be run if there have been any errors during +the scan or import. + +Typically this hook is used to perform additional query\-based tagging on the +imported messages. +.RE .SH ENVIRONMENT The following environment variables can be used to control the behavior of notmuch. @@ -127,6 +127,32 @@ static const char search_terms_help[] = "\n" "\t\t$(date +%%s -d 2009-10-01)..$(date +%%s)\n\n"; +static const char hooks_help[] = + "\tHooks are scripts (or arbitrary executables or symlinks to such) that\n" + "\tnotmuch invokes before and after certain actions. These scripts reside\n" + "\tin the .notmuch/hooks directory within the database directory and must\n" + "\thave executable permissions.\n" + "\n" + "\tThe currently available hooks are described below.\n" + "\n" + "\tpre-new\n" + "\t\tThis hook is invoked by the new command before scanning or\n" + "\t\timporting new messages into the database. If this hook exits\n" + "\t\twith a non-zero status, notmuch will abort further processing\n" + "\t\tof the new command.\n" + "\n" + "\t\tTypically this hook is used for fetching or delivering new\n" + "\t\tmail to be imported into the database.\n" + "\n" + "\tpost-new\n" + "\t\tThis hook is invoked by the new command after new messages\n" + "\t\thave been imported into the database and initial tags have\n" + "\t\tbeen applied. The hook will not be run if there have been any\n" + "\t\terrors during the scan or import.\n" + "\n" + "\t\tTypically this hook is used to perform additional query-based\n" + "\t\ttagging on the imported messages.\n\n"; + static command_t commands[] = { { "setup", notmuch_setup_command, NULL, @@ -144,7 +170,7 @@ static command_t commands[] = { "\tInvoking notmuch with no command argument will run setup if\n" "\tthe setup command has not previously been completed." }, { "new", notmuch_new_command, - "[--verbose]", + "[options...]", "Find and import new messages to the notmuch database.", "\tScans all sub-directories of the mail directory, performing\n" "\tfull-text indexing on new messages that are found. Each new\n" @@ -159,8 +185,15 @@ static command_t commands[] = { "\tis delivered and you wish to incorporate it into the database.\n" "\tThese subsequent runs will be much quicker than the initial run.\n" "\n" + "\tThe new command supports hooks. See \"notmuch help hooks\" for\n" + "\tmore details on hooks.\n" + "\n" "\tSupported options for new include:\n" "\n" + "\t--no-hooks\n" + "\n" + "\t\tPrevent hooks from being run.\n" + "\n" "\t--verbose\n" "\n" "\t\tVerbose operation. Shows paths of message files as\n" @@ -529,6 +562,10 @@ notmuch_help_command (unused (void *ctx), int argc, char *argv[]) printf ("\n"); printf (search_terms_help); return 0; + } else if (strcmp (argv[0], "hooks") == 0) { + printf ("Help for <%s>\n\n", argv[0]); + printf (hooks_help); + return 0; } fprintf (stderr, diff --git a/test/.gitignore b/test/.gitignore index 9e97052d..e63c689d 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,4 +1,6 @@ test-results corpus.mail smtp-dummy +symbol-test +arg-test tmp.* diff --git a/test/Makefile.local b/test/Makefile.local index 8eb04330..fa2df734 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -2,19 +2,32 @@ dir := test +extra_cflags += -I. + smtp_dummy_srcs = \ $(notmuch_compat_srcs) \ $(dir)/smtp-dummy.c smtp_dummy_modules = $(smtp_dummy_srcs:.c=.o) +$(dir)/arg-test: $(dir)/arg-test.o command-line-arguments.o util/libutil.a + $(call quiet,CC) -I. $^ -o $@ + $(dir)/smtp-dummy: $(smtp_dummy_modules) $(call quiet,CC) $^ -o $@ +$(dir)/symbol-test: $(dir)/symbol-test.o + $(call quiet,CXX) $^ -o $@ -Llib -lnotmuch -lxapian + .PHONY: test check -test: all $(dir)/smtp-dummy + +test-binaries: $(dir)/arg-test $(dir)/smtp-dummy $(dir)/symbol-test + +test: all test-binaries @${dir}/notmuch-test $(OPTIONS) check: test -CLEAN := $(CLEAN) $(dir)/smtp-dummy +CLEAN := $(CLEAN) $(dir)/smtp-dummy $(dir)/smtp-dummy.o \ + $(dir)/symbol-test $(dir)/symbol-test.o \ + $(dir)/arg-test $(dir)/arg-test.o diff --git a/test/README b/test/README index 2481f16d..7b2e96d4 100644 --- a/test/README +++ b/test/README @@ -13,7 +13,8 @@ notmuch-test script). Either command will run all available tests. Alternately, you can run a specific subset of tests by simply invoking one of the executable scripts in this directory, (such as ./search, -./reply, etc.) +./reply, etc). Note that you will probably want "make test-binaries" +before running individual tests. The following command-line options are available when running tests: @@ -187,8 +188,8 @@ library for your script to use. is to summarize successes and failures in the test script and exit with an appropriate error code. -There are also a number of mail-specific functions which are useful in -writing tests: +There are also a number of notmuch-specific auxiliary functions and +variables which are useful in writing tests: generate_message @@ -212,3 +213,15 @@ writing tests: will initialize the mail database to a known state of 50 sample messages, (culled from the early history of the notmuch mailing list). + + notmuch_counter_reset + $notmuch_counter_command + notmuch_counter_value + + These allow to count how many times notmuch binary is called. + notmuch_counter_reset() function generates a script that counts + how many times it is called and resets the counter to zero. The + function sets $notmuch_counter_command variable to the path to the + generated script that should be called instead of notmuch to do + the counting. The notmuch_counter_value() function prints the + current counter value. diff --git a/test/arg-test.c b/test/arg-test.c new file mode 100644 index 00000000..adc56e30 --- /dev/null +++ b/test/arg-test.c @@ -0,0 +1,52 @@ +#include <stdio.h> +#include "command-line-arguments.h" + + +int main(int argc, char **argv){ + + int opt_index=1; + + int kw_val=0; + int int_val=0; + char *pos_arg1=NULL; + char *pos_arg2=NULL; + char *string_val=NULL; + + notmuch_opt_desc_t options[] = { + { NOTMUCH_OPT_KEYWORD, &kw_val, "keyword", 'k', + (notmuch_keyword_t []){ { "one", 1 }, + { "two", 2 }, + { 0, 0 } } }, + { NOTMUCH_OPT_INT, &int_val, "int", 'i', 0}, + { NOTMUCH_OPT_STRING, &string_val, "string", 's', 0}, + { NOTMUCH_OPT_POSITION, &pos_arg1, 0,0, 0}, + { NOTMUCH_OPT_POSITION, &pos_arg2, 0,0, 0}, + { 0, 0, 0, 0, 0 } }; + + opt_index = parse_arguments(argc, argv, options, 1); + + if (opt_index < 0) + return 1; + + if (kw_val) + printf("keyword %d\n", kw_val); + + if (int_val) + printf("int %d\n", int_val); + + if (string_val) + printf("string %s\n", string_val); + + if (pos_arg1) + printf("positional arg 1 %s\n", pos_arg1); + + if (pos_arg2) + printf("positional arg 2 %s\n", pos_arg1); + + + for ( ; opt_index < argc ; opt_index ++) { + printf("non parsed arg %d = %s\n", opt_index, argv[opt_index]); + } + + return 0; +} diff --git a/test/argument-parsing b/test/argument-parsing new file mode 100755 index 00000000..672de0b3 --- /dev/null +++ b/test/argument-parsing @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +test_description="argument parsing" +. ./test-lib.sh + +test_begin_subtest "sanity check" +$TEST_DIRECTORY/arg-test pos1 --keyword=one --string=foo pos2 --int=7 > OUTPUT +cat <<EOF > EXPECTED +keyword 1 +int 7 +string foo +positional arg 1 pos1 +positional arg 2 pos1 +EOF +test_expect_equal_file OUTPUT EXPECTED + +test_done diff --git a/test/atomicity b/test/atomicity index ad7d4a3c..6df0a00e 100755 --- a/test/atomicity +++ b/test/atomicity @@ -7,8 +7,7 @@ test_description='atomicity' # final database contents should be the same regardless of when (or # if) it is killed and restarted. -if which gdb 1>/dev/null 2>&1; then - test_set_prereq GDB +if test_require_external_prereq gdb; then # Create a maildir structure to also stress flag synchronization mkdir $MAIL_DIR/cur @@ -91,14 +90,11 @@ if which gdb 1>/dev/null 2>&1; then i=$(expr $end - 1) fi done -else - say_color info "%-6s" "WARNING" - echo " Missing test prerequisite GDB" -fi +fi test_begin_subtest '"notmuch new" is idempotent under arbitrary aborts' -test_expect_equal_file GDB searchall expectall +test_expect_equal_file searchall expectall -test_expect_success GDB "detected $outcount>10 abort points" "test $outcount -gt 10" +test_expect_success "detected $outcount>10 abort points" "test $outcount -gt 10" test_done @@ -51,17 +51,11 @@ test_expect_code 2 'failure to clean up causes the test to fail' ' # Ensure that all tests are being run test_begin_subtest 'Ensure that all available tests will be run by notmuch-test' -eval $(sed -n -e '/^TESTS="$/,/^"$/p' notmuch-test $TEST_DIRECTORY/notmuch-test) +eval $(sed -n -e '/^TESTS="$/,/^"$/p' $TEST_DIRECTORY/notmuch-test) tests_in_suite=$(for i in $TESTS; do echo $i; done | sort) -available=$(ls -1 $TEST_DIRECTORY/ | \ - sed -r -e "/^(aggregate-results.sh|Makefile|Makefile.local|notmuch-test)/d" \ - -e "/^(README|test-lib.sh|test-lib.el|test-results|tmp.*|valgrind|corpus*)/d" \ - -e "/^(emacs.expected-output|smtp-dummy|smtp-dummy.c|test-verbose|symbol-test.cc)/d" \ - -e "/^(test.expected-output|.*~)/d" \ - -e "/^(gnupg-secret-key.asc)/d" \ - -e "/^(gnupg-secret-key.NOTE)/d" \ - -e "/^(atomicity.gdb)/d" \ - | sort) +available=$(find "$TEST_DIRECTORY" -maxdepth 1 -type f -executable -printf '%f\n' | \ + sed -r -e "/^(aggregate-results.sh|notmuch-test|smtp-dummy|test-verbose|symbol-test|arg-test)$/d" | \ + sort) test_expect_equal "$tests_in_suite" "$available" EXPECTED=$TEST_DIRECTORY/test.expected-output @@ -50,6 +50,27 @@ test_emacs "(notmuch-show \"$maildir_storage_thread\") (test-output)" test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-maildir-storage +test_begin_subtest "Basic notmuch-show view in emacs default indentation" +maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu) +test_emacs "(let ((notmuch-show-indent-messages-width 1)) + (notmuch-show \"$maildir_storage_thread\") + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-maildir-storage + +test_begin_subtest "Basic notmuch-show view in emacs without indentation" +maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu) +test_emacs "(let ((notmuch-show-indent-messages-width 0)) + (notmuch-show \"$maildir_storage_thread\") + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-maildir-storage-without-indentation + +test_begin_subtest "Basic notmuch-show view in emacs with fourfold indentation" +maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu) +test_emacs "(let ((notmuch-show-indent-messages-width 4)) + (notmuch-show \"$maildir_storage_thread\") + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-maildir-storage-with-fourfold-indentation + test_begin_subtest "notmuch-show for message with invalid From" add_message "[subject]=\"message-with-invalid-from\"" \ "[from]=\"\\\"Invalid \\\" From\\\" <test_suite@notmuchmail.org>\"" @@ -369,8 +390,18 @@ test_emacs '(notmuch-show "id:\"bought\"") (rotate-yank-pointer 1)) (reverse-region (point-min) (point-max)) (test-output)' -sed -i -e 's/^.*tmp.emacs\/mail.*$/FILENAME/' OUTPUT -test_expect_equal_file OUTPUT $EXPECTED/emacs-stashing +cat <<EOF >EXPECTED +Sat, 01 Jan 2000 12:00:00 -0000 +Some One <someone@somewhere.org> +Some One Else <notsomeone@somewhere.org> +Notmuch <notmuch@notmuchmail.org> +Stash my stashables +id:"bought" +bought +inbox,stashtest +${gen_msg_filename} +EOF +test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Stashing in notmuch-search" test_emacs '(notmuch-search "id:\"bought\"") @@ -381,33 +412,7 @@ test_emacs '(notmuch-search "id:\"bought\"") (yank) (test-output)' sed -i -e 's/^thread:.*$/thread:XXX/' OUTPUT -test_expect_equal $(cat OUTPUT) "thread:XXX" - -test_begin_subtest 'Hiding message following HTML part' -test_subtest_known_broken -id='html-message@notmuchmail.org' -emacs_deliver_message \ - 'HTML message' \ - '' \ - "(message-goto-eoh) - (insert \"Message-ID: <$id>\n\") - (message-goto-body) - (mml-insert-part \"text/html\") - (insert \"<body>This is a test HTML message</body>\")" -emacs_deliver_message \ - 'Reply to HTML message' \ - 'This is a reply to the test HTML message' \ - "(message-goto-eoh) - (insert \"In-Reply-To: <$id>\n\")" -test_emacs "(notmuch-show \"id:$id\") \ - (notmuch-show-next-message) \ - (command-execute (key-binding (kbd \"RET\"))) \ - (test-visible-output)" -test_emacs "(notmuch-show \"id:$id\") \ - (notmuch-show-next-message) \ - (notmuch-show-toggle-message) \ - (test-visible-output \"EXPECTED\")" -test_expect_equal_file OUTPUT EXPECTED +test_expect_equal "$(cat OUTPUT)" "thread:XXX" test_begin_subtest 'notmuch-show-advance-and-archive with invisible signature' message1='id:20091118010116.GC25380@dottiness.seas.harvard.edu' @@ -441,4 +446,72 @@ test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail. (test-visible-output)' test_expect_equal_file OUTPUT EXPECTED +test_begin_subtest "Do not call notmuch for non-inlinable application/mpeg parts" +id='message-with-application/mpeg-attachment@notmuchmail.org' +emacs_deliver_message \ + 'Message with application/mpeg attachment' \ + '' \ + "(message-goto-eoh) + (insert \"Message-ID: <$id>\n\") + (message-goto-body) + (mml-insert-part \"application/mpeg\") + (insert \"a fake mp3 file\")" +notmuch_counter_reset +test_emacs "(let ((notmuch-command \"$notmuch_counter_command\")) + (notmuch-show \"id:$id\"))" +test_expect_equal $(notmuch_counter_value) 1 + +test_begin_subtest "Do not call notmuch for non-inlinable audio/mpeg parts" +id='message-with-audio/mpeg-attachment@notmuchmail.org' +emacs_deliver_message \ + 'Message with audio/mpeg attachment' \ + '' \ + "(message-goto-eoh) + (insert \"Message-ID: <$id>\n\") + (message-goto-body) + (mml-insert-part \"audio/mpeg\") + (insert \"a fake mp3 file\")" +notmuch_counter_reset +test_emacs "(let ((notmuch-command \"$notmuch_counter_command\")) + (notmuch-show \"id:$id\"))" +test_expect_equal $(notmuch_counter_value) 1 + +test_begin_subtest "notmuch-hello-mode hook is called" +counter=$(test_emacs \ + '(let ((notmuch-hello-mode-hook-counter 0)) + (kill-buffer "*notmuch-hello*") + (notmuch-hello) + notmuch-hello-mode-hook-counter)' +) +test_expect_equal "$counter" 1 + +test_begin_subtest "notmuch-hello-mode hook is not called on updates" +counter=$(test_emacs \ + '(let ((notmuch-hello-mode-hook-counter 0)) + (kill-buffer "*notmuch-hello*") + (notmuch-hello) + (notmuch-hello-update) + notmuch-hello-mode-hook-counter)' +) +test_expect_equal "$counter" 1 + +test_begin_subtest "notmuch-hello-refresh hook is called" +counter=$(test_emacs \ + '(let ((notmuch-hello-refresh-hook-counter 0)) + (kill-buffer "*notmuch-hello*") + (notmuch-hello) + notmuch-hello-refresh-hook-counter)' +) +test_expect_equal "$counter" 1 + +test_begin_subtest "notmuch-hello-refresh hook is called on updates" +counter=$(test_emacs \ + '(let ((notmuch-hello-refresh-hook-counter 0)) + (kill-buffer "*notmuch-hello*") + (notmuch-hello) + (notmuch-hello-update) + notmuch-hello-refresh-hook-counter)' +) +test_expect_equal "$counter" 2 + test_done diff --git a/test/emacs.expected-output/emacs-stashing b/test/emacs.expected-output/emacs-stashing deleted file mode 100644 index 49235947..00000000 --- a/test/emacs.expected-output/emacs-stashing +++ /dev/null @@ -1,9 +0,0 @@ -Sat, 01 Jan 2000 12:00:00 -0000 -Some One <someone@somewhere.org> -Some One Else <notsomeone@somewhere.org> -Notmuch <notmuch@notmuchmail.org> -Stash my stashables -id:"bought" -bought -inbox,stashtest -FILENAME diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation new file mode 100644 index 00000000..41e2aaa3 --- /dev/null +++ b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation @@ -0,0 +1,215 @@ +Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed) +Subject: [notmuch] Working with Maildir storage? +To: notmuch@notmuchmail.org +Date: Tue, 17 Nov 2009 14:00:54 -0500 + +[ multipart/mixed ] +[ multipart/signed ] +[ text/plain ] +I saw the LWN article and decided to take a look at notmuch. I'm +currently using mutt and mairix to index and read a collection of +Maildir mail folders (around 40,000 messages total). + +notmuch indexed the messages without complaint, but my attempt at +searching bombed out. Running, for example: + + notmuch search storage + +Resulted in 4604 lines of errors along the lines of: + + Error opening + /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S: + Too many open files + +I'm curious if this is expected behavior (i.e., notmuch does not work +with Maildir) or if something else is going on. + +Cheers, + +[ 5-line signature. Click/Enter to show. ] +-- +Lars Kellogg-Stedman <lars@seas.harvard.edu> +Senior Technologist, Computing and Information Technology +Harvard University School of Engineering and Applied Sciences + +[ application/pgp-signature ] +[ text/plain ] +[ 4-line signature. Click/Enter to show. ] +_______________________________________________ +notmuch mailing list +notmuch@notmuchmail.org +http://notmuchmail.org/mailman/listinfo/notmuch + Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread) + Subject: Re: [notmuch] Working with Maildir storage? + To: notmuch@notmuchmail.org + Date: Wed, 18 Nov 2009 01:02:38 +0600 + + [ multipart/mixed ] + [ multipart/signed ] + [ text/plain ] + + Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did gyre and gimble: + + LK> Resulted in 4604 lines of errors along the lines of: + + LK> Error opening + LK> /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S: + LK> Too many open files + + See the patch just posted here. + + [ 2-line signature. Click/Enter to show. ] + -- + http://fossarchy.blogspot.com/ + [ application/pgp-signature ] + [ text/plain ] + [ 4-line signature. Click/Enter to show. ] + _______________________________________________ + notmuch mailing list + notmuch@notmuchmail.org + http://notmuchmail.org/mailman/listinfo/notmuch + Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread) + Subject: Re: [notmuch] Working with Maildir storage? + To: Mikhail Gusarov <dottedmag@dottedmag.net> + Cc: notmuch@notmuchmail.org + Date: Tue, 17 Nov 2009 15:33:01 -0500 + + [ multipart/mixed ] + [ multipart/signed ] + [ text/plain ] + > See the patch just posted here. + + Is the list archived anywhere? The obvious archives + (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I + think I subscribed too late to get the patch (I only just saw the + discussion about it). + + It doesn't look like the patch is in git yet. + + -- Lars + + [ 5-line signature. Click/Enter to show. ] + -- + Lars Kellogg-Stedman <lars@seas.harvard.edu> + Senior Technologist, Computing and Information Technology + Harvard University School of Engineering and Applied Sciences + + [ application/pgp-signature ] + [ text/plain ] + [ 4-line signature. Click/Enter to show. ] + _______________________________________________ + notmuch mailing list + notmuch@notmuchmail.org + http://notmuchmail.org/mailman/listinfo/notmuch + Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread) + Subject: [notmuch] Working with Maildir storage? + To: notmuch@notmuchmail.org + Date: Wed, 18 Nov 2009 02:50:48 +0600 + + + Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu did gyre and gimble: + + LK> Is the list archived anywhere? The obvious archives + LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I + LK> think I subscribed too late to get the patch (I only just saw the + LK> discussion about it). + + LK> It doesn't look like the patch is in git yet. + + Just has been pushed + + [ 10-line signature. Click/Enter to show. ] + -- + http://fossarchy.blogspot.com/ + -------------- next part -------------- + A non-text attachment was scrubbed... + Name: not available + Type: application/pgp-signature + Size: 834 bytes + Desc: not available + URL: <http://notmuchmail.org/pipermail/notmuch/attachments/20091118/0e33d964/attachment.pgp> + + Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread) + Subject: [notmuch] Working with Maildir storage? + To: notmuch@notmuchmail.org + Date: Tue, 17 Nov 2009 13:24:13 -0800 + + On Tue, 17 Nov 2009 15:33:01 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote: + > > See the patch just posted here. + + I've also pushed a slightly more complicated (and complete) fix to my + private notmuch repository + + git://keithp.com/git/notmuch + + > Is the list archived anywhere? + + Oops. Looks like Carl's mail server is broken. He's traveling to + Barcelona today and so it won't get fixed for a while. + + Thanks to everyone for trying out notmuch! + + -keith + + 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 + Date: Tue, 17 Nov 2009 19:50:40 -0500 + + [ multipart/mixed ] + [ multipart/signed ] + [ text/plain ] + > I've also pushed a slightly more complicated (and complete) fix to my + > private notmuch repository + + The version of lib/messages.cc in your repo doesn't build because it's + missing "#include <stdint.h>" (for the uint32_t on line 466). + + [ 5-line signature. Click/Enter to show. ] + -- + Lars Kellogg-Stedman <lars@seas.harvard.edu> + Senior Technologist, Computing and Information Technology + Harvard University School of Engineering and Applied Sciences + + [ application/pgp-signature ] + [ text/plain ] + [ 4-line signature. Click/Enter to show. ] + _______________________________________________ + notmuch mailing list + notmuch@notmuchmail.org + http://notmuchmail.org/mailman/listinfo/notmuch + Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread) + Subject: [notmuch] Working with Maildir storage? + To: notmuch@notmuchmail.org + Date: Wed, 18 Nov 2009 02:08:10 -0800 + + On Tue, 17 Nov 2009 14:00:54 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote: + > I saw the LWN article and decided to take a look at notmuch. I'm + > currently using mutt and mairix to index and read a collection of + > Maildir mail folders (around 40,000 messages total). + + Welcome, Lars! + + I hadn't even seen that Keith's blog post had been picked up by lwn.net. + That's very interesting. So, thanks for coming and trying out notmuch. + + > Error opening + > /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S: + > Too many open files + + Sadly, the lwn article coincided with me having just introduced this + bug, and then getting on a Trans-Atlantic flight. So I fixed the bug + fairly quickly, but there was quite a bit of latency before I could push + the fix out. It should be fixed now. + + > I'm curious if this is expected behavior (i.e., notmuch does not work + > with Maildir) or if something else is going on. + + Notmuch works just fine with maildir---it's one of the things that it + likes the best. + + Happy hacking, + + -Carl + diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation new file mode 100644 index 00000000..fa2108ef --- /dev/null +++ b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation @@ -0,0 +1,215 @@ +Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed) +Subject: [notmuch] Working with Maildir storage? +To: notmuch@notmuchmail.org +Date: Tue, 17 Nov 2009 14:00:54 -0500 + +[ multipart/mixed ] +[ multipart/signed ] +[ text/plain ] +I saw the LWN article and decided to take a look at notmuch. I'm +currently using mutt and mairix to index and read a collection of +Maildir mail folders (around 40,000 messages total). + +notmuch indexed the messages without complaint, but my attempt at +searching bombed out. Running, for example: + + notmuch search storage + +Resulted in 4604 lines of errors along the lines of: + + Error opening + /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S: + Too many open files + +I'm curious if this is expected behavior (i.e., notmuch does not work +with Maildir) or if something else is going on. + +Cheers, + +[ 5-line signature. Click/Enter to show. ] +-- +Lars Kellogg-Stedman <lars@seas.harvard.edu> +Senior Technologist, Computing and Information Technology +Harvard University School of Engineering and Applied Sciences + +[ application/pgp-signature ] +[ text/plain ] +[ 4-line signature. Click/Enter to show. ] +_______________________________________________ +notmuch mailing list +notmuch@notmuchmail.org +http://notmuchmail.org/mailman/listinfo/notmuch +Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread) +Subject: Re: [notmuch] Working with Maildir storage? +To: notmuch@notmuchmail.org +Date: Wed, 18 Nov 2009 01:02:38 +0600 + +[ multipart/mixed ] +[ multipart/signed ] +[ text/plain ] + +Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did gyre and gimble: + + LK> Resulted in 4604 lines of errors along the lines of: + + LK> Error opening + LK> /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S: + LK> Too many open files + +See the patch just posted here. + +[ 2-line signature. Click/Enter to show. ] +-- +http://fossarchy.blogspot.com/ +[ application/pgp-signature ] +[ text/plain ] +[ 4-line signature. Click/Enter to show. ] +_______________________________________________ +notmuch mailing list +notmuch@notmuchmail.org +http://notmuchmail.org/mailman/listinfo/notmuch +Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread) +Subject: Re: [notmuch] Working with Maildir storage? +To: Mikhail Gusarov <dottedmag@dottedmag.net> +Cc: notmuch@notmuchmail.org +Date: Tue, 17 Nov 2009 15:33:01 -0500 + +[ multipart/mixed ] +[ multipart/signed ] +[ text/plain ] +> See the patch just posted here. + +Is the list archived anywhere? The obvious archives +(http://notmuchmail.org/pipermail/notmuch/) aren't available, and I +think I subscribed too late to get the patch (I only just saw the +discussion about it). + +It doesn't look like the patch is in git yet. + +-- Lars + +[ 5-line signature. Click/Enter to show. ] +-- +Lars Kellogg-Stedman <lars@seas.harvard.edu> +Senior Technologist, Computing and Information Technology +Harvard University School of Engineering and Applied Sciences + +[ application/pgp-signature ] +[ text/plain ] +[ 4-line signature. Click/Enter to show. ] +_______________________________________________ +notmuch mailing list +notmuch@notmuchmail.org +http://notmuchmail.org/mailman/listinfo/notmuch +Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread) +Subject: [notmuch] Working with Maildir storage? +To: notmuch@notmuchmail.org +Date: Wed, 18 Nov 2009 02:50:48 +0600 + + +Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu did gyre and gimble: + + LK> Is the list archived anywhere? The obvious archives + LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I + LK> think I subscribed too late to get the patch (I only just saw the + LK> discussion about it). + + LK> It doesn't look like the patch is in git yet. + +Just has been pushed + +[ 10-line signature. Click/Enter to show. ] +-- +http://fossarchy.blogspot.com/ +-------------- next part -------------- +A non-text attachment was scrubbed... +Name: not available +Type: application/pgp-signature +Size: 834 bytes +Desc: not available +URL: <http://notmuchmail.org/pipermail/notmuch/attachments/20091118/0e33d964/attachment.pgp> + +Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread) +Subject: [notmuch] Working with Maildir storage? +To: notmuch@notmuchmail.org +Date: Tue, 17 Nov 2009 13:24:13 -0800 + +On Tue, 17 Nov 2009 15:33:01 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote: +> > See the patch just posted here. + +I've also pushed a slightly more complicated (and complete) fix to my +private notmuch repository + +git://keithp.com/git/notmuch + +> Is the list archived anywhere? + +Oops. Looks like Carl's mail server is broken. He's traveling to +Barcelona today and so it won't get fixed for a while. + +Thanks to everyone for trying out notmuch! + +-keith + +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 +Date: Tue, 17 Nov 2009 19:50:40 -0500 + +[ multipart/mixed ] +[ multipart/signed ] +[ text/plain ] +> I've also pushed a slightly more complicated (and complete) fix to my +> private notmuch repository + +The version of lib/messages.cc in your repo doesn't build because it's +missing "#include <stdint.h>" (for the uint32_t on line 466). + +[ 5-line signature. Click/Enter to show. ] +-- +Lars Kellogg-Stedman <lars@seas.harvard.edu> +Senior Technologist, Computing and Information Technology +Harvard University School of Engineering and Applied Sciences + +[ application/pgp-signature ] +[ text/plain ] +[ 4-line signature. Click/Enter to show. ] +_______________________________________________ +notmuch mailing list +notmuch@notmuchmail.org +http://notmuchmail.org/mailman/listinfo/notmuch +Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread) +Subject: [notmuch] Working with Maildir storage? +To: notmuch@notmuchmail.org +Date: Wed, 18 Nov 2009 02:08:10 -0800 + +On Tue, 17 Nov 2009 14:00:54 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote: +> I saw the LWN article and decided to take a look at notmuch. I'm +> currently using mutt and mairix to index and read a collection of +> Maildir mail folders (around 40,000 messages total). + +Welcome, Lars! + +I hadn't even seen that Keith's blog post had been picked up by lwn.net. +That's very interesting. So, thanks for coming and trying out notmuch. + +> Error opening +> /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S: +> Too many open files + +Sadly, the lwn article coincided with me having just introduced this +bug, and then getting on a Trans-Atlantic flight. So I fixed the bug +fairly quickly, but there was quite a bit of latency before I could push +the fix out. It should be fixed now. + +> I'm curious if this is expected behavior (i.e., notmuch does not work +> with Maildir) or if something else is going on. + +Notmuch works just fine with maildir---it's one of the things that it +likes the best. + +Happy hacking, + +-Carl + diff --git a/test/hooks b/test/hooks new file mode 100755 index 00000000..77e8569b --- /dev/null +++ b/test/hooks @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +test_description='hooks' +. ./test-lib.sh + +HOOK_DIR=${MAIL_DIR}/.notmuch/hooks + +create_echo_hook () { + local TOKEN="${RANDOM}" + mkdir -p ${HOOK_DIR} + cat <<EOF >"${HOOK_DIR}/${1}" +#!/bin/sh +echo "${TOKEN}" > ${3} +EOF + chmod +x "${HOOK_DIR}/${1}" + echo "${TOKEN}" > ${2} +} + +create_failing_hook () { + mkdir -p ${HOOK_DIR} + cat <<EOF >"${HOOK_DIR}/${1}" +#!/bin/sh +exit 13 +EOF + chmod +x "${HOOK_DIR}/${1}" +} + +rm_hooks () { + rm -rf ${HOOK_DIR} +} + +# add a message to generate mail dir and database +add_message + +test_begin_subtest "pre-new is run" +rm_hooks +generate_message +create_echo_hook "pre-new" expected output +notmuch new > /dev/null +test_expect_equal_file expected output + +test_begin_subtest "post-new is run" +rm_hooks +generate_message +create_echo_hook "post-new" expected output +notmuch new > /dev/null +test_expect_equal_file expected output + +test_begin_subtest "pre-new is run before post-new" +rm_hooks +generate_message +create_echo_hook "pre-new" pre-new.expected pre-new.output +create_echo_hook "post-new" post-new.expected post-new.output +notmuch new > /dev/null +test_expect_equal_file post-new.expected post-new.output + +test_begin_subtest "pre-new non-zero exit status (hook status)" +rm_hooks +generate_message +create_failing_hook "pre-new" +output=`notmuch new 2>&1` +test_expect_equal "$output" "Error: pre-new hook failed with status 13" + +# depends on the previous subtest leaving broken hook behind +test_expect_code 1 "pre-new non-zero exit status (notmuch status)" "notmuch new" + +# depends on the previous subtests leaving 1 new message behind +test_begin_subtest "pre-new non-zero exit status aborts new" +rm_hooks +output=$(NOTMUCH_NEW) +test_expect_equal "$output" "Added 1 new message to the database." + +test_begin_subtest "post-new non-zero exit status (hook status)" +rm_hooks +generate_message +create_failing_hook "post-new" +NOTMUCH_NEW 2>output.stderr >output +cat output.stderr >> output +echo "Added 1 new message to the database." > expected +echo "Error: post-new hook failed with status 13" >> expected +test_expect_equal_file expected output + +# depends on the previous subtest leaving broken hook behind +test_expect_code 1 "post-new non-zero exit status (notmuch status)" "notmuch new" + +# test_begin_subtest "hook without executable permissions" +rm_hooks +mkdir -p ${HOOK_DIR} +cat <<EOF >"${HOOK_DIR}/pre-new" +#!/bin/sh +echo foo +EOF +output=`notmuch new 2>&1` +test_expect_code 1 "hook without executable permissions" "notmuch new" + +# test_begin_subtest "hook execution failure" +rm_hooks +mkdir -p ${HOOK_DIR} +cat <<EOF >"${HOOK_DIR}/pre-new" +no hashbang, execl fails +EOF +chmod +x "${HOOK_DIR}/pre-new" +test_expect_code 1 "hook execution failure" "notmuch new" + +test_done @@ -12,6 +12,7 @@ add_message "[subject]=\"json-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00 output=$(notmuch search --format=json "json-search-message" | notmuch_search_sanitize) test_expect_equal "$output" "[{\"thread\": \"XXX\", \"timestamp\": 946728000, +\"date_relative\": \"2000-01-01\", \"matched\": 1, \"total\": 1, \"authors\": \"Notmuch Test Suite\", @@ -41,6 +42,7 @@ add_message "[subject]=\"json-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Ja output=$(notmuch search --format=json "jsön-search-méssage" | notmuch_search_sanitize) test_expect_equal "$output" "[{\"thread\": \"XXX\", \"timestamp\": 946728000, +\"date_relative\": \"2000-01-01\", \"matched\": 1, \"total\": 1, \"authors\": \"Notmuch Test Suite\", diff --git a/test/notmuch-test b/test/notmuch-test index 113ea7cf..ded79e8f 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -48,6 +48,8 @@ TESTS=" search-folder-coherence atomicity python + hooks + argument-parsing " TESTS=${NOTMUCH_TESTS:=$TESTS} @@ -62,10 +64,13 @@ else TEST_TIMEOUT_CMD="" fi +trap 'e=$?; kill $!; exit $e' HUP INT TERM # Run the tests for test in $TESTS; do - $TEST_TIMEOUT_CMD ./$test "$@" + $TEST_TIMEOUT_CMD ./$test "$@" & + wait $! done +trap - HUP INT TERM # Report results ./aggregate-results.sh test-results/* diff --git a/test/python b/test/python index f737749f..c3aa7266 100755 --- a/test/python +++ b/test/python @@ -5,9 +5,7 @@ test_description="python bindings" add_email_corpus test_begin_subtest "compare thread ids" -LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib \ -PYTHONPATH=$TEST_DIRECTORY/../bindings/python \ -python <<EOF | sort > OUTPUT +test_python <<EOF import notmuch db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE) q_new = notmuch.Query(db, 'tag:inbox') @@ -15,5 +13,5 @@ for t in q_new.search_threads(): print t.get_thread_id() EOF notmuch search --output=threads tag:inbox | sed s/^thread:// | sort > EXPECTED -test_expect_equal_file OUTPUT EXPECTED +test_expect_equal_file <(sort OUTPUT) EXPECTED test_done @@ -1,4 +1,4 @@ -#!/usr//bin/env bash +#!/usr/bin/env bash test_description='notmuch show --format=raw' . ./test-lib.sh diff --git a/test/symbol-hiding b/test/symbol-hiding index d0b31aec..68f0d1b1 100755 --- a/test/symbol-hiding +++ b/test/symbol-hiding @@ -12,15 +12,22 @@ test_description='exception symbol hiding' . ./test-lib.sh run_test(){ - result=$(LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib ./symbol-test 2>&1) + result=$(LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib $TEST_DIRECTORY/symbol-test 2>&1) } output="A Xapian exception occurred opening database: Couldn't stat 'fakedb/.notmuch/xapian' caught No chert database found at path \`./nonexistant'" -g++ -o symbol-test -I$TEST_DIRECTORY/../lib $TEST_DIRECTORY/symbol-test.cc -L$TEST_DIRECTORY/../lib -lnotmuch -lxapian mkdir -p fakedb/.notmuch + test_expect_success 'running test' run_test + test_begin_subtest 'checking output' test_expect_equal "$result" "$output" + +test_begin_subtest 'comparing existing to exported symbols' +objdump -t $TEST_DIRECTORY/../lib/*.o | awk '$4 == ".text" && $6 ~ "^notmuch" {print $6}' | sort | uniq > ACTUAL +sed -n 's/[[:blank:]]*\(notmuch_[^;]*\);/\1/p' $TEST_DIRECTORY/../notmuch.sym | sort | uniq > EXPORTED +test_expect_equal_file EXPORTED ACTUAL + test_done diff --git a/test/symbol-test.cc b/test/symbol-test.cc index 1de06eae..1548ca40 100644 --- a/test/symbol-test.cc +++ b/test/symbol-test.cc @@ -1,17 +1,17 @@ #include <stdio.h> #include <xapian.h> #include <notmuch.h> -main (int argc, char **argv){ - notmuch_database_t *notmuch - = notmuch_database_open ("fakedb", - NOTMUCH_DATABASE_MODE_READ_ONLY); - try{ - (void)new Xapian::WritableDatabase ("./nonexistant", Xapian::DB_OPEN); +int main() { + (void) notmuch_database_open("fakedb", NOTMUCH_DATABASE_MODE_READ_ONLY); + + try { + (void) new Xapian::WritableDatabase("./nonexistant", 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; } + return 1; } diff --git a/test/test-lib.el b/test/test-lib.el index 97ae5938..3b817c37 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -61,3 +61,18 @@ running, quit if it terminated." (if (not (process-attributes pid)) (kill-emacs) (run-at-time "1 min" nil 'orphan-watchdog 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) diff --git a/test/test-lib.sh b/test/test-lib.sh index cf309f94..b5e346c0 100755..100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -116,6 +116,16 @@ do esac done +if test -n "$debug"; then + print_subtest () { + printf " %-4s" "[$((test_count - 1))]" + } +else + print_subtest () { + true + } +fi + if test -n "$color"; then say_color () { ( @@ -132,6 +142,7 @@ if test -n "$color"; then printf " " printf "$@" tput sgr0 + print_subtest ) } else @@ -140,6 +151,7 @@ else shift printf " " printf "$@" + print_subtest } fi @@ -398,6 +410,8 @@ emacs_deliver_message () (insert \"${body}\") $@ (message-send-and-exit))" + # opportunistically quit smtp-dummy in case above fails. + { echo QUIT > /dev/tcp/localhost/25025; } 2>/dev/null wait ${smtp_dummy_pid} notmuch new >/dev/null } @@ -427,7 +441,7 @@ test_begin_subtest () error "bug in test script: Missing test_expect_equal in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}" fi test_subtest_name="$1" - test_subtest_known_broken_= + test_reset_state_ # Remember stdout and stderr file descriptors and redirect test # output to the previously prepared file descriptors 3 and 4 (see # below) @@ -546,6 +560,33 @@ test_have_prereq () { esac } +# declare prerequisite for the given external binary +test_declare_external_prereq () { + binary="$1" + test "$#" = 2 && name=$2 || name="$binary(1)" + + hash $binary 2>/dev/null || eval " + test_missing_external_prereq_${binary}_=t +$binary () { + echo -n \"\$test_subtest_missing_external_prereqs_ \" | grep -qe \" $name \" || + test_subtest_missing_external_prereqs_=\"\$test_subtest_missing_external_prereqs_ $name\" + false +}" +} + +# Explicitly require external prerequisite. Useful when binary is +# called indirectly (e.g. from emacs). +# Returns success if dependency is available, failure otherwise. +test_require_external_prereq () { + binary="$1" + if [ "$(eval echo -n \$test_missing_external_prereq_${binary}_)" = t ]; then + # dependency is missing, call the replacement function to note it + eval "$binary" + else + true + fi +} + # You are not expected to call test_ok_ and test_failure_ directly, use # the text_expect_* functions instead. @@ -579,14 +620,14 @@ test_failure_message_ () { } test_known_broken_ok_ () { - test_subtest_known_broken_= + test_reset_state_ test_fixed=$(($test_fixed+1)) say_color pass "%-6s" "FIXED" echo " $@" } test_known_broken_failure_ () { - test_subtest_known_broken_= + test_reset_state_ test_broken=$(($test_broken+1)) test_failure_message_ "BROKEN" "$@" return 1 @@ -622,18 +663,32 @@ test_skip () { fi case "$to_skip" in t) - test_subtest_known_broken_= - say_color skip >&3 "skipping test: $@" - say_color skip "%-6s" "SKIP" - echo " $1" - : true + test_report_skip_ "$@" ;; *) - false + test_check_missing_external_prereqs_ "$@" ;; esac } +test_check_missing_external_prereqs_ () { + if test -n "$test_subtest_missing_external_prereqs_"; then + say_color skip >&3 "missing prerequisites:" + echo "$test_subtest_missing_external_prereqs_" >&3 + test_report_skip_ "$@" + else + false + fi +} + +test_report_skip_ () { + test_reset_state_ + say_color skip >&3 "skipping test:" + echo " $@" >&3 + say_color skip "%-6s" "SKIP" + echo " $1" +} + test_subtest_known_broken () { test_subtest_known_broken_=t } @@ -642,10 +697,14 @@ test_expect_success () { test "$#" = 3 && { prereq=$1; shift; } || prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test-expect-success" + test_reset_state_ if ! test_skip "$@" then test_run_ "$2" - if [ "$?" = 0 -a "$eval_ret" = 0 ] + run_ret="$?" + # test_run_ may update missing external prerequisites + test_check_missing_external_prereqs_ "$@" || + if [ "$run_ret" = 0 -a "$eval_ret" = 0 ] then test_ok_ "$1" else @@ -658,10 +717,14 @@ test_expect_code () { test "$#" = 4 && { prereq=$1; shift; } || prereq= test "$#" = 3 || error "bug in the test script: not 3 or 4 parameters to test-expect-code" + test_reset_state_ if ! test_skip "$@" then test_run_ "$3" - if [ "$?" = 0 -a "$eval_ret" = "$1" ] + run_ret="$?" + # test_run_ may update missing external prerequisites, + test_check_missing_external_prereqs_ "$@" || + if [ "$run_ret" = 0 -a "$eval_ret" = "$1" ] then test_ok_ "$2" else @@ -684,6 +747,7 @@ test_external () { error >&5 "bug in the test script: not 3 or 4 parameters to test_external" descr="$1" shift + test_reset_state_ if ! test_skip "$descr" "$@" then # Announce the script to reduce confusion about the @@ -842,17 +906,22 @@ EOF } test_emacs () { + # test dependencies beforehand to avoid the waiting loop below + test_require_external_prereq emacs || return + test_require_external_prereq emacsclient || return + if [ -z "$EMACS_SERVER" ]; then - EMACS_SERVER="notmuch-test-suite-$$" + server_name="notmuch-test-suite-$$" # start a detached session with an emacs server # user's TERM is given to dtach which assumes a minimally # VT100-compatible terminal -- and emacs inherits that TERM=$ORIGINAL_TERM dtach -n "$TEST_TMPDIR/emacs-dtach-socket.$$" \ sh -c "stty rows 24 cols 80; exec '$TMP_DIRECTORY/run_emacs' \ --no-window-system \ - --eval '(setq server-name \"$EMACS_SERVER\")' \ + --eval '(setq server-name \"$server_name\")' \ --eval '(server-start)' \ --eval '(orphan-watchdog $$)'" || return + EMACS_SERVER="$server_name" # wait until the emacs server is up until test_emacs '()' 2>/dev/null; do sleep 1 @@ -862,6 +931,67 @@ test_emacs () { emacsclient --socket-name="$EMACS_SERVER" --eval "(progn $@)" } +test_python() { + export LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib + export PYTHONPATH=$TEST_DIRECTORY/../bindings/python + + # Some distros (e.g. Arch Linux) ship Python 2.* as /usr/bin/python2, + # most others as /usr/bin/python. So first try python2, and fallback to + # python if python2 doesn't exist. + cmd=python2 + [[ "$test_missing_external_prereq_python2_" = t ]] && cmd=python + + (echo "import sys; _orig_stdout=sys.stdout; sys.stdout=open('OUTPUT', 'w')"; cat) \ + | $cmd - +} + +# Creates a script that counts how much time it is executed and calls +# notmuch. $notmuch_counter_command is set to the path to the +# generated script. Use notmuch_counter_value() function to get the +# current counter value. +notmuch_counter_reset () { + notmuch_counter_command="$TMP_DIRECTORY/notmuch_counter" + if [ ! -x "$notmuch_counter_command" ]; then + notmuch_counter_state_path="$TMP_DIRECTORY/notmuch_counter.state" + cat >"$notmuch_counter_command" <<EOF || return +#!/bin/sh + +read count < "$notmuch_counter_state_path" +echo \$((count + 1)) > "$notmuch_counter_state_path" + +exec notmuch "\$@" +EOF + chmod +x "$notmuch_counter_command" || return + fi + + echo 0 > "$notmuch_counter_state_path" +} + +# Returns the current notmuch counter value. +notmuch_counter_value () { + if [ -r "$notmuch_counter_state_path" ]; then + read count < "$notmuch_counter_state_path" + else + count=0 + fi + echo $count +} + +test_reset_state_ () { + test -z "$test_init_done_" && test_init_ + + test_subtest_known_broken_= + test_subtest_missing_external_prereqs_= +} + +# called once before the first subtest +test_init_ () { + test_init_done_=t + + # skip all tests if there were external prerequisites missing during init + test_check_missing_external_prereqs_ "all tests in $this_test" && test_done +} + find_notmuch_path () { @@ -1069,3 +1199,12 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS rm -f y + +# declare prerequisites for external binaries used in tests +test_declare_external_prereq dtach +test_declare_external_prereq emacs +test_declare_external_prereq emacsclient +test_declare_external_prereq gdb +test_declare_external_prereq gpg +test_declare_external_prereq python +test_declare_external_prereq python2 diff --git a/util/Makefile.local b/util/Makefile.local index 2ff42b3d..26e4c3f3 100644 --- a/util/Makefile.local +++ b/util/Makefile.local @@ -9,3 +9,5 @@ libutil_modules := $(libutil_c_srcs:.c=.o) $(dir)/libutil.a: $(libutil_modules) $(call quiet,AR) rcs $@ $^ + +CLEAN := $(CLEAN) $(libutil_modules) $(dir)/libutil.a @@ -1 +1 @@ -0.10.2 +0.11 |
