X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=database.cc;h=71246eb456115ade91ee9b47b89629850ab32e11;hp=25489e3f5d8bde7151164a0e6f592621fbb5395d;hb=df959868d9fc4f78682da3363d15685d69d2353b;hpb=ec77f6b50cb460dc8c376eeee045a25ed390c337 diff --git a/database.cc b/database.cc index 25489e3f..71246eb4 100644 --- a/database.cc +++ b/database.cc @@ -24,7 +24,7 @@ #include -#include /* g_strdup_printf, g_free, GPtrArray, GHashTable */ +#include /* g_free, GPtrArray, GHashTable */ using namespace std; @@ -51,15 +51,18 @@ typedef struct { * id: Unique ID of mail, (from Message-ID header or generated * as "notmuch-sha1-. * + * thread: The ID of the thread to which the mail belongs + * * Multiple terms of given prefix: * - * ref: The message IDs from all In-Reply-To and References - * headers in the message. + * ref: All unresolved message IDs from In-Reply-To and + * References headers in the message. (Once a referenced + * message is added to the database and the thread IDs + * are linked the corresponding "ref" term is dropped + * from the message document.) * * tag: Any tags associated with this message by the user. * - * thread: The thread ID of all threads to which the mail belongs - * * A mail document also has two values: * * TIMESTAMP: The time_t value corresponding to the message's @@ -101,16 +104,30 @@ typedef struct { prefix_t BOOLEAN_PREFIX_INTERNAL[] = { { "type", "T" }, - { "thread", "G" }, { "ref", "XREFERENCE" }, { "timestamp", "XTIMESTAMP" }, }; prefix_t BOOLEAN_PREFIX_EXTERNAL[] = { + { "thread", "G" }, { "tag", "K" }, { "id", "Q" } }; +int +_internal_error (const char *format, ...) +{ + va_list va_args; + + va_start (va_args, format); + + vfprintf (stderr, format, va_args); + + exit (1); + + return 1; +} + const char * _find_prefix (const char *name) { @@ -135,6 +152,8 @@ notmuch_status_to_string (notmuch_status_t status) switch (status) { case NOTMUCH_STATUS_SUCCESS: return "No error occurred"; + case NOTMUCH_STATUS_OUT_OF_MEMORY: + return "Out of memory"; case NOTMUCH_STATUS_XAPIAN_EXCEPTION: return "A Xapian exception occurred"; case NOTMUCH_STATUS_FILE_ERROR: @@ -147,35 +166,14 @@ notmuch_status_to_string (notmuch_status_t status) return "Erroneous NULL pointer"; case NOTMUCH_STATUS_TAG_TOO_LONG: return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)"; + case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: + return "Unblanced number of calls to notmuch_message_freeze/thaw"; default: case NOTMUCH_STATUS_LAST_STATUS: return "Unknown error status value"; } } -/* XXX: We should drop this function and convert all callers to call - * _notmuch_message_add_term instead. */ -static void -add_term (Xapian::Document doc, - const char *prefix_name, - const char *value) -{ - const char *prefix; - char *term; - - if (value == NULL) - return; - - prefix = _find_prefix (prefix_name); - - term = g_strdup_printf ("%s%s", prefix, value); - - if (strlen (term) <= NOTMUCH_TERM_MAX) - doc.add_term (term); - - g_free (term); -} - static void find_doc_ids (notmuch_database_t *notmuch, const char *prefix_name, @@ -186,13 +184,14 @@ find_doc_ids (notmuch_database_t *notmuch, Xapian::PostingIterator i; char *term; - term = g_strdup_printf ("%s%s", _find_prefix (prefix_name), value); + term = talloc_asprintf (notmuch, "%s%s", + _find_prefix (prefix_name), value); *begin = notmuch->xapian_db->postlist_begin (term); *end = notmuch->xapian_db->postlist_end (term); - free (term); + talloc_free (term); } static notmuch_private_status_t @@ -240,37 +239,6 @@ find_unique_document (notmuch_database_t *notmuch, return NOTMUCH_PRIVATE_STATUS_SUCCESS; } -/* XXX: Should rewrite this to accept a notmuch_message_t* instead of - * a Xapian:Document and then we could just use - * notmuch_message_get_thread_ids instead of duplicating its logic - * here. */ -static void -insert_thread_id (GHashTable *thread_ids, Xapian::Document doc) -{ - string value_string; - Xapian::TermIterator i; - const char *prefix_str = _find_prefix ("thread"); - char prefix; - - assert (strlen (prefix_str) == 1); - - prefix = *prefix_str; - - i = doc.termlist_begin (); - i.skip_to (prefix_str); - - while (1) { - if (i == doc.termlist_end ()) - break; - value_string = *i; - if (value_string.empty () || value_string[0] != prefix) - break; - g_hash_table_insert (thread_ids, - strdup (value_string.c_str () + 1), NULL); - i++; - } -} - notmuch_message_t * notmuch_database_find_message (notmuch_database_t *notmuch, const char *message_id) @@ -286,75 +254,6 @@ notmuch_database_find_message (notmuch_database_t *notmuch, return _notmuch_message_create (notmuch, notmuch, doc_id, NULL); } -/* Return one or more thread_ids, (as a GPtrArray of strings), for the - * given message based on looking into the database for any messages - * referenced in parents, and also for any messages in the database - * referencing message_id. - * - * Caller should free all strings in the array and the array itself, - * (g_ptr_array_free) when done. */ -static GPtrArray * -find_thread_ids (notmuch_database_t *notmuch, - GPtrArray *parents, - const char *message_id) -{ - Xapian::PostingIterator child, children_end; - Xapian::Document doc; - GHashTable *thread_ids; - GList *keys, *l; - unsigned int i; - const char *parent_message_id; - GPtrArray *result; - - thread_ids = g_hash_table_new_full (g_str_hash, g_str_equal, - free, NULL); - - find_doc_ids (notmuch, "ref", message_id, &child, &children_end); - for ( ; child != children_end; child++) { - doc = find_document_for_doc_id (notmuch, *child); - insert_thread_id (thread_ids, doc); - } - - for (i = 0; i < parents->len; i++) { - notmuch_message_t *parent; - notmuch_thread_ids_t *ids; - - parent_message_id = (char *) g_ptr_array_index (parents, i); - parent = notmuch_database_find_message (notmuch, parent_message_id); - if (parent == NULL) - continue; - - for (ids = notmuch_message_get_thread_ids (parent); - notmuch_thread_ids_has_more (ids); - notmuch_thread_ids_advance (ids)) - { - const char *id; - - id = notmuch_thread_ids_get (ids); - g_hash_table_insert (thread_ids, strdup (id), NULL); - } - - notmuch_message_destroy (parent); - } - - result = g_ptr_array_new (); - - keys = g_hash_table_get_keys (thread_ids); - for (l = keys; l; l = l->next) { - char *id = (char *) l->data; - g_ptr_array_add (result, id); - } - g_list_free (keys); - - /* We're done with the hash table, but we've taken the pointers to - * the allocated strings and put them into our result array, so - * tell the hash not to free them on its way out. */ - g_hash_table_steal_all (thread_ids); - g_hash_table_unref (thread_ids); - - return result; -} - /* Advance 'str' past any whitespace or RFC 822 comments. A comment is * a (potentially nested) parenthesized sequence with '\' used to * escape any character (including parentheses). @@ -397,12 +296,11 @@ skip_space_and_comments (const char **str) * If not NULL, then *next will be made to point to the first character * not parsed, (possibly pointing to the final '\0' terminator. * - * Returns a newly allocated string which the caller should free() - * when done with it. + * Returns a newly talloc'ed string belonging to 'ctx'. * * Returns NULL if there is any error parsing the message-id. */ static char * -parse_message_id (const char *message_id, const char **next) +parse_message_id (void *ctx, const char *message_id, const char **next) { const char *s, *end; char *result; @@ -443,7 +341,7 @@ parse_message_id (const char *message_id, const char **next) if (end <= s) return NULL; - result = strndup (s, end - s + 1); + result = talloc_strndup (ctx, s, end - s + 1); /* Finally, collapse any whitespace that is within the message-id * itself. */ @@ -459,10 +357,11 @@ parse_message_id (const char *message_id, const char **next) return result; } -/* Parse a References header value, putting a copy of each referenced - * message-id into 'array'. */ +/* Parse a References header value, putting a (talloc'ed under 'ctx') + * copy of each referenced message-id into 'hash'. */ static void -parse_references (GPtrArray *array, +parse_references (void *ctx, + GHashTable *hash, const char *refs) { char *ref; @@ -471,20 +370,27 @@ parse_references (GPtrArray *array, return; while (*refs) { - ref = parse_message_id (refs, &refs); + ref = parse_message_id (ctx, refs, &refs); if (ref) - g_ptr_array_add (array, ref); + g_hash_table_insert (hash, ref, NULL); } } char * notmuch_database_default_path (void) { + char *path; + if (getenv ("NOTMUCH_BASE")) return strdup (getenv ("NOTMUCH_BASE")); - return g_strdup_printf ("%s/mail", getenv ("HOME")); + if (asprintf (&path, "%s/mail", getenv ("HOME")) == -1) { + fprintf (stderr, "Out of memory.\n"); + return xstrdup(""); + } + + return path; } notmuch_database_t * @@ -512,7 +418,7 @@ notmuch_database_create (const char *path) goto DONE; } - notmuch_path = g_strdup_printf ("%s/%s", path, ".notmuch"); + notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch"); err = mkdir (notmuch_path, 0755); @@ -526,7 +432,7 @@ notmuch_database_create (const char *path) DONE: if (notmuch_path) - free (notmuch_path); + talloc_free (notmuch_path); if (local_path) free (local_path); @@ -546,7 +452,11 @@ notmuch_database_open (const char *path) if (path == NULL) path = local_path = notmuch_database_default_path (); - notmuch_path = g_strdup_printf ("%s/%s", path, ".notmuch"); + if (asprintf (¬much_path, "%s/%s", path, ".notmuch") == -1) { + notmuch_path = NULL; + fprintf (stderr, "Out of memory\n"); + goto DONE; + } err = stat (notmuch_path, &st); if (err) { @@ -555,7 +465,11 @@ notmuch_database_open (const char *path) goto DONE; } - xapian_path = g_strdup_printf ("%s/%s", notmuch_path, "xapian"); + if (asprintf (&xapian_path, "%s/%s", notmuch_path, "xapian") == -1) { + xapian_path = NULL; + fprintf (stderr, "Out of memory\n"); + goto DONE; + } notmuch = talloc (NULL, notmuch_database_t); notmuch->path = talloc_strdup (notmuch, path); @@ -575,6 +489,7 @@ notmuch_database_open (const char *path) } catch (const Xapian::Error &error) { fprintf (stderr, "A Xapian exception occurred: %s\n", error.get_msg().c_str()); + notmuch = NULL; } DONE: @@ -591,6 +506,8 @@ notmuch_database_open (const char *path) void notmuch_database_close (notmuch_database_t *notmuch) { + notmuch->xapian_db->flush (); + delete notmuch->query_parser; delete notmuch->xapian_db; talloc_free (notmuch); @@ -602,7 +519,7 @@ notmuch_database_get_path (notmuch_database_t *notmuch) return notmuch->path; } -notmuch_private_status_t +static notmuch_private_status_t find_timestamp_document (notmuch_database_t *notmuch, const char *db_key, Xapian::Document *doc, unsigned int *doc_id) { @@ -699,6 +616,177 @@ notmuch_database_get_timestamp (notmuch_database_t *notmuch, const char *key) return ret; } +/* Find the thread ID to which the message with 'message_id' belongs. + * + * Returns NULL if no message with message ID 'message_id' is in the + * database. + * + * Otherwise, returns a newly talloced string belonging to 'ctx'. + */ +static const char * +_resolve_message_id_to_thread_id (notmuch_database_t *notmuch, + void *ctx, + const char *message_id) +{ + notmuch_message_t *message; + const char *ret = NULL; + + message = notmuch_database_find_message (notmuch, message_id); + if (message == NULL) + goto DONE; + + ret = talloc_steal (ctx, notmuch_message_get_thread_id (message)); + + DONE: + if (message) + notmuch_message_destroy (message); + + return ret; +} + +static notmuch_status_t +_merge_threads (notmuch_database_t *notmuch, + const char *winner_thread_id, + const char *loser_thread_id) +{ + Xapian::PostingIterator loser, loser_end; + notmuch_message_t *message = NULL; + notmuch_private_status_t private_status; + notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; + + find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end); + + for ( ; loser != loser_end; loser++) { + message = _notmuch_message_create (notmuch, notmuch, + *loser, &private_status); + if (message == NULL) { + ret = COERCE_STATUS (private_status, + "Cannot find document for doc_id from query"); + goto DONE; + } + + _notmuch_message_remove_term (message, "thread", loser_thread_id); + _notmuch_message_add_term (message, "thread", winner_thread_id); + _notmuch_message_sync (message); + + notmuch_message_destroy (message); + message = NULL; + } + + DONE: + if (message) + notmuch_message_destroy (message); + + return ret; +} + +static void +_my_talloc_free_for_g_hash (void *ptr) +{ + talloc_free (ptr); +} + +static notmuch_status_t +_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch, + notmuch_message_t *message, + notmuch_message_file_t *message_file, + const char **thread_id) +{ + GHashTable *parents = NULL; + const char *refs, *in_reply_to; + GList *l, *keys = NULL; + notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; + + parents = g_hash_table_new_full (g_str_hash, g_str_equal, + _my_talloc_free_for_g_hash, NULL); + + refs = notmuch_message_file_get_header (message_file, "references"); + parse_references (message, parents, refs); + + in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to"); + parse_references (message, parents, in_reply_to); + + keys = g_hash_table_get_keys (parents); + for (l = keys; l; l = l->next) { + char *parent_message_id; + const char *parent_thread_id; + + parent_message_id = (char *) l->data; + parent_thread_id = _resolve_message_id_to_thread_id (notmuch, + message, + parent_message_id); + + if (parent_thread_id == NULL) { + _notmuch_message_add_term (message, "ref", parent_message_id); + } else { + if (*thread_id == NULL) { + *thread_id = talloc_strdup (message, parent_thread_id); + _notmuch_message_add_term (message, "thread", *thread_id); + } else if (strcmp (*thread_id, parent_thread_id)) { + ret = _merge_threads (notmuch, *thread_id, parent_thread_id); + if (ret) + goto DONE; + } + } + } + + DONE: + if (keys) + g_list_free (keys); + if (parents) + g_hash_table_unref (parents); + + return ret; +} + +static notmuch_status_t +_notmuch_database_link_message_to_children (notmuch_database_t *notmuch, + notmuch_message_t *message, + const char **thread_id) +{ + const char *message_id = notmuch_message_get_message_id (message); + Xapian::PostingIterator child, children_end; + notmuch_message_t *child_message = NULL; + const char *child_thread_id; + notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; + notmuch_private_status_t private_status; + + find_doc_ids (notmuch, "ref", message_id, &child, &children_end); + + for ( ; child != children_end; child++) { + + child_message = _notmuch_message_create (message, notmuch, + *child, &private_status); + if (child_message == NULL) { + ret = COERCE_STATUS (private_status, + "Cannot find document for doc_id from query"); + goto DONE; + } + + child_thread_id = notmuch_message_get_thread_id (child_message); + if (*thread_id == NULL) { + *thread_id = talloc_strdup (message, child_thread_id); + _notmuch_message_add_term (message, "thread", *thread_id); + } else if (strcmp (*thread_id, child_thread_id)) { + _notmuch_message_remove_term (child_message, "ref", + message_id); + _notmuch_message_sync (child_message); + ret = _merge_threads (notmuch, *thread_id, child_thread_id); + if (ret) + goto DONE; + } + + notmuch_message_destroy (child_message); + child_message = NULL; + } + + DONE: + if (child_message) + notmuch_message_destroy (child_message); + + return ret; +} + /* Given a (mostly empty) 'message' and its corresponding * 'message_file' link it to existing threads in the database. * @@ -716,49 +804,30 @@ _notmuch_database_link_message (notmuch_database_t *notmuch, notmuch_message_t *message, notmuch_message_file_t *message_file) { - GPtrArray *parents, *thread_ids; - const char *refs, *in_reply_to; - const char *message_id = notmuch_message_get_message_id (message); - unsigned int i; - - parents = g_ptr_array_new (); - - refs = notmuch_message_file_get_header (message_file, "references"); - parse_references (parents, refs); - - in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to"); - parse_references (parents, in_reply_to); + notmuch_status_t status; + const char *thread_id = NULL; - for (i = 0; i < parents->len; i++) - _notmuch_message_add_term (message, "ref", - (char *) g_ptr_array_index (parents, i)); - - thread_ids = find_thread_ids (notmuch, parents, message_id); - - for (i = 0; i < parents->len; i++) - g_free (g_ptr_array_index (parents, i)); - g_ptr_array_free (parents, TRUE); + status = _notmuch_database_link_message_to_parents (notmuch, message, + message_file, + &thread_id); + if (status) + return status; - if (thread_ids->len) { - char *id; + status = _notmuch_database_link_message_to_children (notmuch, message, + &thread_id); + if (status) + return status; - for (i = 0; i < thread_ids->len; i++) { - id = (char *) thread_ids->pdata[i]; - _notmuch_message_add_thread_id (message, id); - free (id); - } - } else { + if (thread_id == NULL) _notmuch_message_ensure_thread_id (message); - } - - g_ptr_array_free (thread_ids, TRUE); return NOTMUCH_STATUS_SUCCESS; } notmuch_status_t notmuch_database_add_message (notmuch_database_t *notmuch, - const char *filename) + const char *filename, + notmuch_message_t **message_ret) { notmuch_message_file_t *message_file; notmuch_message_t *message; @@ -768,6 +837,9 @@ notmuch_database_add_message (notmuch_database_t *notmuch, const char *from, *to, *subject, *old_filename; char *message_id; + if (message_ret) + *message_ret = NULL; + message_file = notmuch_message_file_open (filename); if (message_file == NULL) { ret = NOTMUCH_STATUS_FILE_ERROR; @@ -789,11 +861,11 @@ notmuch_database_add_message (notmuch_database_t *notmuch, header = notmuch_message_file_get_header (message_file, "message-id"); if (header) { - message_id = parse_message_id (header, NULL); + message_id = parse_message_id (message_file, header, NULL); /* So the header value isn't RFC-compliant, but it's * better than no message-id at all. */ if (message_id == NULL) - message_id = xstrdup (header); + message_id = talloc_strdup (message_file, header); } else { /* No message-id at all, let's generate one by taking a * hash over the file's contents. */ @@ -805,7 +877,8 @@ notmuch_database_add_message (notmuch_database_t *notmuch, goto DONE; } - message_id = g_strdup_printf ("notmuch-sha1-%s", sha1); + message_id = talloc_asprintf (message_file, + "notmuch-sha1-%s", sha1); free (sha1); } @@ -818,7 +891,8 @@ notmuch_database_add_message (notmuch_database_t *notmuch, notmuch, message_id, &ret); - free (message_id); + + talloc_free (message_id); if (message == NULL) goto DONE; @@ -861,8 +935,13 @@ notmuch_database_add_message (notmuch_database_t *notmuch, } DONE: - if (message) - notmuch_message_destroy (message); + if (message) { + if (ret == NOTMUCH_STATUS_SUCCESS && message_ret) + *message_ret = message; + else + notmuch_message_destroy (message); + } + if (message_file) notmuch_message_file_close (message_file);