From 1ba3d46fab12d616b2085f5794543444cc4bc750 Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Sun, 25 Oct 2009 23:12:20 -0700 Subject: [PATCH] Add an initial implementation of a notmuch_thread_t object. We've now got a new notmuch_query_search_threads and a notmuch_threads_result_t iterator. The thread object itself doesn't do much yet, (just allows one to get the thread_id), but that's at least enough to see that "notmuch search" is actually doing something now, (since it has been converted to print thread IDs instead of message IDs). And maybe that's all we need. Getting the messages belonging to a thread is as simple as a notmuch_query_search_messages with a string of "thread:". Though it would be convenient to add notmuch_thread_get_messages which could use the existing notmuch_message_results_t iterator. Now we just need an implementation of "notmuch show" and we'll have something somewhat usable. --- Makefile | 1 + notmuch-private.h | 7 +++ notmuch.c | 32 ++++---------- notmuch.h | 107 ++++++++++++++++++++++++++++++++++++++++++++-- query.cc | 87 +++++++++++++++++++++++++++++++++++++ thread.cc | 73 +++++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 27 deletions(-) create mode 100644 thread.cc 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); +}