+ * The 'thread' will talloc_steal the 'message' and hold onto a
+ * reference to it.
+ */
+static void
+_thread_add_message (notmuch_thread_t *thread,
+ notmuch_message_t *message,
+ notmuch_string_list_t *exclude_terms,
+ notmuch_exclude_t omit_exclude)
+{
+ notmuch_tags_t *tags;
+ const char *tag;
+ InternetAddressList *list = NULL;
+ InternetAddress *address;
+ const char *from, *author;
+ char *clean_author;
+ bool message_excluded = false;
+
+ if (omit_exclude != NOTMUCH_EXCLUDE_FALSE) {
+ for (tags = notmuch_message_get_tags (message);
+ notmuch_tags_valid (tags);
+ notmuch_tags_move_to_next (tags))
+ {
+ tag = notmuch_tags_get (tags);
+ /* Is message excluded? */
+ for (notmuch_string_node_t *term = exclude_terms->head;
+ term != NULL;
+ term = term->next)
+ {
+ /* Check for an empty string, and then ignore initial 'K'. */
+ if (*(term->string) && strcmp(tag, (term->string + 1)) == 0) {
+ message_excluded = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (message_excluded && omit_exclude == NOTMUCH_EXCLUDE_ALL)
+ return;
+
+ _notmuch_message_list_add_message (thread->message_list,
+ talloc_steal (thread, message));
+ thread->total_messages++;
+ thread->total_files += notmuch_message_count_files (message);
+
+ g_hash_table_insert (thread->message_hash,
+ xstrdup (notmuch_message_get_message_id (message)),
+ message);
+
+ from = notmuch_message_get_header (message, "from");
+ if (from)
+ list = internet_address_list_parse_string (from);
+
+ if (list) {
+ address = internet_address_list_get_address (list, 0);
+ if (address) {
+ author = internet_address_get_name (address);
+ /* We treat quoted empty names as if they were empty. */
+ if (author == NULL || author[0] == '\0') {
+ InternetAddressMailbox *mailbox;
+ mailbox = INTERNET_ADDRESS_MAILBOX (address);
+ author = internet_address_mailbox_get_addr (mailbox);
+ }
+ clean_author = _thread_cleanup_author (thread, author, from);
+ _thread_add_author (thread, clean_author);
+ _notmuch_message_set_author (message, clean_author);
+ }
+ g_object_unref (G_OBJECT (list));
+ }
+
+ if (! thread->subject) {
+ const char *subject;
+ subject = notmuch_message_get_header (message, "subject");
+ thread->subject = talloc_strdup (thread, subject ? subject : "");
+ }
+
+ for (tags = notmuch_message_get_tags (message);
+ notmuch_tags_valid (tags);
+ notmuch_tags_move_to_next (tags))
+ {
+ tag = notmuch_tags_get (tags);
+ g_hash_table_insert (thread->tags, xstrdup (tag), NULL);
+ }
+
+ /* Mark excluded messages. */
+ if (message_excluded)
+ notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, true);
+}
+
+static void
+_thread_set_subject_from_message (notmuch_thread_t *thread,
+ notmuch_message_t *message)
+{
+ const char *subject;
+ const char *cleaned_subject;
+
+ subject = notmuch_message_get_header (message, "subject");
+ if (! subject)
+ return;
+
+ if ((strncasecmp (subject, "Re: ", 4) == 0) ||
+ (strncasecmp (subject, "Aw: ", 4) == 0) ||
+ (strncasecmp (subject, "Vs: ", 4) == 0) ||
+ (strncasecmp (subject, "Sv: ", 4) == 0)) {
+
+ cleaned_subject = talloc_strndup (thread,
+ subject + 4,
+ strlen(subject) - 4);
+ } else {
+ cleaned_subject = talloc_strdup (thread, subject);
+ }
+
+ if (! EMPTY_STRING(cleaned_subject)) {
+ if (thread->subject)
+ talloc_free (thread->subject);
+
+ thread->subject = talloc_strdup (thread, cleaned_subject);
+ }
+}
+
+/* Add a message to this thread which is known to match the original
+ * search specification. The 'sort' parameter controls whether the
+ * oldest or newest matching subject is applied to the thread as a
+ * whole. */
+static void
+_thread_add_matched_message (notmuch_thread_t *thread,
+ notmuch_message_t *message,
+ notmuch_sort_t sort)
+{
+ time_t date;
+ notmuch_message_t *hashed_message;
+
+ date = notmuch_message_get_date (message);
+
+ if (date < thread->oldest || ! thread->matched_messages) {
+ thread->oldest = date;
+ if (sort == NOTMUCH_SORT_OLDEST_FIRST)
+ _thread_set_subject_from_message (thread, message);
+ }
+
+ if (date > thread->newest || ! thread->matched_messages) {
+ thread->newest = date;
+ const char *cur_subject = notmuch_thread_get_subject(thread);
+ if (sort != NOTMUCH_SORT_OLDEST_FIRST || EMPTY_STRING(cur_subject))
+ _thread_set_subject_from_message (thread, message);
+ }
+
+ if (!notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED))
+ thread->matched_messages++;
+
+ if (g_hash_table_lookup_extended (thread->message_hash,
+ notmuch_message_get_message_id (message), NULL,
+ (void **) &hashed_message)) {
+ notmuch_message_set_flag (hashed_message,
+ NOTMUCH_MESSAGE_FLAG_MATCH, 1);
+ }
+
+ _thread_add_matched_author (thread, _notmuch_message_get_author (hashed_message));
+}
+
+static bool
+_parent_via_in_reply_to (notmuch_thread_t *thread, notmuch_message_t *message) {
+ notmuch_message_t *parent;
+ const char *in_reply_to;
+
+ in_reply_to = _notmuch_message_get_in_reply_to (message);
+ THREAD_DEBUG("checking message = %s in_reply_to=%s\n",
+ notmuch_message_get_message_id (message), in_reply_to);
+
+ if (in_reply_to && (! EMPTY_STRING(in_reply_to)) &&
+ g_hash_table_lookup_extended (thread->message_hash,
+ in_reply_to, NULL,
+ (void **) &parent)) {
+ _notmuch_message_add_reply (parent, message);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void
+_parent_or_toplevel (notmuch_thread_t *thread, notmuch_message_t *message)
+{
+ size_t max_depth = 0;
+ notmuch_message_t *new_parent;
+ notmuch_message_t *parent = NULL;
+ const notmuch_string_list_t *references =
+ _notmuch_message_get_references (message);
+
+ THREAD_DEBUG("trying to reparent via references: %s\n",
+ notmuch_message_get_message_id (message));
+
+ for (notmuch_string_node_t *ref_node = references->head;
+ ref_node; ref_node = ref_node->next) {
+ THREAD_DEBUG("checking reference=%s\n", ref_node->string);
+ if ((g_hash_table_lookup_extended (thread->message_hash,
+ ref_node->string, NULL,
+ (void **) &new_parent))) {
+ size_t new_depth = _notmuch_message_get_thread_depth (new_parent);
+ THREAD_DEBUG("got depth %lu\n", new_depth);
+ if (new_depth > max_depth || !parent) {
+ THREAD_DEBUG("adding at depth %lu parent=%s\n", new_depth, ref_node->string);
+ max_depth = new_depth;
+ parent = new_parent;
+ }
+ }
+ }
+ if (parent) {
+ THREAD_DEBUG("adding reply %s to parent=%s\n",
+ notmuch_message_get_message_id (message),
+ notmuch_message_get_message_id (parent));
+ _notmuch_message_add_reply (parent, message);
+ } else {
+ THREAD_DEBUG("adding as toplevel %s\n",
+ notmuch_message_get_message_id (message));
+ _notmuch_message_list_add_message (thread->toplevel_list, message);
+ }
+}
+
+static void
+_resolve_thread_relationships (notmuch_thread_t *thread)
+{
+ notmuch_message_node_t *node, *first_node;
+ notmuch_message_t *message;
+ void *local;
+ notmuch_message_list_t *maybe_toplevel_list;
+
+ first_node = thread->message_list->head;
+ if (! first_node)
+ return;
+
+ local = talloc_new (thread);
+ maybe_toplevel_list = _notmuch_message_list_create (local);
+
+ for (node = first_node->next; node; node = node->next) {
+ message = node->message;
+ if (! _parent_via_in_reply_to (thread, message))
+ _notmuch_message_list_add_message (maybe_toplevel_list, message);
+ }
+
+ for (notmuch_messages_t *roots = _notmuch_messages_create (maybe_toplevel_list);
+ notmuch_messages_valid (roots);
+ notmuch_messages_move_to_next (roots)) {
+ notmuch_message_t *message = notmuch_messages_get (roots);
+ _parent_or_toplevel (thread, message);
+ }
+ /*
+ * if we reach the end of the list without finding a top-level
+ * message, that means the thread is a cycle (or set of cycles)
+ * and any message can be considered top-level. Choose the oldest
+ * message, which happens to be first in our list.
+ */
+ if (first_node) {
+ message = first_node->message;
+ if (_notmuch_message_list_empty(thread->toplevel_list) ||
+ ! _parent_via_in_reply_to (thread, message)) {
+ /*
+ * If the oldest message happens to be in-reply-to a
+ * missing message, we only check for references if there
+ * is some other candidate for root message.
+ */
+ if (! _notmuch_message_list_empty (thread->toplevel_list))
+ _parent_or_toplevel (thread, message);
+ else
+ _notmuch_message_list_add_message (thread->toplevel_list, message);
+ }
+ }
+
+ /* XXX this could be made conditional on messages being inserted
+ * (out of order) in later passes
+ */
+ thread->toplevel_list = _notmuch_message_sort_subtrees (thread, thread->toplevel_list);
+
+
+ /* XXX: After scanning through the entire list looking for parents
+ * via "In-Reply-To", we should do a second pass that looks at the
+ * list of messages IDs in the "References" header instead. (And
+ * for this the parent would be the "deepest" message of all the
+ * messages found in the "References" list.)
+ *
+ * Doing this will allow messages and sub-threads to be positioned
+ * correctly in the thread even when an intermediate message is
+ * missing from the thread.
+ */
+ talloc_free (local);
+}
+
+/* Create a new notmuch_thread_t object by finding the thread
+ * containing the message with the given doc ID, treating any messages
+ * contained in match_set as "matched". Remove all messages in the
+ * thread from match_set.