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