X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=notmuch-index-message.cc;h=1922dd373856a32efb8c0f6f0ac4c857554da4ca;hp=a0faf442dbf9ef1c04910046e3560c90df50f7a8;hb=be72bf30703c5df814d4f572c3d9132d39700b36;hpb=c0da89a8e02190f172e6f53073827bbdf91d60eb diff --git a/notmuch-index-message.cc b/notmuch-index-message.cc index a0faf442..1922dd37 100644 --- a/notmuch-index-message.cc +++ b/notmuch-index-message.cc @@ -33,6 +33,9 @@ using namespace std; #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0])) +/* Xapian complains if we provide a term longer than this. */ +#define NOTMUCH_MAX_TERM 245 + /* These prefix values are specifically chosen to be compatible * with sup, (http://sup.rubyforge.org), written by * William Morgan , and released @@ -109,7 +112,8 @@ add_term (Xapian::Document doc, term = g_strdup_printf ("%s%s", prefix, value); - doc.add_term (term); + if (strlen (term) <= NOTMUCH_MAX_TERM) + doc.add_term (term); g_free (term); } @@ -204,122 +208,207 @@ skip_re_in_subject (const char *subject) return s; } -/* Add a term for each message-id in the References header of the - * message. */ static void -add_terms_references (Xapian::Document doc, - GMimeMessage *message) +find_messages_by_term (Xapian::Database db, + const char *prefix_name, + const char *value, + Xapian::PostingIterator *begin, + Xapian::PostingIterator *end) { - const char *refs, *end, *next; + Xapian::PostingIterator i; char *term; - refs = g_mime_object_get_header (GMIME_OBJECT (message), "references"); + term = g_strdup_printf ("%s%s", find_prefix (prefix_name), value); - if (refs == NULL) - return; + *begin = db.postlist_begin (term); + + if (end) + *end = db.postlist_end (term); + + free (term); +} + +Xapian::Document +find_message_by_docid (Xapian::Database db, Xapian::docid docid) +{ + return db.get_document (docid); +} + +Xapian::Document +find_message_by_message_id (Xapian::Database db, const char *message_id) +{ + Xapian::PostingIterator i, end; + + find_messages_by_term (db, "msgid", message_id, &i, &end); + + if (i != end) + return find_message_by_docid (db, *i); + else + return Xapian::Document (); +} + +static void +insert_thread_id (GHashTable *thread_ids, Xapian::Document doc) +{ + string value_string; + const char *value; + + value_string = doc.get_value (NOTMUCH_VALUE_THREAD); + value = value_string.c_str(); + if (strlen (value)) + g_hash_table_insert (thread_ids, strdup (value), NULL); +} - while (*refs) { - while (*refs && isspace (*refs)) - refs++; - if (*refs == '<') - refs++; - end = refs; - while (*end && !isspace (*end)) - end++; - next = end; - end--; - if (end > refs && *end == '>') - end--; - if (end > refs) { - term = g_strndup (refs, end - refs + 1); - add_term (doc, "ref", term); - g_free (term); +static char * +find_thread_id (Xapian::Database db, + GPtrArray *parents, + const char *message_id) +{ + Xapian::PostingIterator child, children_end; + Xapian::Document doc; + GHashTable *thread_ids; + GList *keys, *l; + GString *result = NULL; + unsigned int i; + const char *parent_message_id; + + thread_ids = g_hash_table_new (g_str_hash, g_str_equal); + + find_messages_by_term (db, "ref", message_id, &child, &children_end); + for ( ; child != children_end; child++) { + doc = find_message_by_docid (db, *child); + insert_thread_id (thread_ids, doc); + } + + for (i = 0; i < parents->len; i++) { + parent_message_id = (char *) g_ptr_array_index (parents, i); + doc = find_message_by_message_id (db, parent_message_id); + insert_thread_id (thread_ids, doc); + } + + keys = g_hash_table_get_keys (thread_ids); + for (l = keys; l; l = l->next) { + char *id = (char *) l->data; + if (result == NULL) { + result = g_string_new (id); + } else { + g_string_append_printf (result, ",%s", id); } - refs = next; + free (id); } + + if (result) + return g_string_free (result, FALSE); + else + return NULL; } -/* Generate terms for the body of a message, given the filename of the - * message and the offset at which the headers of the message end, - * (and hence the body begins). */ +/* Add a term for each message-id in the References header of the + * message. */ static void -gen_terms_body (Xapian::TermGenerator term_gen, - const char * filename, - gint64 body_offset) +parse_references (GPtrArray *array, + const char *refs_str) { - GIOChannel *channel; - GIOStatus gio_status; - GError *error = NULL; - char *p, *body_line = NULL, *prev_line = NULL; + GMimeReferences *refs, *r; + const char *message_id; - channel = g_io_channel_new_file (filename, "r", &error); - if (channel == NULL) { - fprintf (stderr, "Error: %s\n", error->message); - exit (1); - } + if (refs_str == NULL) + return; - gio_status = g_io_channel_seek_position (channel, body_offset, - G_SEEK_SET, &error); - if (gio_status != G_IO_STATUS_NORMAL) { - fprintf (stderr, "Error: %s\n", error->message); - exit (1); + refs = g_mime_references_decode (refs_str); + + for (r = refs; r; r = r->next) { + message_id = g_mime_references_get_message_id (r); + g_ptr_array_add (array, g_strdup (message_id)); } - while (1) { - if (body_line) - g_free (body_line); + g_mime_references_free (refs); +} + +/* Given a string representing the body of a message, generate terms + * for it, (skipping quoted portions and signatures). */ +static void +gen_terms_body_str (Xapian::TermGenerator term_gen, + char *body) +{ + char *line, *line_end, *next_line; - gio_status = g_io_channel_read_line (channel, &body_line, - NULL, NULL, &error); - if (gio_status == G_IO_STATUS_EOF) + if (body == NULL) + return; + + next_line = body; + + while (1) { + line = next_line; + if (*line == '\0') break; - if (gio_status != G_IO_STATUS_NORMAL) { - fprintf (stderr, "Error: %s\n", error->message); - exit (1); + + next_line = strchr (line, '\n'); + if (next_line == NULL) { + next_line = line + strlen (line); } + line_end = next_line - 1; - if (strlen (body_line) == 0) - continue; + /* Trim whitespace. */ + while (*next_line && isspace (*next_line)) + next_line++; - /* If the line looks like it might be introducing a quote, - * save it until we see if the next line begins a quote. */ - p = body_line + strlen (body_line) - 1; - while (p > body_line and isspace (*p)) - p--; - if (*p == ':') { - prev_line = body_line; - body_line = NULL; + /* Skip lines that are quotes. */ + if (*line == '>') continue; - } - /* Skip quoted lines, (and previous lines that introduced them) */ - if (body_line[0] == '>') { - if (prev_line) { - g_free (prev_line); - prev_line = NULL; - } - continue; - } + /* Also skip lines introducing a quote on the next line. */ + while (line_end > line && isspace (*line_end)) + line_end--; - /* Now that we're not looking at a quote we can add the prev_line */ - if (prev_line) { - gen_terms (term_gen, "body", prev_line); - g_free (prev_line); - prev_line = NULL; - } + if (*line_end == ':' && *next_line == '>') + continue; - /* Skip signatures */ + /* Finally, bail as soon as we see a signature. */ /* XXX: Should only do this if "near" the end of the message. */ - if (strncmp (body_line, "-- ", 3) == 0) + if (strncmp (line, "-- ", 3) == 0 || + strncmp (line, "----------", 10) == 0 || + strncmp (line, "__________", 10) == 0) break; - gen_terms (term_gen, "body", body_line); + *(line_end + 1) = '\0'; + gen_terms (term_gen, "body", line); } +} - if (body_line) - g_free (body_line); - g_io_channel_close (channel); +/* Callback to generate terms for each mime part of a message. */ +static void +gen_terms_part (GMimeObject *parent, + GMimeObject *part, + gpointer user_data) +{ + Xapian::TermGenerator *term_gen = (Xapian::TermGenerator *) user_data; + GMimeStream *stream; + GMimeDataWrapper *wrapper; + GByteArray *byte_array; + char *body; + + if (! GMIME_IS_PART (part)) { + fprintf (stderr, "Warning: Not indexing unknown mime part: %s.\n", + g_type_name (G_OBJECT_TYPE (part))); + return; + } + + byte_array = g_byte_array_new (); + + stream = g_mime_stream_mem_new_with_byte_array (byte_array); + g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (stream), FALSE); + wrapper = g_mime_part_get_content_object (GMIME_PART (part)); + g_mime_data_wrapper_write_to_stream (wrapper, stream); + + g_object_unref (stream); + + body = (char *) g_byte_array_free (byte_array, FALSE); + + gen_terms_body_str (*term_gen, body); + + free (body); } static void @@ -333,14 +422,17 @@ index_file (Xapian::WritableDatabase db, GMimeParser *parser; GMimeMessage *message; InternetAddressList *addresses; + GPtrArray *parents; FILE *file; - const char *value, *from; + const char *subject, *refs, *in_reply_to, *from; + const char *message_id, *thread_id; time_t time; struct tm gm_time_tm; char date_str[16]; /* YYYYMMDDHHMMSS + 1 for Y100k compatibility ;-) */ + unsigned int i; file = fopen (filename, "r"); if (! file) { @@ -370,15 +462,32 @@ index_file (Xapian::WritableDatabase db, addresses = g_mime_message_get_all_recipients (message); gen_terms_address_names (term_gen, addresses, "to_name"); - value = g_mime_message_get_subject (message); - value = skip_re_in_subject (value); - gen_terms (term_gen, "subject", value); - gen_terms (term_gen, "body", value); + subject = g_mime_message_get_subject (message); + subject = skip_re_in_subject (subject); + gen_terms (term_gen, "subject", subject); + gen_terms (term_gen, "body", subject); + + g_mime_message_foreach (message, gen_terms_part, &term_gen); + + parents = g_ptr_array_new (); + + refs = g_mime_object_get_header (GMIME_OBJECT (message), "references"); + parse_references (parents, refs); + + in_reply_to = g_mime_object_get_header (GMIME_OBJECT (message), + "in-reply-to"); + parse_references (parents, in_reply_to); + + for (i = 0; i < parents->len; i++) + add_term (doc, "ref", (char *) g_ptr_array_index (parents, i)); + + message_id = g_mime_message_get_message_id (message); - gen_terms_body (term_gen, filename, - g_mime_parser_get_headers_end (parser)); + thread_id = find_thread_id (db, parents, message_id); - add_terms_references (doc, message); + for (i = 0; i < parents->len; i++) + g_free (g_ptr_array_index (parents, i)); + g_ptr_array_free (parents, TRUE); from = g_mime_message_get_sender (message); addresses = internet_address_list_parse_string (from); @@ -406,12 +515,18 @@ index_file (Xapian::WritableDatabase db, add_term (doc, "type", "mail"); add_term (doc, "source_id", "1"); - value = g_mime_message_get_message_id (message); - add_term (doc, "msgid", value); - add_term (doc, "thread", value); - - doc.add_value (NOTMUCH_VALUE_MESSAGE_ID, value); - doc.add_value (NOTMUCH_VALUE_THREAD, value); + add_term (doc, "msgid", message_id); + doc.add_value (NOTMUCH_VALUE_MESSAGE_ID, message_id); + + if (thread_id) { + add_term (doc, "thread", thread_id); + doc.add_value (NOTMUCH_VALUE_THREAD, thread_id); + free ((void *) thread_id); + } else { + /* If not referenced thread, use the message ID */ + add_term (doc, "thread", message_id); + doc.add_value (NOTMUCH_VALUE_THREAD, message_id); + } doc.add_value (NOTMUCH_VALUE_DATE, Xapian::sortable_serialise (time));