* src/procmsg.c
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 Hiroyuki Yamamoto
4  *
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 2 of the License, or
8  * (at your option) any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "defs.h"
21
22 #include <glib.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26
27 #include "intl.h"
28 #include "main.h"
29 #include "utils.h"
30 #include "procmsg.h"
31 #include "procheader.h"
32 #include "send_message.h"
33 #include "procmime.h"
34 #include "statusbar.h"
35 #include "prefs_filtering.h"
36 #include "filtering.h"
37 #include "folder.h"
38 #include "prefs_common.h"
39 #include "account.h"
40 #include "alertpanel.h"
41 #include "news.h"
42 #include "hooks.h"
43 #include "msgcache.h"
44
45 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
46 {
47         GHashTable *msg_table;
48
49         if (mlist == NULL) return NULL;
50
51         msg_table = g_hash_table_new(NULL, g_direct_equal);
52         procmsg_msg_hash_table_append(msg_table, mlist);
53
54         return msg_table;
55 }
56
57 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
58 {
59         GSList *cur;
60         MsgInfo *msginfo;
61
62         if (msg_table == NULL || mlist == NULL) return;
63
64         for (cur = mlist; cur != NULL; cur = cur->next) {
65                 msginfo = (MsgInfo *)cur->data;
66
67                 g_hash_table_insert(msg_table,
68                                     GUINT_TO_POINTER(msginfo->msgnum),
69                                     msginfo);
70         }
71 }
72
73 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
74 {
75         GHashTable *msg_table;
76         GSList *cur;
77         MsgInfo *msginfo;
78
79         if (mlist == NULL) return NULL;
80
81         msg_table = g_hash_table_new(NULL, g_direct_equal);
82
83         for (cur = mlist; cur != NULL; cur = cur->next) {
84                 msginfo = (MsgInfo *)cur->data;
85                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
86         }
87
88         return msg_table;
89 }
90
91 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
92 {
93         GSList *cur;
94         MsgInfo *msginfo;
95         gint last = 0;
96
97         for (cur = mlist; cur != NULL; cur = cur->next) {
98                 msginfo = (MsgInfo *)cur->data;
99                 if (msginfo && msginfo->msgnum > last)
100                         last = msginfo->msgnum;
101         }
102
103         return last;
104 }
105
106 void procmsg_msg_list_free(GSList *mlist)
107 {
108         GSList *cur;
109         MsgInfo *msginfo;
110
111         for (cur = mlist; cur != NULL; cur = cur->next) {
112                 msginfo = (MsgInfo *)cur->data;
113                 procmsg_msginfo_free(msginfo);
114         }
115         g_slist_free(mlist);
116 }
117
118 struct MarkSum {
119         gint *new_msgs;
120         gint *unread_msgs;
121         gint *total_msgs;
122         gint *min;
123         gint *max;
124         gint first;
125 };
126
127 /* CLAWS subject threading:
128   
129   in the first round it inserts subject lines in a 
130   relation (subject <-> node)
131
132   the second round finishes the threads by attaching
133   matching subject lines to the one found in the
134   relation. will use the oldest node with the same
135   subject that is not more then thread_by_subject_max_age
136   days old (see subject_relation_lookup)
137 */  
138
139 static void subject_relation_insert(GRelation *relation, GNode *node)
140 {
141         gchar *subject;
142         MsgInfo *msginfo;
143
144         g_return_if_fail(relation != NULL);
145         g_return_if_fail(node != NULL);
146         msginfo = (MsgInfo *) node->data;
147         g_return_if_fail(msginfo != NULL);
148
149         subject = msginfo->subject;
150         if (subject == NULL)
151                 return;
152         subject += subject_get_prefix_length(subject);
153
154         g_relation_insert(relation, subject, node);
155 }
156
157 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
158 {
159         gchar *subject;
160         GTuples *tuples;
161         GNode *node = NULL;
162         gint prefix_length;
163     
164         g_return_val_if_fail(relation != NULL, NULL);
165
166         subject = msginfo->subject;
167         if (subject == NULL)
168                 return NULL;
169         prefix_length = subject_get_prefix_length(subject);
170         if (prefix_length <= 0)
171                 return NULL;
172         subject += prefix_length;
173         
174         tuples = g_relation_select(relation, subject, 0);
175         if (tuples == NULL)
176                 return NULL;
177
178         if (tuples->len > 0) {
179                 int i;
180                 GNode *relation_node;
181                 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
182                 gboolean match;
183
184                 /* check all nodes with the same subject to find the best parent */
185                 for (i = 0; i < tuples->len; i++) {
186                         relation_node = (GNode *) g_tuples_index(tuples, i, 1);
187                         relation_msginfo = (MsgInfo *) relation_node->data;
188                         match = FALSE;
189
190                         /* best node should be the oldest in the found nodes */
191                         /* parent node must not be older then msginfo */
192                         if ((relation_msginfo->date_t < msginfo->date_t) &&
193                             ((best_msginfo == NULL) ||
194                              (best_msginfo->date_t > relation_msginfo->date_t)))
195                                 match = TRUE;
196
197                         /* parent node must not be more then thread_by_subject_max_age
198                            days older then msginfo */
199                         if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
200                             prefs_common.thread_by_subject_max_age * 3600 * 24)
201                                 match = FALSE;
202
203                         /* can add new tests for all matching
204                            nodes found by subject */
205
206                         if (match) {
207                                 node = relation_node;
208                                 best_msginfo = relation_msginfo;
209                         }
210                 }           
211         }
212
213         g_tuples_destroy(tuples);
214         return node;
215 }
216
217 /* return the reversed thread tree */
218 GNode *procmsg_get_thread_tree(GSList *mlist)
219 {
220         GNode *root, *parent, *node, *next, *last;
221         GNode *prev; /* CLAWS */
222         GHashTable *msgid_table;
223         GRelation *subject_relation;
224         MsgInfo *msginfo;
225         const gchar *msgid;
226         const gchar *subject;
227
228         root = g_node_new(NULL);
229         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
230         subject_relation = g_relation_new(2);
231         g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
232
233         for (; mlist != NULL; mlist = mlist->next) {
234                 msginfo = (MsgInfo *)mlist->data;
235                 parent = root;
236
237                 if (msginfo->inreplyto) {
238                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
239                         if (parent == NULL) {
240                                 parent = root;
241                         }
242                 }
243                 node = g_node_insert_data_before
244                         (parent, parent == root ? parent->children : NULL,
245                          msginfo);
246                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
247                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
248
249                 /* CLAWS: add subject to relation (without prefix) */
250                 if (prefs_common.thread_by_subject) {
251                         subject_relation_insert(subject_relation, node);
252                 }
253         }
254
255         /* complete the unfinished threads. record the last encountered node. */
256         for (node = root->children; node != NULL; ) {
257                 parent = prev = NULL;
258                 next = node->next;
259                 msginfo = (MsgInfo *)node->data;
260                 if (msginfo->inreplyto) { 
261                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
262                         /* node should not be the parent, and node should not 
263                            be an ancestor of parent (circular reference) */
264                         if (parent && parent != node && 
265                             !g_node_is_ancestor(node, parent)) {
266                                 /* since this node is moved away, the previous
267                                  * one is the last recorded one */
268                                 prev = node->prev;
269                                 g_node_unlink(node);
270                                 g_node_insert_before
271                                         (parent, parent->children, node);
272                         }                               
273                 }
274                 last = prev ? prev : node;
275                 node = next;
276         }
277
278         if (prefs_common.thread_by_subject) {
279                 for (node = last; node && node != NULL;) {
280                         next = node->prev;
281                         msginfo = (MsgInfo *) node->data;
282                         
283                         /* may not parentize if parent was delivered after childs */
284                         if (subject != msginfo->subject)
285                                 parent = subject_relation_lookup(subject_relation, msginfo);
286                         else
287                                 parent = NULL; 
288                         
289                         /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to 
290                            find the parent node */
291                         if (parent != NULL) {
292                                 if (g_node_is_ancestor(node, parent))
293                                         parent = NULL;
294                                 if (parent == node)
295                                         parent = NULL;
296                         }
297                         
298                         if (parent) {
299                                 g_node_unlink(node);
300                                 g_node_append(parent, node);
301                         }
302
303                         node = next;
304                 }       
305         }
306         
307         g_relation_destroy(subject_relation);
308         g_hash_table_destroy(msgid_table);
309
310         return root;
311 }
312
313 void procmsg_move_messages(GSList *mlist)
314 {
315         GSList *cur, *movelist = NULL;
316         MsgInfo *msginfo;
317         FolderItem *dest = NULL;
318
319         if (!mlist) return;
320
321         folder_item_update_freeze();
322
323         for (cur = mlist; cur != NULL; cur = cur->next) {
324                 msginfo = (MsgInfo *)cur->data;
325                 if (!dest) {
326                         dest = msginfo->to_folder;
327                         movelist = g_slist_append(movelist, msginfo);
328                 } else if (dest == msginfo->to_folder) {
329                         movelist = g_slist_append(movelist, msginfo);
330                 } else {
331                         folder_item_move_msgs(dest, movelist);
332                         g_slist_free(movelist);
333                         movelist = NULL;
334                         dest = msginfo->to_folder;
335                         movelist = g_slist_append(movelist, msginfo);
336                 }
337                 procmsg_msginfo_set_to_folder(msginfo, NULL);
338         }
339
340         if (movelist) {
341                 folder_item_move_msgs(dest, movelist);
342                 g_slist_free(movelist);
343         }
344
345         folder_item_update_thaw();
346 }
347
348 void procmsg_copy_messages(GSList *mlist)
349 {
350         GSList *cur, *copylist = NULL;
351         MsgInfo *msginfo;
352         FolderItem *dest = NULL;
353
354         if (!mlist) return;
355
356         folder_item_update_freeze();
357
358         for (cur = mlist; cur != NULL; cur = cur->next) {
359                 msginfo = (MsgInfo *)cur->data;
360                 if (!dest) {
361                         dest = msginfo->to_folder;
362                         copylist = g_slist_append(copylist, msginfo);
363                 } else if (dest == msginfo->to_folder) {
364                         copylist = g_slist_append(copylist, msginfo);
365                 } else {
366                         folder_item_copy_msgs(dest, copylist);
367                         g_slist_free(copylist);
368                         copylist = NULL;
369                         dest = msginfo->to_folder;
370                         copylist = g_slist_append(copylist, msginfo);
371                 }
372                 procmsg_msginfo_set_to_folder(msginfo, NULL);
373         }
374
375         if (copylist) {
376                 folder_item_copy_msgs(dest, copylist);
377                 g_slist_free(copylist);
378         }
379
380         folder_item_update_thaw();
381 }
382
383 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
384 {
385         gchar *file;
386
387         g_return_val_if_fail(msginfo != NULL, NULL);
388
389         if (msginfo->plaintext_file)
390                 file = g_strdup(msginfo->plaintext_file);
391         else {
392                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
393         }
394
395         return file;
396 }
397
398 gchar *procmsg_get_message_file(MsgInfo *msginfo)
399 {
400         gchar *filename = NULL;
401
402         g_return_val_if_fail(msginfo != NULL, NULL);
403
404         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
405         if (!filename)
406                 debug_print("can't fetch message %d\n", msginfo->msgnum);
407
408         return filename;
409 }
410
411 GSList *procmsg_get_message_file_list(GSList *mlist)
412 {
413         GSList *file_list = NULL;
414         MsgInfo *msginfo;
415         MsgFileInfo *fileinfo;
416         gchar *file;
417
418         while (mlist != NULL) {
419                 msginfo = (MsgInfo *)mlist->data;
420                 file = procmsg_get_message_file(msginfo);
421                 if (!file) {
422                         procmsg_message_file_list_free(file_list);
423                         return NULL;
424                 }
425                 fileinfo = g_new(MsgFileInfo, 1);
426                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
427                 fileinfo->file = file;
428                 fileinfo->flags = g_new(MsgFlags, 1);
429                 *fileinfo->flags = msginfo->flags;
430                 file_list = g_slist_prepend(file_list, fileinfo);
431                 mlist = mlist->next;
432         }
433
434         file_list = g_slist_reverse(file_list);
435
436         return file_list;
437 }
438
439 void procmsg_message_file_list_free(MsgInfoList *file_list)
440 {
441         GSList *cur;
442         MsgFileInfo *fileinfo;
443
444         for (cur = file_list; cur != NULL; cur = cur->next) {
445                 fileinfo = (MsgFileInfo *)cur->data;
446                 procmsg_msginfo_free(fileinfo->msginfo);
447                 g_free(fileinfo->file);
448                 g_free(fileinfo->flags);
449                 g_free(fileinfo);
450         }
451
452         g_slist_free(file_list);
453 }
454
455 FILE *procmsg_open_message(MsgInfo *msginfo)
456 {
457         FILE *fp;
458         gchar *file;
459
460         g_return_val_if_fail(msginfo != NULL, NULL);
461
462         file = procmsg_get_message_file_path(msginfo);
463         g_return_val_if_fail(file != NULL, NULL);
464
465         if (!is_file_exist(file)) {
466                 g_free(file);
467                 file = procmsg_get_message_file(msginfo);
468                 if (!file)
469                         return NULL;
470         }
471
472         if ((fp = fopen(file, "rb")) == NULL) {
473                 FILE_OP_ERROR(file, "fopen");
474                 g_free(file);
475                 return NULL;
476         }
477
478         g_free(file);
479
480         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
481                 gchar buf[BUFFSIZE];
482
483                 while (fgets(buf, sizeof(buf), fp) != NULL)
484                         if (buf[0] == '\r' || buf[0] == '\n') break;
485         }
486
487         return fp;
488 }
489
490 gboolean procmsg_msg_exist(MsgInfo *msginfo)
491 {
492         gchar *path;
493         gboolean ret;
494
495         if (!msginfo) return FALSE;
496
497         path = folder_item_get_path(msginfo->folder);
498         change_dir(path);
499         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
500         g_free(path);
501
502         return ret;
503 }
504
505 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
506                                 PrefsFilterType type)
507 {
508         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
509                                        {"X-ML-Name:",      NULL, TRUE},
510                                        {"X-List:",         NULL, TRUE},
511                                        {"X-Mailing-list:", NULL, TRUE},
512                                        {"List-Id:",        NULL, TRUE},
513                                        {"X-Sequence:",     NULL, TRUE},
514                                        {NULL,              NULL, FALSE}};
515         enum
516         {
517                 H_X_BEENTHERE    = 0,
518                 H_X_ML_NAME      = 1,
519                 H_X_LIST         = 2,
520                 H_X_MAILING_LIST = 3,
521                 H_LIST_ID        = 4,
522                 H_X_SEQUENCE     = 5
523         };
524
525         FILE *fp;
526
527         g_return_if_fail(msginfo != NULL);
528         g_return_if_fail(header != NULL);
529         g_return_if_fail(key != NULL);
530
531         *header = NULL;
532         *key = NULL;
533
534         switch (type) {
535         case FILTER_BY_NONE:
536                 return;
537         case FILTER_BY_AUTO:
538                 if ((fp = procmsg_open_message(msginfo)) == NULL)
539                         return;
540                 procheader_get_header_fields(fp, hentry);
541                 fclose(fp);
542
543 #define SET_FILTER_KEY(hstr, idx)       \
544 {                                       \
545         *header = g_strdup(hstr);       \
546         *key = hentry[idx].body;        \
547         hentry[idx].body = NULL;        \
548 }
549
550                 if (hentry[H_X_BEENTHERE].body != NULL) {
551                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
552                 } else if (hentry[H_X_ML_NAME].body != NULL) {
553                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
554                 } else if (hentry[H_X_LIST].body != NULL) {
555                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
556                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
557                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
558                 } else if (hentry[H_LIST_ID].body != NULL) {
559                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
560                         extract_list_id_str(*key);
561                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
562                         gchar *p;
563
564                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
565                         p = *key;
566                         while (*p != '\0') {
567                                 while (*p != '\0' && !isspace(*p)) p++;
568                                 while (isspace(*p)) p++;
569                                 if (isdigit(*p)) {
570                                         *p = '\0';
571                                         break;
572                                 }
573                         }
574                         g_strstrip(*key);
575                 } else if (msginfo->subject) {
576                         *header = g_strdup("subject");
577                         *key = g_strdup(msginfo->subject);
578                 }
579
580 #undef SET_FILTER_KEY
581
582                 g_free(hentry[H_X_BEENTHERE].body);
583                 hentry[H_X_BEENTHERE].body = NULL;
584                 g_free(hentry[H_X_ML_NAME].body);
585                 hentry[H_X_ML_NAME].body = NULL;
586                 g_free(hentry[H_X_LIST].body);
587                 hentry[H_X_LIST].body = NULL;
588                 g_free(hentry[H_X_MAILING_LIST].body);
589                 hentry[H_X_MAILING_LIST].body = NULL;
590                 g_free(hentry[H_LIST_ID].body);
591                 hentry[H_LIST_ID].body = NULL;
592
593                 break;
594         case FILTER_BY_FROM:
595                 *header = g_strdup("from");
596                 *key = g_strdup(msginfo->from);
597                 break;
598         case FILTER_BY_TO:
599                 *header = g_strdup("to");
600                 *key = g_strdup(msginfo->to);
601                 break;
602         case FILTER_BY_SUBJECT:
603                 *header = g_strdup("subject");
604                 *key = g_strdup(msginfo->subject);
605                 break;
606         default:
607                 break;
608         }
609 }
610
611 void procmsg_empty_trash(void)
612 {
613         FolderItem *trash;
614         GList *cur;
615
616         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
617                 trash = FOLDER(cur->data)->trash;
618                 if (trash && trash->total_msgs > 0)
619                         folder_item_remove_all_msg(trash);
620         }
621 }
622
623 /*!
624  *\brief        Send messages in queue
625  *
626  *\param        queue Queue folder to process
627  *\param        save_msgs Unused
628  *
629  *\return       Number of messages sent, negative if an error occurred
630  *              positive if no error occurred
631  */
632 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
633 {
634         gint ret = 1, count = 0;
635         GSList *list, *elem;
636
637         if (!queue)
638                 queue = folder_get_default_queue();
639         g_return_val_if_fail(queue != NULL, -1);
640
641         folder_item_scan(queue);
642         list = folder_item_get_msg_list(queue);
643
644         for (elem = list; elem != NULL; elem = elem->next) {
645                 gchar *file;
646                 MsgInfo *msginfo;
647                 
648                 msginfo = (MsgInfo *)(elem->data);
649                 if (!MSG_IS_LOCKED(msginfo->flags)) {
650                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
651                         if (file) {
652                                 if (procmsg_send_message_queue(file) < 0) {
653                                         g_warning("Sending queued message %d failed.\n", 
654                                                   msginfo->msgnum);
655                                         ret = -1;
656                                 } else {
657                                         /* CLAWS: 
658                                          * We save in procmsg_send_message_queue because
659                                          * we need the destination folder from the queue
660                                          * header
661                                                         
662                                         if (save_msgs)
663                                                 procmsg_save_to_outbox
664                                                         (queue->folder->outbox,
665                                                          file, TRUE);
666                                          */
667                                         count++; 
668                                         folder_item_remove_msg(queue, msginfo->msgnum);
669                                 }
670                                 g_free(file);
671                         }
672                 }
673                 /* FIXME: supposedly if only one message is locked, and queue
674                  * is being flushed, the following free says something like 
675                  * "freeing msg ## in folder (nil)". */
676                 procmsg_msginfo_free(msginfo);
677         }
678
679         return ret * count;
680 }
681
682 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
683 {
684         FILE *fp, *outfp;
685         gchar buf[BUFFSIZE];
686         
687         if ((fp = fopen(in, "rb")) == NULL) {
688                 FILE_OP_ERROR(in, "fopen");
689                 return -1;
690         }
691         if ((outfp = fopen(out, "wb")) == NULL) {
692                 FILE_OP_ERROR(out, "fopen");
693                 fclose(fp);
694                 return -1;
695         }
696         while (fgets(buf, sizeof(buf), fp) != NULL)
697                 if (buf[0] == '\r' || buf[0] == '\n') break;
698         while (fgets(buf, sizeof(buf), fp) != NULL)
699                 fputs(buf, outfp);
700         fclose(outfp);
701         fclose(fp);
702         return 0;
703
704 }
705 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
706                             gboolean is_queued)
707 {
708         gint num;
709         MsgInfo *msginfo, *tmp_msginfo;
710         MsgFlags flag = {0, 0};
711
712         debug_print("saving sent message...\n");
713
714         if (!outbox)
715                 outbox = folder_get_default_outbox();
716         g_return_val_if_fail(outbox != NULL, -1);
717
718         /* remove queueing headers */
719         if (is_queued) {
720                 gchar tmp[MAXPATHLEN + 1];
721
722                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
723                            get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
724                 
725                 if (procmsg_remove_special_headers(file, tmp) !=0)
726                         return -1;
727
728                 folder_item_scan(outbox);
729                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
730                         g_warning("can't save message\n");
731                         unlink(tmp);
732                         return -1;
733                 }
734         } else {
735                 folder_item_scan(outbox);
736                 if ((num = folder_item_add_msg
737                         (outbox, file, &flag, FALSE)) < 0) {
738                         g_warning("can't save message\n");
739                         return -1;
740                 }
741                 return -1;
742         }
743         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
744         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
745         if (msginfo != NULL) {
746                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
747                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
748                 /* tmp_msginfo == msginfo */
749                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
750                     msginfo->returnreceiptto)) {
751                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
752                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
753                 }       
754         }
755
756         return 0;
757 }
758
759 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
760 {
761         static const gchar *def_cmd = "lpr %s";
762         static guint id = 0;
763         gchar *prtmp;
764         FILE *tmpfp, *prfp;
765         gchar buf[1024];
766         gchar *p;
767
768         g_return_if_fail(msginfo);
769
770         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
771                 g_warning("Can't get text part\n");
772                 return;
773         }
774
775         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
776                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
777
778         if ((prfp = fopen(prtmp, "wb")) == NULL) {
779                 FILE_OP_ERROR(prtmp, "fopen");
780                 g_free(prtmp);
781                 fclose(tmpfp);
782                 return;
783         }
784
785         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
786         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
787         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
788         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
789         if (msginfo->newsgroups)
790                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
791         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
792         fputc('\n', prfp);
793
794         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
795                 fputs(buf, prfp);
796
797         fclose(prfp);
798         fclose(tmpfp);
799
800         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
801             !strchr(p + 2, '%'))
802                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
803         else {
804                 if (cmdline)
805                         g_warning("Print command line is invalid: `%s'\n",
806                                   cmdline);
807                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
808         }
809
810         g_free(prtmp);
811
812         g_strchomp(buf);
813         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
814         system(buf);
815 }
816
817 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
818 {
819         msginfo->refcnt++;
820         
821         return msginfo;
822 }
823
824 MsgInfo *procmsg_msginfo_new(void)
825 {
826         MsgInfo *newmsginfo;
827
828         newmsginfo = g_new0(MsgInfo, 1);
829         newmsginfo->refcnt = 1;
830         
831         return newmsginfo;
832 }
833
834 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
835 {
836         MsgInfo *newmsginfo;
837
838         if (msginfo == NULL) return NULL;
839
840         newmsginfo = g_new0(MsgInfo, 1);
841
842         newmsginfo->refcnt = 1;
843
844 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
845 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
846                         g_strdup(msginfo->mmb) : NULL
847
848         MEMBCOPY(msgnum);
849         MEMBCOPY(size);
850         MEMBCOPY(mtime);
851         MEMBCOPY(date_t);
852         MEMBCOPY(flags);
853
854         MEMBDUP(fromname);
855
856         MEMBDUP(date);
857         MEMBDUP(from);
858         MEMBDUP(to);
859         MEMBDUP(cc);
860         MEMBDUP(newsgroups);
861         MEMBDUP(subject);
862         MEMBDUP(msgid);
863         MEMBDUP(inreplyto);
864         MEMBDUP(xref);
865
866         MEMBCOPY(folder);
867         MEMBCOPY(to_folder);
868
869         MEMBDUP(xface);
870         MEMBDUP(dispositionnotificationto);
871         MEMBDUP(returnreceiptto);
872         MEMBDUP(references);
873
874         MEMBCOPY(score);
875         MEMBCOPY(threadscore);
876
877         return newmsginfo;
878 }
879
880 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
881 {
882         MsgInfo *full_msginfo;
883         gchar *file;
884
885         if (msginfo == NULL) return NULL;
886
887         file = procmsg_get_message_file(msginfo);
888         if (!file) {
889                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
890                 return NULL;
891         }
892
893         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
894         g_free(file);
895         if (!full_msginfo) return NULL;
896
897         /* CLAWS: make sure we add the missing members; see: 
898          * procheader.c::procheader_get_headernames() */
899         if (!msginfo->xface)
900                 msginfo->xface = g_strdup(full_msginfo->xface);
901         if (!msginfo->dispositionnotificationto)
902                 msginfo->dispositionnotificationto = 
903                         g_strdup(full_msginfo->dispositionnotificationto);
904         if (!msginfo->returnreceiptto)
905                 msginfo->returnreceiptto = g_strdup
906                         (full_msginfo->returnreceiptto);
907         procmsg_msginfo_free(full_msginfo);
908
909         return procmsg_msginfo_new_ref(msginfo);
910 }
911
912 void procmsg_msginfo_free(MsgInfo *msginfo)
913 {
914         if (msginfo == NULL) return;
915
916         msginfo->refcnt--;
917         if (msginfo->refcnt > 0)
918                 return;
919
920         if (msginfo->to_folder) {
921                 msginfo->to_folder->op_count--;
922                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
923         }
924
925         g_free(msginfo->fromspace);
926         g_free(msginfo->references);
927         g_free(msginfo->returnreceiptto);
928         g_free(msginfo->dispositionnotificationto);
929         g_free(msginfo->xface);
930
931         g_free(msginfo->fromname);
932
933         g_free(msginfo->date);
934         g_free(msginfo->from);
935         g_free(msginfo->to);
936         g_free(msginfo->cc);
937         g_free(msginfo->newsgroups);
938         g_free(msginfo->subject);
939         g_free(msginfo->msgid);
940         g_free(msginfo->inreplyto);
941         g_free(msginfo->xref);
942
943         g_free(msginfo);
944 }
945
946 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
947 {
948         guint memusage = 0;
949         
950         memusage += sizeof(MsgInfo);
951         if (msginfo->fromname)
952                 memusage += strlen(msginfo->fromname);
953         if (msginfo->date)
954                 memusage += strlen(msginfo->date);
955         if (msginfo->from)
956                 memusage += strlen(msginfo->from);
957         if (msginfo->to)
958                 memusage += strlen(msginfo->to);
959         if (msginfo->cc)
960                 memusage += strlen(msginfo->cc);
961         if (msginfo->newsgroups)
962                 memusage += strlen(msginfo->newsgroups);
963         if (msginfo->subject)
964                 memusage += strlen(msginfo->subject);
965         if (msginfo->msgid)
966                 memusage += strlen(msginfo->msgid);
967         if (msginfo->inreplyto)
968                 memusage += strlen(msginfo->inreplyto);
969         if (msginfo->xface)
970                 memusage += strlen(msginfo->xface);
971         if (msginfo->dispositionnotificationto)
972                 memusage += strlen(msginfo->dispositionnotificationto);
973         if (msginfo->returnreceiptto)
974                 memusage += strlen(msginfo->returnreceiptto);
975         if (msginfo->references)
976                 memusage += strlen(msginfo->references);
977         if (msginfo->fromspace)
978                 memusage += strlen(msginfo->fromspace);
979
980         return memusage;
981 }
982
983 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
984 {
985         const MsgInfo *msginfo1 = a;
986         const MsgInfo *msginfo2 = b;
987
988         if (!msginfo1)
989                 return -1;
990         if (!msginfo2)
991                 return -1;
992
993         return msginfo1->msgnum - msginfo2->msgnum;
994 }
995
996 enum
997 {
998         Q_SENDER           = 0,
999         Q_SMTPSERVER       = 1,
1000         Q_RECIPIENTS       = 2,
1001         Q_NEWSGROUPS       = 3,
1002         Q_MAIL_ACCOUNT_ID  = 4,
1003         Q_NEWS_ACCOUNT_ID  = 5,
1004         Q_SAVE_COPY_FOLDER = 6,
1005         Q_REPLY_MESSAGE_ID = 7,
1006         Q_FWD_MESSAGE_ID   = 8
1007 };
1008
1009 gint procmsg_send_message_queue(const gchar *file)
1010 {
1011         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1012                                        {"SSV:",  NULL, FALSE},
1013                                        {"R:",    NULL, FALSE},
1014                                        {"NG:",   NULL, FALSE},
1015                                        {"MAID:", NULL, FALSE},
1016                                        {"NAID:", NULL, FALSE},
1017                                        {"SCF:",  NULL, FALSE},
1018                                        {"RMID:", NULL, FALSE},
1019                                        {"FMID:", NULL, FALSE},
1020                                        {NULL,    NULL, FALSE}};
1021         FILE *fp;
1022         gint filepos;
1023         gint mailval = 0, newsval = 0;
1024         gchar *from = NULL;
1025         gchar *smtpserver = NULL;
1026         GSList *to_list = NULL;
1027         GSList *newsgroup_list = NULL;
1028         gchar *savecopyfolder = NULL;
1029         gchar *replymessageid = NULL;
1030         gchar *fwdmessageid = NULL;
1031         gchar buf[BUFFSIZE];
1032         gint hnum;
1033         PrefsAccount *mailac = NULL, *newsac = NULL;
1034         int local = 0;
1035
1036         g_return_val_if_fail(file != NULL, -1);
1037
1038         if ((fp = fopen(file, "rb")) == NULL) {
1039                 FILE_OP_ERROR(file, "fopen");
1040                 return -1;
1041         }
1042
1043         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1044                != -1) {
1045                 gchar *p = buf + strlen(qentry[hnum].name);
1046
1047                 switch (hnum) {
1048                 case Q_SENDER:
1049                         if (!from) from = g_strdup(p);
1050                         break;
1051                 case Q_SMTPSERVER:
1052                         if (!smtpserver) smtpserver = g_strdup(p);
1053                         break;
1054                 case Q_RECIPIENTS:
1055                         to_list = address_list_append(to_list, p);
1056                         break;
1057                 case Q_NEWSGROUPS:
1058                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1059                         break;
1060                 case Q_MAIL_ACCOUNT_ID:
1061                         mailac = account_find_from_id(atoi(p));
1062                         break;
1063                 case Q_NEWS_ACCOUNT_ID:
1064                         newsac = account_find_from_id(atoi(p));
1065                         break;
1066                 case Q_SAVE_COPY_FOLDER:
1067                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1068                         break;
1069                 case Q_REPLY_MESSAGE_ID:
1070                         if (!replymessageid) replymessageid = g_strdup(p);
1071                         break;
1072                 case Q_FWD_MESSAGE_ID:
1073                         if (!fwdmessageid) fwdmessageid = g_strdup(p);
1074                         break;
1075                 }
1076         }
1077         filepos = ftell(fp);
1078
1079         if (to_list) {
1080                 debug_print("Sending message by mail\n");
1081                 if (!from) {
1082                         g_warning("Queued message header is broken.\n");
1083                         mailval = -1;
1084                 } else if (mailac && mailac->use_mail_command &&
1085                            mailac->mail_command && (* mailac->mail_command)) {
1086                         mailval = send_message_local(mailac->mail_command, fp);
1087                         local = 1;
1088                 } else {
1089                         if (!mailac) {
1090                                 mailac = account_find_from_smtp_server(from, smtpserver);
1091                                 if (!mailac) {
1092                                         g_warning("Account not found. "
1093                                                     "Using current account...\n");
1094                                         mailac = cur_account;
1095                                 }
1096                         }
1097
1098                         if (mailac)
1099                                 mailval = send_message_smtp(mailac, to_list, fp);
1100                         else {
1101                                 PrefsAccount tmp_ac;
1102
1103                                 g_warning("Account not found.\n");
1104
1105                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1106                                 tmp_ac.address = from;
1107                                 tmp_ac.smtp_server = smtpserver;
1108                                 tmp_ac.smtpport = SMTP_PORT;
1109                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1110                         }
1111                 }
1112         }
1113
1114         fseek(fp, filepos, SEEK_SET);
1115         if (newsgroup_list && (newsval == 0)) {
1116                 Folder *folder;
1117                 gchar *tmp = NULL;
1118                 FILE *tmpfp;
1119
1120                 /* write to temporary file */
1121                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1122                             G_DIR_SEPARATOR, (gint)file);
1123                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1124                         FILE_OP_ERROR(tmp, "fopen");
1125                         newsval = -1;
1126                         alertpanel_error(_("Could not create temporary file for news sending."));
1127                 } else {
1128                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1129                                 FILE_OP_ERROR(tmp, "chmod");
1130                                 g_warning("can't change file mode\n");
1131                         }
1132
1133                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1134                                 if (fputs(buf, tmpfp) == EOF) {
1135                                         FILE_OP_ERROR(tmp, "fputs");
1136                                         newsval = -1;
1137                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1138                                 }
1139                         }
1140                         fclose(tmpfp);
1141
1142                         if (newsval == 0) {
1143                                 debug_print("Sending message by news\n");
1144
1145                                 folder = FOLDER(newsac->folder);
1146
1147                                 newsval = news_post(folder, tmp);
1148                                 if (newsval < 0) {
1149                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1150                                                  newsac->nntp_server);
1151                                 }
1152                         }
1153                         unlink(tmp);
1154                 }
1155                 g_free(tmp);
1156         }
1157
1158         slist_free_strings(to_list);
1159         g_slist_free(to_list);
1160         slist_free_strings(newsgroup_list);
1161         g_slist_free(newsgroup_list);
1162         g_free(from);
1163         g_free(smtpserver);
1164         fclose(fp);
1165
1166         /* save message to outbox */
1167         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1168                 FolderItem *outbox;
1169
1170                 debug_print("saving sent message...\n");
1171
1172                 outbox = folder_find_item_from_identifier(savecopyfolder);
1173                 if (!outbox)
1174                         outbox = folder_get_default_outbox();
1175
1176                 procmsg_save_to_outbox(outbox, file, TRUE);
1177         }
1178
1179         if (replymessageid != NULL || fwdmessageid != NULL) {
1180                 gchar **tokens;
1181                 FolderItem *item;
1182                 
1183                 if (replymessageid != NULL)
1184                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1185                 else
1186                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1187                 item = folder_find_item_from_identifier(tokens[0]);
1188
1189                 /* check if queued message has valid folder and message id */
1190                 if (item != NULL && tokens[2] != NULL) {
1191                         MsgInfo *msginfo;
1192                         
1193                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1194                 
1195                         /* check if referring message exists and has a message id */
1196                         if ((msginfo != NULL) && 
1197                             (msginfo->msgid != NULL) &&
1198                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1199                                 procmsg_msginfo_free(msginfo);
1200                                 msginfo = NULL;
1201                         }
1202                         
1203                         if (msginfo == NULL) {
1204                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1205                         }
1206                         
1207                         if (msginfo != NULL) {
1208                                 if (replymessageid != NULL) {
1209                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1210                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1211                                 } 
1212                                 else {
1213                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1214                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1215                                 }
1216                                 procmsg_msginfo_free(msginfo);
1217                         }
1218                 }
1219                 g_strfreev(tokens);
1220         }
1221
1222         g_free(savecopyfolder);
1223         g_free(replymessageid);
1224         g_free(fwdmessageid);
1225         
1226         return (newsval != 0 ? newsval : mailval);
1227 }
1228
1229 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1230 {
1231         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1232
1233         /* NEW flag */
1234         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1235                 item->new_msgs++;
1236         }
1237
1238         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1239                 item->new_msgs--;
1240         }
1241
1242         /* UNREAD flag */
1243         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1244                 item->unread_msgs++;
1245                 if (procmsg_msg_has_marked_parent(msginfo))
1246                         item->unreadmarked_msgs++;
1247         }
1248
1249         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1250                 item->unread_msgs--;
1251                 if (procmsg_msg_has_marked_parent(msginfo))
1252                         item->unreadmarked_msgs--;
1253         }
1254         
1255         /* MARK flag */
1256         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1257                 procmsg_update_unread_children(msginfo, TRUE);
1258         }
1259
1260         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1261                 procmsg_update_unread_children(msginfo, FALSE);
1262         }
1263 }
1264
1265 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1266 {
1267         FolderItem *item;
1268         MsgInfoUpdate msginfo_update;
1269         MsgPermFlags perm_flags_new, perm_flags_old;
1270
1271         g_return_if_fail(msginfo != NULL);
1272         item = msginfo->folder;
1273         g_return_if_fail(item != NULL);
1274         
1275         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1276
1277         /* Perm Flags handling */
1278         perm_flags_old = msginfo->flags.perm_flags;
1279         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1280         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1281                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1282         }
1283
1284         if (perm_flags_old != perm_flags_new) {
1285                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1286
1287                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1288
1289                 msginfo_update.msginfo = msginfo;
1290                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1291                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1292                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1293         }
1294
1295         /* Tmp flags hanlding */
1296         msginfo->flags.tmp_flags |= tmp_flags;
1297 }
1298
1299 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1300 {
1301         FolderItem *item;
1302         MsgInfoUpdate msginfo_update;
1303         MsgPermFlags perm_flags_new, perm_flags_old;
1304
1305         g_return_if_fail(msginfo != NULL);
1306         item = msginfo->folder;
1307         g_return_if_fail(item != NULL);
1308         
1309         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1310
1311         /* Perm Flags handling */
1312         perm_flags_old = msginfo->flags.perm_flags;
1313         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1314         
1315         if (perm_flags_old != perm_flags_new) {
1316                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1317
1318                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1319
1320                 msginfo_update.msginfo = msginfo;
1321                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1322                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1323                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1324         }
1325
1326         /* Tmp flags hanlding */
1327         msginfo->flags.tmp_flags &= ~tmp_flags;
1328 }
1329
1330 /*!
1331  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1332  *
1333  *\param        info Current message
1334  *\param        perm_flags Flags to be checked
1335  *\param        parentmsgs Hash of prior msgs to avoid loops
1336  *
1337  *\return       gboolean TRUE if perm_flags are found
1338  */
1339 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1340                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1341 {
1342         MsgInfo *tmp;
1343
1344         g_return_val_if_fail(info != NULL, FALSE);
1345
1346         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1347                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1348                                 info->inreplyto);
1349                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1350                         procmsg_msginfo_free(tmp);
1351                         return TRUE;
1352                 } else if (tmp != NULL) {
1353                         gboolean result;
1354
1355                         if (g_hash_table_lookup(parentmsgs, info)) {
1356                                 debug_print("loop detected: %s%c%d\n",
1357                                         folder_item_get_path(info->folder),
1358                                         G_DIR_SEPARATOR, info->msgnum);
1359                                 result = FALSE;
1360                         } else {
1361                                 g_hash_table_insert(parentmsgs, info, "1");
1362                                 result = procmsg_msg_has_flagged_parent_real(
1363                                     tmp, perm_flags, parentmsgs);
1364                         }
1365                         procmsg_msginfo_free(tmp);
1366                         return result;
1367                 } else {
1368                         return FALSE;
1369                 }
1370         } else
1371                 return FALSE;
1372 }
1373
1374 /*!
1375  *\brief        Callback for cleaning up hash of parentmsgs
1376  */
1377 gboolean parentmsgs_hash_remove(gpointer key,
1378                             gpointer value,
1379                             gpointer user_data)
1380 {
1381         return TRUE;
1382 }
1383
1384 /*!
1385  *\brief        Set up list of parentmsgs
1386  *              See procmsg_msg_has_flagged_parent_real()
1387  */
1388 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1389 {
1390         gboolean result;
1391         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1392
1393         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1394         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1395         g_hash_table_destroy(parentmsgs);
1396         return result;
1397 }
1398
1399 /*!
1400  *\brief        Check if msgs prior in thread are marked
1401  *              See procmsg_msg_has_flagged_parent_real()
1402  */
1403 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1404 {
1405         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1406 }
1407
1408
1409 GSList *procmsg_find_children_func(MsgInfo *info, 
1410                                    GSList *children, GSList *all)
1411 {
1412         GSList *cur;
1413
1414         g_return_val_if_fail(info!=NULL, children);
1415         if (info->msgid == NULL)
1416                 return children;
1417
1418         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1419                 MsgInfo *tmp = (MsgInfo *)cur->data;
1420                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1421                         /* Check if message is already in the list */
1422                         if ((children == NULL) || 
1423                             (g_slist_index(children, tmp) == -1)) {
1424                                 children = g_slist_prepend(children,
1425                                                 procmsg_msginfo_new_ref(tmp));
1426                                 children = procmsg_find_children_func(tmp, 
1427                                                         children, 
1428                                                         all);
1429                         }
1430                 }
1431         }
1432         return children;
1433 }
1434
1435 GSList *procmsg_find_children (MsgInfo *info)
1436 {
1437         GSList *children;
1438         GSList *all, *cur;
1439
1440         g_return_val_if_fail(info!=NULL, NULL);
1441         all = folder_item_get_msg_list(info->folder);
1442         children = procmsg_find_children_func(info, NULL, all);
1443         if (children != NULL) {
1444                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1445                         /* this will not free the used pointers
1446                            created with procmsg_msginfo_new_ref */
1447                         procmsg_msginfo_free((MsgInfo *)cur->data);
1448                 }
1449         }
1450         g_slist_free(all);
1451
1452         return children;
1453 }
1454
1455 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1456 {
1457         GSList *children = procmsg_find_children(info);
1458         GSList *cur;
1459         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1460                 MsgInfo *tmp = (MsgInfo *)cur->data;
1461                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1462                         if(newly_marked) 
1463                                 info->folder->unreadmarked_msgs++;
1464                         else
1465                                 info->folder->unreadmarked_msgs--;
1466                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1467                 }
1468                 procmsg_msginfo_free(tmp);
1469         }
1470         g_slist_free(children);
1471 }
1472
1473 /**
1474  * Set the destination folder for a copy or move operation
1475  *
1476  * \param msginfo The message which's destination folder is changed
1477  * \param to_folder The destination folder for the operation
1478  */
1479 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1480 {
1481         if(msginfo->to_folder != NULL) {
1482                 msginfo->to_folder->op_count--;
1483                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1484         }
1485         msginfo->to_folder = to_folder;
1486         if(to_folder != NULL) {
1487                 to_folder->op_count++;
1488                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1489         }
1490 }
1491
1492 /**
1493  * Apply filtering actions to the msginfo
1494  *
1495  * \param msginfo The MsgInfo describing the message that should be filtered
1496  * \return TRUE if the message was moved and MsgInfo is now invalid,
1497  *         FALSE otherwise
1498  */
1499 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1500 {
1501         MailFilteringData mail_filtering_data;
1502                         
1503         mail_filtering_data.msginfo = msginfo;                  
1504         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1505                 return TRUE;
1506
1507         /* filter if enabled in prefs or move to inbox if not */
1508         if((filtering_rules != NULL) &&
1509            filter_message_by_msginfo(filtering_rules, msginfo))
1510                 return TRUE;
1511
1512         return FALSE;
1513 }