0.9.4claws28
[claws.git] / src / procmsg.c
index 2cb62f3856a787049c33e6db5838a48a4a749ce7..ef9b94793973cd93210463888d6d6ccdf0c4a296 100644 (file)
@@ -137,20 +137,111 @@ static gboolean procmsg_ignore_node(GNode *node, gpointer data)
        return FALSE;
 }
 
+/* CLAWS subject threading:
+  
+  in the first round it inserts subject lines in a 
+  relation (subject <-> node)
+
+  the second round finishes the threads by attaching
+  matching subject lines to the one found in the
+  relation. will use the oldest node with the same
+  subject that is not more then thread_by_subject_max_age
+  days old (see subject_relation_lookup)
+*/  
+
+static void subject_relation_insert(GRelation *relation, GNode *node)
+{
+       gchar *subject;
+       MsgInfo *msginfo;
+
+       g_return_if_fail(relation != NULL);
+       g_return_if_fail(node != NULL);
+       msginfo = (MsgInfo *) node->data;
+       g_return_if_fail(msginfo != NULL);
+
+       subject = msginfo->subject;
+       if (subject == NULL)
+               return;
+       subject += subject_get_prefix_length(subject);
+
+       g_relation_insert(relation, subject, node);
+}
+
+static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
+{
+       gchar *subject;
+       GTuples *tuples;
+       GNode *node = NULL;
+       gint prefix_length;
+    
+       g_return_val_if_fail(relation != NULL, NULL);
+
+       subject = msginfo->subject;
+       if (subject == NULL)
+               return NULL;
+       prefix_length = subject_get_prefix_length(subject);
+       if (prefix_length <= 0)
+               return NULL;
+       subject += prefix_length;
+       
+       tuples = g_relation_select(relation, subject, 0);
+       if (tuples == NULL)
+               return NULL;
+
+       if (tuples->len > 0) {
+               int i;
+               GNode *relation_node;
+               MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
+               gboolean match;
+
+               /* check all nodes with the same subject to find the best parent */
+               for (i = 0; i < tuples->len; i++) {
+                       relation_node = (GNode *) g_tuples_index(tuples, i, 1);
+                       relation_msginfo = (MsgInfo *) relation_node->data;
+                       match = FALSE;
+
+                       /* best node should be the oldest in the found nodes */
+                       /* parent node must not be older then msginfo */
+                       if ((relation_msginfo->date_t < msginfo->date_t) &&
+                           ((best_msginfo == NULL) ||
+                            (best_msginfo->date_t > relation_msginfo->date_t)))
+                               match = TRUE;
+
+                       /* parent node must not be more then thread_by_subject_max_age
+                          days older then msginfo */
+                       if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
+                            prefs_common.thread_by_subject_max_age * 3600 * 24)
+                               match = FALSE;
+
+                       /* can add new tests for all matching
+                          nodes found by subject */
+
+                       if (match) {
+                               node = relation_node;
+                               best_msginfo = relation_msginfo;
+                       }
+               }           
+       }
+
+       g_tuples_destroy(tuples);
+       return node;
+}
+
 /* return the reversed thread tree */
 GNode *procmsg_get_thread_tree(GSList *mlist)
 {
-       GNode *root, *parent, *node, *next;
+       GNode *root, *parent, *node, *next, *last;
+       GNode *prev; /* CLAWS */
        GHashTable *msgid_table;
-       GHashTable *subject_table;
+       GRelation *subject_relation;
        MsgInfo *msginfo;
        const gchar *msgid;
        const gchar *subject;
-       GNode *found_subject;
 
        root = g_node_new(NULL);
        msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
-       subject_table = g_hash_table_new(g_str_hash, g_str_equal);
+       subject_relation = g_relation_new(2);
+       g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
 
        for (; mlist != NULL; mlist = mlist->next) {
                msginfo = (MsgInfo *)mlist->data;
@@ -160,90 +251,71 @@ GNode *procmsg_get_thread_tree(GSList *mlist)
                        parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
                        if (parent == NULL) {
                                parent = root;
-                       } else {
-                               if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
-                                       procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
-                                       procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
-                               }
                        }
                }
                node = g_node_insert_data_before
                        (parent, parent == root ? parent->children : NULL,
                         msginfo);
-               if ((msgid = msginfo->msgid) &&
-                   g_hash_table_lookup(msgid_table, msgid) == NULL)
+               if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
                        g_hash_table_insert(msgid_table, (gchar *)msgid, node);
 
+               /* CLAWS: add subject to relation (without prefix) */
                if (prefs_common.thread_by_subject) {
-                       subject  = msginfo->subject;
-                       subject += subject_get_reply_prefix_length(subject);
-                       found_subject = subject_table_lookup_clean(subject_table,
-                                                                  (gchar *) subject);
-                       if (found_subject == NULL)
-                               subject_table_insert_clean(subject_table, (gchar *) subject,
-                                                          node);
-                       else {
-                               /* replace if msg in table is older than current one 
-                                * can add here more stuff. */
-                               if ( ((MsgInfo*)(found_subject->data))->date_t >
-                                    ((MsgInfo*)(node->data))->date_t )  {
-                                       subject_table_remove_clean(subject_table, (gchar *) subject);
-                                       subject_table_insert_clean(subject_table, (gchar *) subject, node);
-                               }       
-                       }
+                       subject_relation_insert(subject_relation, node);
                }
        }
 
        /* complete the unfinished threads */
        for (node = root->children; node != NULL; ) {
+               prev = node->prev;      /* CLAWS: need the last node */
+               parent = NULL;
                next = node->next;
                msginfo = (MsgInfo *)node->data;
-               parent = NULL;
-               if (msginfo->inreplyto) 
+               if (msginfo->inreplyto) { 
                        parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
-               if (parent && parent != node) {
-                       g_node_unlink(node);
-                       g_node_insert_before
-                               (parent, parent->children, node);
-                       /* CLAWS: ignore thread */
-                       if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
-                               g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, procmsg_ignore_node, NULL);
-                       }
+                       /* node should not be the parent, and node should not 
+                          be an ancestor of parent (circular reference) */
+                       if (parent && parent != node && 
+                           !g_node_is_ancestor(node, parent)) {
+                               g_node_unlink(node);
+                               g_node_insert_before
+                                       (parent, parent->children, node);
+                       }                               
                }
+               last = (next == NULL) ? prev : node;
                node = next;
        }
 
-       /* CLAWS: now see if the first level (below root) still has some nodes that can be
-        * threaded by subject line. we need to handle this in a special way to prevent
-        * circular reference from a node that has already been threaded by IN-REPLY-TO
-        * but is also in the subject line hash table */
        if (prefs_common.thread_by_subject) {
-               for (node = root->children; node != NULL; ) {
-                       next = node->next;
+               for (node = last; node && node != NULL;) {
+                       next = node->prev;
                        msginfo = (MsgInfo *) node->data;
-                       parent = subject_table_lookup(subject_table, msginfo->subject);
-                       /* the node may already be threaded by IN-REPLY-TO,
-                          so go up in the tree to find the parent node */
+                       
+                       /* may not parentize if parent was delivered after childs */
+                       if (subject != msginfo->subject)
+                               parent = subject_relation_lookup(subject_relation, msginfo);
+                       else
+                               parent = NULL; 
+                       
+                       /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to 
+                          find the parent node */
                        if (parent != NULL) {
                                if (g_node_is_ancestor(node, parent))
                                        parent = NULL;
                                if (parent == node)
                                        parent = NULL;
                        }
-
+                       
                        if (parent) {
                                g_node_unlink(node);
                                g_node_append(parent, node);
-                               /* CLAWS: ignore thread */
-                               if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
-                                       g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, procmsg_ignore_node, NULL);
-                               }
                        }
+
                        node = next;
                }       
        }
        
-       g_hash_table_destroy(subject_table);
+       g_relation_destroy(subject_relation);
        g_hash_table_destroy(msgid_table);
 
        return root;
@@ -267,7 +339,7 @@ void procmsg_move_messages(GSList *mlist)
                } else if (dest == msginfo->to_folder) {
                        movelist = g_slist_append(movelist, msginfo);
                } else {
-                       folder_item_move_msgs_with_dest(dest, movelist);
+                       folder_item_move_msgs(dest, movelist);
                        g_slist_free(movelist);
                        movelist = NULL;
                        dest = msginfo->to_folder;
@@ -277,7 +349,7 @@ void procmsg_move_messages(GSList *mlist)
        }
 
        if (movelist) {
-               folder_item_move_msgs_with_dest(dest, movelist);
+               folder_item_move_msgs(dest, movelist);
                g_slist_free(movelist);
        }
 
@@ -302,7 +374,7 @@ void procmsg_copy_messages(GSList *mlist)
                } else if (dest == msginfo->to_folder) {
                        copylist = g_slist_append(copylist, msginfo);
                } else {
-                       folder_item_copy_msgs_with_dest(dest, copylist);
+                       folder_item_copy_msgs(dest, copylist);
                        g_slist_free(copylist);
                        copylist = NULL;
                        dest = msginfo->to_folder;
@@ -312,7 +384,7 @@ void procmsg_copy_messages(GSList *mlist)
        }
 
        if (copylist) {
-               folder_item_copy_msgs_with_dest(dest, copylist);
+               folder_item_copy_msgs(dest, copylist);
                g_slist_free(copylist);
        }
 
@@ -321,17 +393,14 @@ void procmsg_copy_messages(GSList *mlist)
 
 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
 {
-       gchar *path, *file;
+       gchar *file;
 
        g_return_val_if_fail(msginfo != NULL, NULL);
 
        if (msginfo->plaintext_file)
                file = g_strdup(msginfo->plaintext_file);
        else {
-               path = folder_item_get_path(msginfo->folder);
-               file = g_strconcat(path, G_DIR_SEPARATOR_S,
-                                  itos(msginfo->msgnum), NULL);
-               g_free(path);
+               file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
        }
 
        return file;
@@ -350,6 +419,50 @@ gchar *procmsg_get_message_file(MsgInfo *msginfo)
        return filename;
 }
 
+GSList *procmsg_get_message_file_list(GSList *mlist)
+{
+        GSList *file_list = NULL;
+        MsgInfo *msginfo;
+        MsgFileInfo *fileinfo;
+        gchar *file;
+
+        while (mlist != NULL) {
+                msginfo = (MsgInfo *)mlist->data;
+                file = procmsg_get_message_file(msginfo);
+                if (!file) {
+                        procmsg_message_file_list_free(file_list);
+                        return NULL;
+                }
+                fileinfo = g_new(MsgFileInfo, 1);
+               fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
+                fileinfo->file = file;
+                fileinfo->flags = g_new(MsgFlags, 1);
+                *fileinfo->flags = msginfo->flags;
+                file_list = g_slist_prepend(file_list, fileinfo);
+                mlist = mlist->next;
+        }
+
+        file_list = g_slist_reverse(file_list);
+
+        return file_list;
+}
+
+void procmsg_message_file_list_free(MsgInfoList *file_list)
+{
+       GSList *cur;
+       MsgFileInfo *fileinfo;
+
+       for (cur = file_list; cur != NULL; cur = cur->next) {
+               fileinfo = (MsgFileInfo *)cur->data;
+               procmsg_msginfo_free(fileinfo->msginfo);
+               g_free(fileinfo->file);
+               g_free(fileinfo->flags);
+               g_free(fileinfo);
+       }
+
+       g_slist_free(file_list);
+}
+
 FILE *procmsg_open_message(MsgInfo *msginfo)
 {
        FILE *fp;
@@ -654,7 +767,8 @@ gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
                            gboolean is_queued)
 {
        gint num;
-       MsgInfo *msginfo;
+       MsgInfo *msginfo, *tmp_msginfo;
+       MsgFlags flag = {0, 0};
 
        debug_print("saving sent message...\n");
 
@@ -673,23 +787,31 @@ gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
                        return -1;
 
                folder_item_scan(outbox);
-               if ((num = folder_item_add_msg(outbox, tmp, TRUE)) < 0) {
+               if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
                        g_warning("can't save message\n");
                        unlink(tmp);
                        return -1;
                }
        } else {
                folder_item_scan(outbox);
-               if ((num = folder_item_add_msg(outbox, file, FALSE)) < 0) {
+               if ((num = folder_item_add_msg
+                       (outbox, file, &flag, FALSE)) < 0) {
                        g_warning("can't save message\n");
                        return -1;
                }
                return -1;
        }
-       msginfo = folder_item_get_msginfo(outbox, num);
+       msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
+       tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
        if (msginfo != NULL) {
-           procmsg_msginfo_unset_flags(msginfo, ~0, 0);
-           procmsg_msginfo_free(msginfo);
+               procmsg_msginfo_unset_flags(msginfo, ~0, 0);
+               procmsg_msginfo_free(msginfo);                  /* refcnt-- */
+               /* tmp_msginfo == msginfo */
+               if (tmp_msginfo && (msginfo->dispositionnotificationto || 
+                   msginfo->returnreceiptto)) {
+                       procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
+                       procmsg_msginfo_free(msginfo);          /* refcnt-- */
+               }       
        }
        folder_item_update(outbox, TRUE);
 
@@ -834,6 +956,20 @@ MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
        g_free(file);
        if (!full_msginfo) return NULL;
 
+       /* CLAWS: make sure we add the missing members; see: 
+        * procheader.c::procheader_get_headernames() */
+       if (!msginfo->xface)
+               msginfo->xface = g_strdup(full_msginfo->xface);
+       if (!msginfo->dispositionnotificationto)
+               msginfo->dispositionnotificationto = 
+                       g_strdup(full_msginfo->dispositionnotificationto);
+       if (!msginfo->returnreceiptto)
+               msginfo->returnreceiptto = g_strdup
+                       (full_msginfo->returnreceiptto);
+       procmsg_msginfo_free(full_msginfo);
+
+       return procmsg_msginfo_new_ref(msginfo);
+#if 0
        full_msginfo->msgnum = msginfo->msgnum;
        full_msginfo->size = msginfo->size;
        full_msginfo->mtime = msginfo->mtime;
@@ -845,6 +981,7 @@ MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
        procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
 
        return full_msginfo;
+#endif
 }
 
 void procmsg_msginfo_free(MsgInfo *msginfo)
@@ -1128,13 +1265,14 @@ gint procmsg_send_message_queue(const gchar *file)
                else
                        tokens = g_strsplit(fwdmessageid, "\x7f", 0);
                item = folder_find_item_from_identifier(tokens[0]);
-               if (item != NULL) {
+
+               /* check if queued message has valid folder and message id */
+               if (item != NULL && tokens[2] != NULL) {
                        MsgInfo *msginfo;
                        
                        msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
-                       
-                       /*!< note that if the message has no msgid (maybe it was invalid), 
-                       * we also refuse to do something with the reply to flag */
+               
+                       /* check if referring message exists and has a message id */
                        if ((msginfo != NULL) && 
                            (msginfo->msgid != NULL) &&
                            (strcmp(msginfo->msgid, tokens[2]) != 0)) {