From: David Bremner Date: Tue, 6 Dec 2011 23:39:33 +0000 (-0400) Subject: Merge branch 'release' X-Git-Tag: 0.11_rc1~58 X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=commitdiff_plain;h=f0e0053149bb3b51f4a0cd43371292b639f236a8;hp=07bb8b9e895541006eca88430925f1c6524c4708 Merge branch 'release' Conflicts: NEWS Conflicts resolved by inserting the 0.10.2 stanza before 0.11 --- diff --git a/Makefile.local b/Makefile.local index d97fa618..15e6d882 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." @@ -179,7 +187,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 +207,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 diff --git a/NEWS b/NEWS index 3f577e42..bb5e4d55 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,16 @@ +Notmuch 0.11 (201x-xx-xx) +========================= + +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. + + Notmuch 0.10.2 (2011-12-04) =========================== diff --git a/RELEASING b/RELEASING index e3e0cefe..88dab04e 100644 --- a/RELEASING +++ b/RELEASING @@ -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 ' """ 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 "" % 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 . Copyright 2010 Sebastian Spaeth ' """ 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 . Copyright 2010 Sebastian Spaeth ' """ -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 ' """ -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 . Copyright 2010 Sebastian Spaeth ' """ 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 . Copyright 2010 Sebastian Spaeth ' """ -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() - - ###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 "thread:%s %12s [%d/%d] %s; %s (%s)" % (thread['id'], - thread['date'], - thread['matched'], - thread['total'], - thread['authors'], - thread['subject'], - thread['tags']) + return unicode(self).encode('utf-8') + + def __unicode__(self): + frm = "thread:%s %12s [%d/%d] %s; %s (%s)" + + 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(), + ) + + _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/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. - - - Copyright (C) - - 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. - - , 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. 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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 +/* 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 */ #ifdef HAVE_CONFIG_H 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/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 1a76c30a..0582cae7 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.") @@ -168,8 +188,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))) @@ -440,6 +460,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) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index d5c95d80..d7fbbca9 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -91,6 +91,16 @@ any given message." :group 'notmuch :type 'boolean) +(defcustom notmuch-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-indent-messages-width depth)) (notmuch-show-clean-address (plist-get headers :From)) " (" date @@ -739,7 +749,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-indent-messages-width depth)) (setq message-end (point-max-marker)) @@ -843,6 +853,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) @@ -1135,26 +1147,18 @@ 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 @@ -1173,8 +1177,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..89361498 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -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) 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/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-dump.c b/notmuch-dump.c index 126593d1..a490917f 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -67,13 +67,16 @@ notmuch_dump_command (unused (void *ctx), int argc, char *argv[]) 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-tag.c b/notmuch-tag.c index dded39ea..537d5a4d 100644 --- a/notmuch-tag.c +++ b/notmuch-tag.c @@ -30,6 +30,81 @@ handle_sigint (unused (int sig)) 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; diff --git a/test/.gitignore b/test/.gitignore index 9e97052d..7e30e8d5 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,4 +1,5 @@ test-results corpus.mail smtp-dummy +symbol-test tmp.* diff --git a/test/Makefile.local b/test/Makefile.local index 8eb04330..bffbbdbd 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -11,10 +11,16 @@ smtp_dummy_modules = $(smtp_dummy_srcs:.c=.o) $(dir)/smtp-dummy: $(smtp_dummy_modules) $(call quiet,CC) $^ -o $@ +$(dir)/symbol-test: $(dir)/symbol-test.o + $(call quiet,CC) $^ -o $@ -Llib -lnotmuch -lxapian + .PHONY: test check -test: all $(dir)/smtp-dummy + +test-binaries: $(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 diff --git a/test/README b/test/README index 2481f16d..2e757e0e 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: diff --git a/test/basic b/test/basic index 38db2baf..4edf8315 100755 --- a/test/basic +++ b/test/basic @@ -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)$/d" | \ + sort) test_expect_equal "$tests_in_suite" "$available" EXPECTED=$TEST_DIRECTORY/test.expected-output diff --git a/test/emacs b/test/emacs index 75a0a744..3f8c72d3 100755 --- a/test/emacs +++ b/test/emacs @@ -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-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-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-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\\\" \"" @@ -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 <EXPECTED +Sat, 01 Jan 2000 12:00:00 -0000 +Some One +Some One Else +Notmuch +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,7 +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_expect_equal "$(cat OUTPUT)" "thread:XXX" test_begin_subtest 'Hiding message following HTML part' test_subtest_known_broken 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 -Some One Else -Notmuch -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 (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 +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 (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 (2009-11-17) (inbox signed unread) + Subject: Re: [notmuch] Working with Maildir storage? + To: Mikhail Gusarov + 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 + 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 (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: + + Keith Packard (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 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 (2009-11-18) (inbox signed unread) + Subject: Re: [notmuch] Working with Maildir storage? + To: Keith Packard + 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 " (for the uint32_t on line 466). + + [ 5-line signature. Click/Enter to show. ] + -- + Lars Kellogg-Stedman + 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 (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 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 (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 +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 (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 (2009-11-17) (inbox signed unread) +Subject: Re: [notmuch] Working with Maildir storage? +To: Mikhail Gusarov +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 +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 (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: + +Keith Packard (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 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 (2009-11-18) (inbox signed unread) +Subject: Re: [notmuch] Working with Maildir storage? +To: Keith Packard +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 " (for the uint32_t on line 466). + +[ 5-line signature. Click/Enter to show. ] +-- +Lars Kellogg-Stedman +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 (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 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/notmuch-test b/test/notmuch-test index 113ea7cf..53ce355c 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -62,10 +62,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/raw b/test/raw index b7e265a8..99d3a3bf 100755 --- a/test/raw +++ b/test/raw @@ -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..f67b653e 100755 --- a/test/symbol-hiding +++ b/test/symbol-hiding @@ -12,13 +12,12 @@ 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' 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 #include #include -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.sh b/test/test-lib.sh old mode 100755 new mode 100644 index cf309f94..a9759570 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -398,6 +398,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 +429,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 +548,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 +608,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 +651,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 +685,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 +705,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 +735,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 +894,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 +919,21 @@ test_emacs () { emacsclient --socket-name="$EMACS_SERVER" --eval "(progn $@)" } +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 +1141,10 @@ 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 diff --git a/util/Makefile.local b/util/Makefile.local index 2ff42b3d..03408995 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) $(dir)/xutil.o $(dir)/error_util.o $(dir)/libutil.a