1 /* message.cc - Results of message-based searches from a notmuch database
3 * Copyright © 2009 Carl Worth
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.
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.
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/ .
18 * Author: Carl Worth <cworth@cworth.org>
21 #include "notmuch-private.h"
22 #include "database-private.h"
26 struct _notmuch_message {
27 notmuch_database_t *notmuch;
34 struct _notmuch_tags {
35 Xapian::TermIterator iterator;
36 Xapian::TermIterator iterator_end;
39 struct _notmuch_thread_ids {
44 /* "128 bits of thread-id ought to be enough for anybody" */
45 #define NOTMUCH_THREAD_ID_BITS 128
46 #define NOTMUCH_THREAD_ID_DIGITS (NOTMUCH_THREAD_ID_BITS / 4)
47 typedef struct _thread_id {
48 char str[NOTMUCH_THREAD_ID_DIGITS + 1];
51 /* We end up having to call the destructor explicitly because we had
52 * to use "placement new" in order to initialize C++ objects within a
53 * block that we allocated with talloc. So C++ is making talloc
54 * slightly less simple to use, (we wouldn't need
55 * talloc_set_destructor at all otherwise).
58 _notmuch_message_destructor (notmuch_message_t *message)
60 message->doc.~Document ();
65 /* Create a new notmuch_message_t object for an existing document in
68 * Here, 'talloc owner' is an optional talloc context to which the new
69 * message will belong. This allows for the caller to not bother
70 * calling notmuch_message_destroy on the message, and no that all
71 * memory will be reclaimed with 'talloc_owner' is free. The caller
72 * still can call notmuch_message_destroy when finished with the
75 * The 'talloc_owner' argument can also be NULL, in which case the
76 * caller *is* responsible for calling notmuch_message_destroy.
78 * If no document exists in the database with document ID of 'doc_id'
79 * then this function returns NULL.
82 _notmuch_message_create (const void *talloc_owner,
83 notmuch_database_t *notmuch,
86 notmuch_message_t *message;
88 message = talloc (talloc_owner, notmuch_message_t);
89 if (unlikely (message == NULL))
92 message->notmuch = notmuch;
93 message->doc_id = doc_id;
94 message->message_id = NULL; /* lazily created */
95 message->filename = NULL; /* lazily created */
96 new (&message->doc) Xapian::Document;
98 talloc_set_destructor (message, _notmuch_message_destructor);
101 message->doc = notmuch->xapian_db->get_document (doc_id);
102 } catch (const Xapian::DocNotFoundError &error) {
103 talloc_free (message);
110 /* Create a new notmuch_message_t object for a specific message ID,
111 * (which may or may not already exist in the databas).
113 * Here, 'talloc owner' is an optional talloc context to which the new
114 * message will belong. This allows for the caller to not bother
115 * calling notmuch_message_destroy on the message, and no that all
116 * memory will be reclaimed with 'talloc_owner' is free. The caller
117 * still can call notmuch_message_destroy when finished with the
118 * message if desired.
120 * The 'talloc_owner' argument can also be NULL, in which case the
121 * caller *is* responsible for calling notmuch_message_destroy.
123 * If there is already a document with message ID 'message_id' in the
124 * database, then the returned message can be used to query/modify the
125 * document. Otherwise, a new document will be inserted into the
126 * database before this function returns;
129 _notmuch_message_create_for_message_id (const void *talloc_owner,
130 notmuch_database_t *notmuch,
131 const char *message_id)
133 notmuch_message_t *message;
134 Xapian::Document doc;
138 message = notmuch_database_find_message (notmuch, message_id);
140 return talloc_steal (talloc_owner, message);
142 term = talloc_asprintf (NULL, "%s%s",
143 _find_prefix ("id"), message_id);
147 doc.add_value (NOTMUCH_VALUE_MESSAGE_ID, message_id);
149 doc_id = notmuch->xapian_db->add_document (doc);
151 return _notmuch_message_create (talloc_owner, notmuch, doc_id);
155 notmuch_message_get_message_id (notmuch_message_t *message)
157 Xapian::TermIterator i;
159 if (message->message_id)
160 return message->message_id;
162 i = message->doc.termlist_begin ();
163 i.skip_to (_find_prefix ("id"));
165 if (i == message->doc.termlist_end ()) {
166 fprintf (stderr, "Internal error: Message with document ID of %d has no message ID.\n",
171 message->message_id = talloc_strdup (message, (*i).c_str () + 1);
172 return message->message_id;
175 /* Set the filename for 'message' to 'filename'.
177 * XXX: We should still figure out what we want to do for multiple
178 * files with identical message IDs. We will probably want to store a
179 * list of filenames here, (so that this will be "add_filename"
180 * instead of "set_filename"). Which would make this very similar to
183 * This change will not be reflected in the database until the next
184 * call to _notmuch_message_set_sync. */
186 _notmuch_message_set_filename (notmuch_message_t *message,
187 const char *filename)
189 if (message->filename)
190 talloc_free (message->filename);
191 message->doc.set_data (filename);
195 notmuch_message_get_filename (notmuch_message_t *message)
197 std::string filename_str;
199 if (message->filename)
200 return message->filename;
202 filename_str = message->doc.get_data ();
203 message->filename = talloc_strdup (message, filename_str.c_str ());
205 return message->filename;
208 /* We end up having to call the destructors explicitly because we had
209 * to use "placement new" in order to initialize C++ objects within a
210 * block that we allocated with talloc. So C++ is making talloc
211 * slightly less simple to use, (we wouldn't need
212 * talloc_set_destructor at all otherwise).
215 _notmuch_tags_destructor (notmuch_tags_t *tags)
217 tags->iterator.~TermIterator ();
218 tags->iterator_end.~TermIterator ();
224 notmuch_message_get_tags (notmuch_message_t *message)
226 notmuch_tags_t *tags;
228 tags = talloc (message, notmuch_tags_t);
229 if (unlikely (tags == NULL))
232 new (&tags->iterator) Xapian::TermIterator;
233 new (&tags->iterator_end) Xapian::TermIterator;
235 talloc_set_destructor (tags, _notmuch_tags_destructor);
237 tags->iterator = message->doc.termlist_begin ();
238 tags->iterator.skip_to (_find_prefix ("tag"));
239 tags->iterator_end = message->doc.termlist_end ();
244 notmuch_thread_ids_t *
245 notmuch_message_get_thread_ids (notmuch_message_t *message)
247 notmuch_thread_ids_t *thread_ids;
250 thread_ids = talloc (message, notmuch_thread_ids_t);
251 if (unlikely (thread_ids == NULL))
254 id_str = message->doc.get_value (NOTMUCH_VALUE_THREAD);
255 thread_ids->next = talloc_strdup (message, id_str.c_str ());
257 /* Initialize thread_ids->current and terminate first ID. */
258 notmuch_thread_ids_advance (thread_ids);
264 _notmuch_message_set_date (notmuch_message_t *message,
269 time_value = notmuch_parse_date (date, NULL);
271 message->doc.add_value (NOTMUCH_VALUE_DATE,
272 Xapian::sortable_serialise (time_value));
276 _notmuch_message_add_thread_id (notmuch_message_t *message,
277 const char *thread_id)
281 _notmuch_message_add_term (message, "thread", thread_id);
283 id_str = message->doc.get_value (NOTMUCH_VALUE_THREAD);
285 if (id_str.empty ()) {
286 message->doc.add_value (NOTMUCH_VALUE_THREAD, thread_id);
290 /* Think about using a hash here if there's any performance
292 pos = id_str.find (thread_id);
293 if (pos == std::string::npos) {
295 id_str.append (thread_id);
296 message->doc.add_value (NOTMUCH_VALUE_THREAD, id_str);
302 thread_id_generate (thread_id_t *thread_id)
304 static int seeded = 0;
311 dev_random = fopen ("/dev/random", "r");
312 if (dev_random == NULL) {
315 fread ((void *) &value, sizeof (value), 1, dev_random);
323 for (i = 0; i < NOTMUCH_THREAD_ID_DIGITS; i += 8) {
325 sprintf (s, "%08x", value);
331 _notmuch_message_ensure_thread_id (notmuch_message_t *message)
333 /* If not part of any existing thread, generate a new thread_id. */
334 thread_id_t thread_id;
336 thread_id_generate (&thread_id);
337 _notmuch_message_add_term (message, "thread", thread_id.str);
338 message->doc.add_value (NOTMUCH_VALUE_THREAD, thread_id.str);
341 /* Synchronize changes made to message->doc out into the database. */
343 _notmuch_message_sync (notmuch_message_t *message)
345 Xapian::WritableDatabase *db = message->notmuch->xapian_db;
347 db->replace_document (message->doc_id, message->doc);
350 /* Add a name:value term to 'message', (the actual term will be
351 * encoded by prefixing the value with a short prefix). See
352 * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term
353 * names to prefix values.
355 * This change will not be reflected in the database until the next
356 * call to _notmuch_message_set_sync. */
357 notmuch_private_status_t
358 _notmuch_message_add_term (notmuch_message_t *message,
359 const char *prefix_name,
366 return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
368 term = talloc_asprintf (message, "%s%s",
369 _find_prefix (prefix_name), value);
371 if (strlen (term) > NOTMUCH_TERM_MAX)
372 return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
374 message->doc.add_term (term);
378 return NOTMUCH_PRIVATE_STATUS_SUCCESS;
381 /* Remove a name:value term from 'message', (the actual term will be
382 * encoded by prefixing the value with a short prefix). See
383 * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term
384 * names to prefix values.
386 * This change will not be reflected in the database until the next
387 * call to _notmuch_message_set_sync. */
388 notmuch_private_status_t
389 _notmuch_message_remove_term (notmuch_message_t *message,
390 const char *prefix_name,
396 return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
398 term = talloc_asprintf (message, "%s%s",
399 _find_prefix (prefix_name), value);
401 if (strlen (term) > NOTMUCH_TERM_MAX)
402 return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
404 message->doc.remove_term (term);
408 return NOTMUCH_PRIVATE_STATUS_SUCCESS;
412 notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
414 notmuch_private_status_t status;
417 return NOTMUCH_STATUS_NULL_POINTER;
419 if (strlen (tag) > NOTMUCH_TAG_MAX)
420 return NOTMUCH_STATUS_TAG_TOO_LONG;
422 status = _notmuch_message_add_term (message, "tag", tag);
424 fprintf (stderr, "Internal error: _notmuch_message_add_term return unexpected value: %d\n",
429 _notmuch_message_sync (message);
431 return NOTMUCH_STATUS_SUCCESS;
435 notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
437 notmuch_private_status_t status;
440 return NOTMUCH_STATUS_NULL_POINTER;
442 if (strlen (tag) > NOTMUCH_TAG_MAX)
443 return NOTMUCH_STATUS_TAG_TOO_LONG;
445 status = _notmuch_message_remove_term (message, "tag", tag);
447 fprintf (stderr, "Internal error: _notmuch_message_remove_term return unexpected value: %d\n",
452 _notmuch_message_sync (message);
454 return NOTMUCH_STATUS_SUCCESS;
458 notmuch_message_destroy (notmuch_message_t *message)
460 talloc_free (message);
464 notmuch_tags_has_more (notmuch_tags_t *tags)
468 if (tags->iterator == tags->iterator_end)
472 if (! s.empty () && s[0] == 'L')
479 notmuch_tags_get (notmuch_tags_t *tags)
481 return talloc_strdup (tags, (*tags->iterator).c_str () + 1);
485 notmuch_tags_advance (notmuch_tags_t *tags)
491 notmuch_tags_destroy (notmuch_tags_t *tags)
497 notmuch_thread_ids_has_more (notmuch_thread_ids_t *thread_ids)
499 if (thread_ids->current == NULL || *thread_ids->current == '\0')
506 notmuch_thread_ids_get (notmuch_thread_ids_t *thread_ids)
508 return thread_ids->current;
512 notmuch_thread_ids_advance (notmuch_thread_ids_t *thread_ids)
514 thread_ids->current = strsep (&thread_ids->next, ",");
518 notmuch_thread_ids_destroy (notmuch_thread_ids_t *thread_ids)
520 talloc_free (thread_ids);