4f0696b4fc01b8ee13b3fc1af49e3ac03b0bdcb1
[notmuch] / lib / thread.cc
1 /* thread.cc - Results of thread-based searches from a notmuch database
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #include "notmuch-private.h"
22 #include "database-private.h"
23
24 #include <xapian.h>
25
26 #include <gmime/gmime.h>
27 #include <glib.h> /* GHashTable */
28
29 struct _notmuch_thread {
30     notmuch_database_t *notmuch;
31     char *thread_id;
32     char *subject;
33     GHashTable *authors_hash;
34     char *authors;
35     GHashTable *tags;
36
37     notmuch_bool_t has_message;
38     time_t oldest;
39     time_t newest;
40 };
41
42 static int
43 _notmuch_thread_destructor (notmuch_thread_t *thread)
44 {
45     g_hash_table_unref (thread->authors_hash);
46     g_hash_table_unref (thread->tags);
47
48     return 0;
49 }
50
51 static void
52 _thread_add_author (notmuch_thread_t *thread,
53                     const char *author)
54 {
55     if (author == NULL)
56         return;
57
58     if (g_hash_table_lookup_extended (thread->authors_hash,
59                                       author, NULL, NULL))
60         return;
61
62     g_hash_table_insert (thread->authors_hash, xstrdup (author), NULL);
63
64     if (thread->authors)
65         thread->authors = talloc_asprintf (thread, "%s, %s",
66                                            thread->authors,
67                                            author);
68     else
69         thread->authors = talloc_strdup (thread, author);
70 }
71
72 static void
73 _thread_add_message (notmuch_thread_t *thread,
74                      notmuch_message_t *message)
75 {
76     notmuch_tags_t *tags;
77     const char *tag;
78     time_t date;
79     InternetAddressList *list;
80     InternetAddress *address;
81     const char *from, *author;
82
83     from = notmuch_message_get_header (message, "from");
84     list = internet_address_list_parse_string (from);
85     if (list) {
86         address = internet_address_list_get_address (list, 0);
87         if (address) {
88             author = internet_address_get_name (address);
89             if (author == NULL) {
90                 InternetAddressMailbox *mailbox;
91                 mailbox = INTERNET_ADDRESS_MAILBOX (address);
92                 author = internet_address_mailbox_get_addr (mailbox);
93             }
94             _thread_add_author (thread, author);
95         }
96         g_object_unref (G_OBJECT (list));
97     }
98
99     if (! thread->subject) {
100         const char *subject;
101         subject = notmuch_message_get_header (message, "subject");
102         thread->subject = talloc_strdup (thread, subject);
103     }
104
105     for (tags = notmuch_message_get_tags (message);
106          notmuch_tags_has_more (tags);
107          notmuch_tags_advance (tags))
108     {
109         tag = notmuch_tags_get (tags);
110         g_hash_table_insert (thread->tags, xstrdup (tag), NULL);
111     }
112
113     date = notmuch_message_get_date (message);
114
115     if (date < thread->oldest || ! thread->has_message)
116         thread->oldest = date;
117
118     if (date > thread->newest || ! thread->has_message)
119         thread->newest = date;
120
121     thread->has_message = 1;
122 }
123
124 /* Create a new notmuch_thread_t object for the given thread ID.
125  *
126  * Creating the thread will trigger a database search for the messages
127  * belonging to the thread so that the thread object can return some
128  * details about them, (authors, subject, etc.).
129  *
130  * Here, 'talloc owner' is an optional talloc context to which the new
131  * thread will belong. This allows for the caller to not bother
132  * calling notmuch_thread_destroy on the thread, and know that all
133  * memory will be reclaimed with 'talloc_owner' is freed. The caller
134  * still can call notmuch_thread_destroy when finished with the
135  * thread if desired.
136  *
137  * The 'talloc_owner' argument can also be NULL, in which case the
138  * caller *is* responsible for calling notmuch_thread_destroy.
139  *
140  * This function returns NULL in the case of any error.
141  */
142 notmuch_thread_t *
143 _notmuch_thread_create (const void *ctx,
144                         notmuch_database_t *notmuch,
145                         const char *thread_id)
146 {
147     notmuch_thread_t *thread;
148     const char *query_string;
149     notmuch_query_t *query;
150     notmuch_messages_t *messages;
151
152     query_string = talloc_asprintf (ctx, "thread:%s", thread_id);
153     if (unlikely (query_string == NULL))
154         return NULL;
155
156     query = notmuch_query_create (notmuch, query_string);
157     if (unlikely (query == NULL))
158         return NULL;
159
160     thread = talloc (ctx, notmuch_thread_t);
161     if (unlikely (thread == NULL))
162         return NULL;
163
164     talloc_set_destructor (thread, _notmuch_thread_destructor);
165
166     thread->notmuch = notmuch;
167     thread->thread_id = talloc_strdup (thread, thread_id);
168     thread->subject = NULL;
169     thread->authors_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
170                                                   free, NULL);
171     thread->authors = NULL;
172     thread->tags = g_hash_table_new_full (g_str_hash, g_str_equal,
173                                           free, NULL);
174
175     thread->has_message = 0;
176     thread->oldest = 0;
177     thread->newest = 0;
178
179     notmuch_query_set_sort (query, NOTMUCH_SORT_DATE_OLDEST_FIRST);
180
181     for (messages = notmuch_query_search_messages (query, 0, -1);
182          notmuch_messages_has_more (messages);
183          notmuch_messages_advance (messages))
184     {
185         _thread_add_message (thread, notmuch_messages_get (messages));
186     }
187
188     notmuch_query_destroy (query);
189
190     return thread;
191 }
192
193 const char *
194 notmuch_thread_get_thread_id (notmuch_thread_t *thread)
195 {
196     return thread->thread_id;
197 }
198
199 const char *
200 notmuch_thread_get_authors (notmuch_thread_t *thread)
201 {
202     return thread->authors;
203 }
204
205 const char *
206 notmuch_thread_get_subject (notmuch_thread_t *thread)
207 {
208     return thread->subject;
209 }
210
211 time_t
212 notmuch_thread_get_oldest_date (notmuch_thread_t *thread)
213 {
214     return thread->oldest;
215 }
216
217 time_t
218 notmuch_thread_get_newest_date (notmuch_thread_t *thread)
219 {
220     return thread->newest;
221 }
222
223 notmuch_tags_t *
224 notmuch_thread_get_tags (notmuch_thread_t *thread)
225 {
226     notmuch_tags_t *tags;
227     GList *keys, *l;
228
229     tags = _notmuch_tags_create (thread);
230     if (unlikely (tags == NULL))
231         return NULL;
232
233     keys = g_hash_table_get_keys (thread->tags);
234
235     for (l = keys; l; l = l->next)
236         _notmuch_tags_add_tag (tags, (char *) l->data);
237
238     g_list_free (keys);
239
240     _notmuch_tags_prepare_iterator (tags);
241
242     return tags;
243 }
244
245 void
246 notmuch_thread_destroy (notmuch_thread_t *thread)
247 {
248     talloc_free (thread);
249 }