diff --git a/Makefile b/Makefile index 6af7faf5..747408a6 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ MODULES= \ message-file.o \ query.o \ sha1.o \ + thread.o \ libsha1.o \ xutil.o diff --git a/notmuch-private.h b/notmuch-private.h index aac67b2e..d257f2b9 100644 --- a/notmuch-private.h +++ b/notmuch-private.h @@ -137,6 +137,13 @@ typedef enum _notmuch_private_status { : \ (notmuch_status_t) private_status) +/* thread.cc */ + +notmuch_thread_t * +_notmuch_thread_create (const void *talloc_owner, + notmuch_database_t *notmuch, + const char *thread_id); + /* message.cc */ notmuch_message_t * diff --git a/notmuch.c b/notmuch.c index 47969e1f..5ab155e5 100644 --- a/notmuch.c +++ b/notmuch.c @@ -608,9 +608,8 @@ search_command (int argc, char *argv[]) void *local = talloc_new (NULL); notmuch_database_t *notmuch = NULL; notmuch_query_t *query; - notmuch_message_results_t *results; - notmuch_message_t *message; - notmuch_tags_t *tags; + notmuch_thread_results_t *results; + notmuch_thread_t *thread; char *query_str; int i; notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; @@ -638,30 +637,15 @@ search_command (int argc, char *argv[]) goto DONE; } - for (results = notmuch_query_search_messages (query); - notmuch_message_results_has_more (results); - notmuch_message_results_advance (results)) + for (results = notmuch_query_search_threads (query); + notmuch_thread_results_has_more (results); + notmuch_thread_results_advance (results)) { - int first = 1; - message = notmuch_message_results_get (results); + thread = notmuch_thread_results_get (results); - printf ("%s (", notmuch_message_get_message_id (message)); + printf ("%s\n", notmuch_thread_get_thread_id (thread)); - for (tags = notmuch_message_get_tags (message); - notmuch_tags_has_more (tags); - notmuch_tags_advance (tags)) - { - if (! first) - printf (" "); - - printf ("%s", notmuch_tags_get (tags)); - - first = 0; - } - - printf (")\n"); - - notmuch_message_destroy (message); + notmuch_thread_destroy (thread); } notmuch_query_destroy (query); diff --git a/notmuch.h b/notmuch.h index d383e7d8..522bf1b9 100644 --- a/notmuch.h +++ b/notmuch.h @@ -102,6 +102,8 @@ notmuch_status_to_string (notmuch_status_t status); * notmuch_ functions below. */ typedef struct _notmuch_database notmuch_database_t; typedef struct _notmuch_query notmuch_query_t; +typedef struct _notmuch_thread_results notmuch_thread_results_t; +typedef struct _notmuch_thread notmuch_thread_t; typedef struct _notmuch_message_results notmuch_message_results_t; typedef struct _notmuch_message notmuch_message_t; typedef struct _notmuch_tags notmuch_tags_t; @@ -313,6 +315,45 @@ typedef enum { void notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort); +/* Execute a query for threads, returning a notmuch_thread_results_t + * object which can be used to iterate over the results. The results + * object is owned by the query and as such, will only be valid until + * notmuch_query_destroy. + * + * Typical usage might be: + * + * notmuch_query_t *query; + * notmuch_thread_results_t *results; + * notmuch_thread_t *thread; + * + * query = notmuch_query_create (database, query_string); + * + * for (results = notmuch_query_search_threads (query); + * notmuch_thread_results_has_more (results); + * notmuch_thread_results_advance (results)) + * { + * thread = notmuch_thread_results_get (results); + * .... + * notmuch_thread_destroy (thread); + * } + * + * notmuch_query_destroy (query); + * + * Note: If you are finished with a thread before its containing + * query, you can call notmuch_thread_destroy to clean up some memory + * sooner (as in the above example). Otherwise, if your thread objects + * are long-lived, then you don't need to call notmuch_thread_destroy + * and all the memory will still be reclaimed when the query is + * destroyed. + * + * Note that there's no explicit destructor needed for the + * notmuch_thread_results_t object. (For consistency, we do provide a + * notmuch_thread_results_destroy function, but there's no good reason + * to call it if the query is about to be destroyed). + */ +notmuch_thread_results_t * +notmuch_query_search_threads (notmuch_query_t *query); + /* Execute a query for messages, returning a notmuch_message_results_t * object which can be used to iterate over the results. The results * object is owned by the query and as such, will only be valid until @@ -354,13 +395,73 @@ notmuch_query_search_messages (notmuch_query_t *query); /* Destroy a notmuch_query_t along with any associated resources. * - * This will in turn destroy any notmuch_results_t objects generated - * by this query, (and in turn any notmuch_message_t objects generated - * from those results, etc.). + * This will in turn destroy any notmuch_thread_results_t and + * notmuch_message_results_t objects generated by this query, (and in + * turn any notmuch_thrad_t and notmuch_message_t objects generated + * from those results, etc.), if such objects haven't already been + * destroyed. */ void notmuch_query_destroy (notmuch_query_t *query); +/* Does the given notmuch_thread_results_t object contain any more + * results. + * + * When this function returns TRUE, notmuch_thread_results_get will + * return a valid object. Whereas when this function returns FALSE, + * notmuch_thread_results_get will return NULL. + * + * See the documentation of notmuch_query_search_threads for example + * code showing how to iterate over a notmuch_thread_results_t object. + */ +notmuch_bool_t +notmuch_thread_results_has_more (notmuch_thread_results_t *results); + +/* Get the current result from 'results' as a notmuch_thread_t. + * + * Note: The returned thread belongs to 'results' and has a lifetime + * identical to it (and the query to which it belongs). + * + * See the documentation of notmuch_query_search_threads for example + * code showing how to iterate over a notmuch_thread_results_t object. + * + * If an out-of-memory situation occurs, this function will return + * NULL. + */ +notmuch_thread_t * +notmuch_thread_results_get (notmuch_thread_results_t *results); + +/* Advance the 'results' iterator to the next result. + * + * See the documentation of notmuch_query_search_threads for example + * code showing how to iterate over a notmuch_thread_results_t object. + */ +void +notmuch_thread_results_advance (notmuch_thread_results_t *results); + +/* Destroy a notmuch_thread_results_t object. + * + * It's not strictly necessary to call this function. All memory from + * the notmuch_thread_results_t object will be reclaimed when the + * containg query object is destroyed. + */ +void +notmuch_thread_results_destroy (notmuch_thread_results_t *results); + +/* Get the thread ID of 'thread'. + * + * The returned string belongs to 'thread' and as such, should not be + * modified by the caller and will only be valid for as long as the + * thread is valid, (which is until notmuch_thread_destroy or until + * the query from which it derived is destroyed). + */ +const char * +notmuch_thread_get_thread_id (notmuch_thread_t *thread); + +/* Destroy a notmuch_thread_t object. */ +void +notmuch_thread_destroy (notmuch_thread_t *thread); + /* Does the given notmuch_message_results_t object contain any more * results. * diff --git a/query.cc b/query.cc index c153dad9..b454560a 100644 --- a/query.cc +++ b/query.cc @@ -21,6 +21,8 @@ #include "notmuch-private.h" #include "database-private.h" +#include /* GHashTable, GPtrArray */ + #include struct _notmuch_query { @@ -35,6 +37,12 @@ struct _notmuch_message_results { Xapian::MSetIterator iterator_end; }; +struct _notmuch_thread_results { + notmuch_database_t *notmuch; + GPtrArray *thread_ids; + unsigned int index; +}; + notmuch_query_t * notmuch_query_create (notmuch_database_t *notmuch, const char *query_string) @@ -150,6 +158,50 @@ notmuch_query_search_messages (notmuch_query_t *query) return results; } +notmuch_thread_results_t * +notmuch_query_search_threads (notmuch_query_t *query) +{ + notmuch_thread_results_t *thread_results; + notmuch_message_results_t *message_results; + notmuch_message_t *message; + const char *thread_id; + GHashTable *seen; + + thread_results = talloc (query, notmuch_thread_results_t); + if (thread_results == NULL) + return NULL; + + thread_results->notmuch = query->notmuch; + thread_results->thread_ids = g_ptr_array_new (); + thread_results->index = 0; + + seen = g_hash_table_new_full (g_str_hash, g_str_equal, + free, NULL); + + for (message_results = notmuch_query_search_messages (query); + notmuch_message_results_has_more (message_results); + notmuch_message_results_advance (message_results)) + { + message = notmuch_message_results_get (message_results); + thread_id = notmuch_message_get_thread_id (message); + + if (g_hash_table_lookup_extended (seen, + thread_id, NULL, NULL)) + { + continue; + } + + g_hash_table_insert (seen, xstrdup (thread_id), NULL); + + g_ptr_array_add (thread_results->thread_ids, + talloc_strdup (thread_results, thread_id)); + } + + g_hash_table_unref (seen); + + return thread_results; +} + void notmuch_query_destroy (notmuch_query_t *query) { @@ -195,3 +247,38 @@ notmuch_message_results_destroy (notmuch_message_results_t *results) { talloc_free (results); } + +notmuch_bool_t +notmuch_thread_results_has_more (notmuch_thread_results_t *results) +{ + return (results->index < results->thread_ids->len); +} + +notmuch_thread_t * +notmuch_thread_results_get (notmuch_thread_results_t *results) +{ + notmuch_thread_t *thread; + const char *thread_id; + + thread_id = (const char *) g_ptr_array_index (results->thread_ids, + results->index); + + thread = _notmuch_thread_create (results, + results->notmuch, + thread_id); + + return thread; +} + +void +notmuch_thread_results_advance (notmuch_thread_results_t *results) +{ + results->index++; +} + +void +notmuch_thread_results_destroy (notmuch_thread_results_t *results) +{ + g_ptr_array_free (results->thread_ids, TRUE); + talloc_free (results); +} diff --git a/thread.cc b/thread.cc new file mode 100644 index 00000000..89157299 --- /dev/null +++ b/thread.cc @@ -0,0 +1,73 @@ +/* thread.cc - Results of thread-based searches from a notmuch database + * + * Copyright © 2009 Carl Worth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/ . + * + * Author: Carl Worth + */ + +#include "notmuch-private.h" +#include "database-private.h" + +#include + +struct _notmuch_thread { + notmuch_database_t *notmuch; + char *thread_id; +}; + +/* Create a new notmuch_thread_t object for an existing document in + * the database. + * + * Here, 'talloc owner' is an optional talloc context to which the new + * thread will belong. This allows for the caller to not bother + * calling notmuch_thread_destroy on the thread, and know that all + * memory will be reclaimed with 'talloc_owner' is freed. The caller + * still can call notmuch_thread_destroy when finished with the + * thread if desired. + * + * The 'talloc_owner' argument can also be NULL, in which case the + * caller *is* responsible for calling notmuch_thread_destroy. + * + * This function returns NULL in the case of any error. + */ +notmuch_thread_t * +_notmuch_thread_create (const void *talloc_owner, + notmuch_database_t *notmuch, + const char *thread_id) +{ + notmuch_thread_t *thread; + + thread = talloc (talloc_owner, notmuch_thread_t); + if (unlikely (thread == NULL)) + return NULL; + + thread->notmuch = notmuch; + thread->thread_id = talloc_strdup (thread, thread_id); + + return thread; +} + +const char * +notmuch_thread_get_thread_id (notmuch_thread_t *thread) +{ + return thread->thread_id; +} + +void +notmuch_thread_destroy (notmuch_thread_t *thread) +{ + talloc_free (thread); +}