Add notmuch_message_get_thread_ids function
[notmuch] / message.cc
1 /* message.cc - Results of message-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 struct _notmuch_message {
27     Xapian::Document doc;
28 };
29
30 struct _notmuch_tags {
31     Xapian::TermIterator iterator;
32     Xapian::TermIterator iterator_end;
33 };
34
35 struct _notmuch_thread_ids {
36     char *current;
37     char *next;
38 };
39
40 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
41
42 /* These prefix values are specifically chosen to be compatible
43  * with sup, (http://sup.rubyforge.org), written by
44  * William Morgan <wmorgan-sup@masanjin.net>, and released
45  * under the GNU GPL v2.
46  */
47
48 typedef struct {
49     const char *name;
50     const char *prefix;
51 } prefix_t;
52
53 prefix_t NORMAL_PREFIX[] = {
54     { "subject", "S" },
55     { "body", "B" },
56     { "from_name", "FN" },
57     { "to_name", "TN" },
58     { "name", "N" },
59     { "attachment", "A" }
60 };
61
62 prefix_t BOOLEAN_PREFIX[] = {
63     { "type", "K" },
64     { "from_email", "FE" },
65     { "to_email", "TE" },
66     { "email", "E" },
67     { "date", "D" },
68     { "label", "L" },
69     { "source_id", "I" },
70     { "attachment_extension", "O" },
71     { "msgid", "Q" },
72     { "thread", "H" },
73     { "ref", "R" }
74 };
75
76 const char *
77 _find_prefix (const char *name)
78 {
79     unsigned int i;
80
81     for (i = 0; i < ARRAY_SIZE (NORMAL_PREFIX); i++)
82         if (strcmp (name, NORMAL_PREFIX[i].name) == 0)
83             return NORMAL_PREFIX[i].prefix;
84
85     for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX); i++)
86         if (strcmp (name, BOOLEAN_PREFIX[i].name) == 0)
87             return BOOLEAN_PREFIX[i].prefix;
88
89     return "";
90 }
91
92 /* We end up having to call the destructor explicitly because we had
93  * to use "placement new" in order to initialize C++ objects within a
94  * block that we allocated with talloc. So C++ is making talloc
95  * slightly less simple to use, (we wouldn't need
96  * talloc_set_destructor at all otherwise).
97  */
98 static int
99 _notmuch_message_destructor (notmuch_message_t *message)
100 {
101     message->doc.~Document ();
102
103     return 0;
104 }
105
106 notmuch_message_t *
107 _notmuch_message_create (notmuch_results_t *owner,
108                          notmuch_database_t *notmuch,
109                          Xapian::docid doc_id)
110 {
111     notmuch_message_t *message;
112
113     message = talloc (owner, notmuch_message_t);
114     if (unlikely (message == NULL))
115         return NULL;
116
117     new (&message->doc) Xapian::Document;
118
119     talloc_set_destructor (message, _notmuch_message_destructor);
120
121     message->doc = notmuch->xapian_db->get_document (doc_id);
122
123     return message;
124 }
125
126 const char *
127 notmuch_message_get_message_id (notmuch_message_t *message)
128 {
129     Xapian::TermIterator i;
130
131     i = message->doc.termlist_begin ();
132     i.skip_to ("Q");
133     if (i != message->doc.termlist_end ())
134         return talloc_strdup (message, (*i).c_str () + 1);
135     else
136         return NULL;
137 }
138
139 /* We end up having to call the destructors explicitly because we had
140  * to use "placement new" in order to initialize C++ objects within a
141  * block that we allocated with talloc. So C++ is making talloc
142  * slightly less simple to use, (we wouldn't need
143  * talloc_set_destructor at all otherwise).
144  */
145 static int
146 _notmuch_tags_destructor (notmuch_tags_t *tags)
147 {
148     tags->iterator.~TermIterator ();
149     tags->iterator_end.~TermIterator ();
150
151     return 0;
152 }
153
154 notmuch_tags_t *
155 notmuch_message_get_tags (notmuch_message_t *message)
156 {
157     notmuch_tags_t *tags;
158
159     tags = talloc (message, notmuch_tags_t);
160     if (unlikely (tags == NULL))
161         return NULL;
162
163     new (&tags->iterator) Xapian::TermIterator;
164     new (&tags->iterator_end) Xapian::TermIterator;
165
166     talloc_set_destructor (tags, _notmuch_tags_destructor);
167
168     tags->iterator = message->doc.termlist_begin ();
169     tags->iterator.skip_to ("L");
170     tags->iterator_end = message->doc.termlist_end ();
171
172     return tags;
173 }
174
175 notmuch_thread_ids_t *
176 notmuch_message_get_thread_ids (notmuch_message_t *message)
177 {
178     notmuch_thread_ids_t *thread_ids;
179     const char *id_str;
180
181     thread_ids = talloc (message, notmuch_thread_ids_t);
182     if (unlikely (thread_ids == NULL))
183         return NULL;
184
185     id_str = message->doc.get_value (NOTMUCH_VALUE_THREAD).c_str ();
186     thread_ids->next = talloc_strdup (message, id_str);
187
188     /* Initialize thread_ids->current and terminate first ID. */
189     notmuch_thread_ids_advance (thread_ids);
190
191     return thread_ids;
192 }
193
194 void
195 notmuch_message_destroy (notmuch_message_t *message)
196 {
197     talloc_free (message);
198 }
199
200 notmuch_bool_t
201 notmuch_tags_has_more (notmuch_tags_t *tags)
202 {
203     std::string s;
204
205     if (tags->iterator == tags->iterator_end)
206         return FALSE;
207
208     s = *tags->iterator;
209     if (s.size () && s[0] == 'L')
210         return TRUE;
211     else
212         return FALSE;
213 }
214
215 const char *
216 notmuch_tags_get (notmuch_tags_t *tags)
217 {
218     return talloc_strdup (tags, (*tags->iterator).c_str () + 1);
219 }
220
221 void
222 notmuch_tags_advance (notmuch_tags_t *tags)
223 {
224     tags->iterator++;
225 }
226
227 void
228 notmuch_tags_destroy (notmuch_tags_t *tags)
229 {
230     talloc_free (tags);
231 }
232
233 notmuch_bool_t
234 notmuch_thread_ids_has_more (notmuch_thread_ids_t *thread_ids)
235 {
236     if (thread_ids->current == NULL || *thread_ids->current == '\0')
237         return FALSE;
238     else
239         return TRUE;
240 }
241
242 const char *
243 notmuch_thread_ids_get (notmuch_thread_ids_t *thread_ids)
244 {
245     return thread_ids->current;
246 }
247
248 void
249 notmuch_thread_ids_advance (notmuch_thread_ids_t *thread_ids)
250 {
251     thread_ids->current = strsep (&thread_ids->next, ",");
252 }
253
254 void
255 notmuch_thread_ids_destroy (notmuch_thread_ids_t *thread_ids)
256 {
257     talloc_free (thread_ids);
258 }