22b50d4d7bf836e58d7086825d072a3588889b89
[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 */
256         for (node = root->children; node != NULL; ) {
257                 prev = node->prev;      /* CLAWS: need the last node */
258                 parent = NULL;
259                 next = node->next;
260                 msginfo = (MsgInfo *)node->data;
261                 if (msginfo->inreplyto) { 
262                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
263                         /* node should not be the parent, and node should not 
264                            be an ancestor of parent (circular reference) */
265                         if (parent && parent != node && 
266                             !g_node_is_ancestor(node, parent)) {
267                                 g_node_unlink(node);
268                                 g_node_insert_before
269                                         (parent, parent->children, node);
270                         }                               
271                 }
272                 last = (next == NULL) ? prev : node;
273                 node = next;
274         }
275
276         if (prefs_common.thread_by_subject) {
277                 for (node = last; node && node != NULL;) {
278                         next = node->prev;
279                         msginfo = (MsgInfo *) node->data;
280                         
281                         /* may not parentize if parent was delivered after childs */
282                         if (subject != msginfo->subject)
283                                 parent = subject_relation_lookup(subject_relation, msginfo);
284                         else
285                                 parent = NULL; 
286                         
287                         /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to 
288                            find the parent node */
289                         if (parent != NULL) {
290                                 if (g_node_is_ancestor(node, parent))
291                                         parent = NULL;
292                                 if (parent == node)
293                                         parent = NULL;
294                         }
295                         
296                         if (parent) {
297                                 g_node_unlink(node);
298                                 g_node_append(parent, node);
299                         }
300
301                         node = next;
302                 }       
303         }
304         
305         g_relation_destroy(subject_relation);
306         g_hash_table_destroy(msgid_table);
307
308         return root;
309 }
310
311 void procmsg_move_messages(GSList *mlist)
312 {
313         GSList *cur, *movelist = NULL;
314         MsgInfo *msginfo;
315         FolderItem *dest = NULL;
316
317         if (!mlist) return;
318
319         folder_item_update_freeze();
320
321         for (cur = mlist; cur != NULL; cur = cur->next) {
322                 msginfo = (MsgInfo *)cur->data;
323                 if (!dest) {
324                         dest = msginfo->to_folder;
325                         movelist = g_slist_append(movelist, msginfo);
326                 } else if (dest == msginfo->to_folder) {
327                         movelist = g_slist_append(movelist, msginfo);
328                 } else {
329                         folder_item_move_msgs(dest, movelist);
330                         g_slist_free(movelist);
331                         movelist = NULL;
332                         dest = msginfo->to_folder;
333                         movelist = g_slist_append(movelist, msginfo);
334                 }
335                 procmsg_msginfo_set_to_folder(msginfo, NULL);
336         }
337
338         if (movelist) {
339                 folder_item_move_msgs(dest, movelist);
340                 g_slist_free(movelist);
341         }
342
343         folder_item_update_thaw();
344 }
345
346 void procmsg_copy_messages(GSList *mlist)
347 {
348         GSList *cur, *copylist = NULL;
349         MsgInfo *msginfo;
350         FolderItem *dest = NULL;
351
352         if (!mlist) return;
353
354         folder_item_update_freeze();
355
356         for (cur = mlist; cur != NULL; cur = cur->next) {
357                 msginfo = (MsgInfo *)cur->data;
358                 if (!dest) {
359                         dest = msginfo->to_folder;
360                         copylist = g_slist_append(copylist, msginfo);
361                 } else if (dest == msginfo->to_folder) {
362                         copylist = g_slist_append(copylist, msginfo);
363                 } else {
364                         folder_item_copy_msgs(dest, copylist);
365                         g_slist_free(copylist);
366                         copylist = NULL;
367                         dest = msginfo->to_folder;
368                         copylist = g_slist_append(copylist, msginfo);
369                 }
370                 procmsg_msginfo_set_to_folder(msginfo, NULL);
371         }
372
373         if (copylist) {
374                 folder_item_copy_msgs(dest, copylist);
375                 g_slist_free(copylist);
376         }
377
378         folder_item_update_thaw();
379 }
380
381 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
382 {
383         gchar *file;
384
385         g_return_val_if_fail(msginfo != NULL, NULL);
386
387         if (msginfo->plaintext_file)
388                 file = g_strdup(msginfo->plaintext_file);
389         else {
390                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
391         }
392
393         return file;
394 }
395
396 gchar *procmsg_get_message_file(MsgInfo *msginfo)
397 {
398         gchar *filename = NULL;
399
400         g_return_val_if_fail(msginfo != NULL, NULL);
401
402         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
403         if (!filename)
404                 debug_print("can't fetch message %d\n", msginfo->msgnum);
405
406         return filename;
407 }
408
409 GSList *procmsg_get_message_file_list(GSList *mlist)
410 {
411         GSList *file_list = NULL;
412         MsgInfo *msginfo;
413         MsgFileInfo *fileinfo;
414         gchar *file;
415
416         while (mlist != NULL) {
417                 msginfo = (MsgInfo *)mlist->data;
418                 file = procmsg_get_message_file(msginfo);
419                 if (!file) {
420                         procmsg_message_file_list_free(file_list);
421                         return NULL;
422                 }
423                 fileinfo = g_new(MsgFileInfo, 1);
424                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
425                 fileinfo->file = file;
426                 fileinfo->flags = g_new(MsgFlags, 1);
427                 *fileinfo->flags = msginfo->flags;
428                 file_list = g_slist_prepend(file_list, fileinfo);
429                 mlist = mlist->next;
430         }
431
432         file_list = g_slist_reverse(file_list);
433
434         return file_list;
435 }
436
437 void procmsg_message_file_list_free(MsgInfoList *file_list)
438 {
439         GSList *cur;
440         MsgFileInfo *fileinfo;
441
442         for (cur = file_list; cur != NULL; cur = cur->next) {
443                 fileinfo = (MsgFileInfo *)cur->data;
444                 procmsg_msginfo_free(fileinfo->msginfo);
445                 g_free(fileinfo->file);
446                 g_free(fileinfo->flags);
447                 g_free(fileinfo);
448         }
449
450         g_slist_free(file_list);
451 }
452
453 FILE *procmsg_open_message(MsgInfo *msginfo)
454 {
455         FILE *fp;
456         gchar *file;
457
458         g_return_val_if_fail(msginfo != NULL, NULL);
459
460         file = procmsg_get_message_file_path(msginfo);
461         g_return_val_if_fail(file != NULL, NULL);
462
463         if (!is_file_exist(file)) {
464                 g_free(file);
465                 file = procmsg_get_message_file(msginfo);
466                 if (!file)
467                         return NULL;
468         }
469
470         if ((fp = fopen(file, "rb")) == NULL) {
471                 FILE_OP_ERROR(file, "fopen");
472                 g_free(file);
473                 return NULL;
474         }
475
476         g_free(file);
477
478         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
479                 gchar buf[BUFFSIZE];
480
481                 while (fgets(buf, sizeof(buf), fp) != NULL)
482                         if (buf[0] == '\r' || buf[0] == '\n') break;
483         }
484
485         return fp;
486 }
487
488 gboolean procmsg_msg_exist(MsgInfo *msginfo)
489 {
490         gchar *path;
491         gboolean ret;
492
493         if (!msginfo) return FALSE;
494
495         path = folder_item_get_path(msginfo->folder);
496         change_dir(path);
497         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
498         g_free(path);
499
500         return ret;
501 }
502
503 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
504                                 PrefsFilterType type)
505 {
506         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
507                                        {"X-ML-Name:",      NULL, TRUE},
508                                        {"X-List:",         NULL, TRUE},
509                                        {"X-Mailing-list:", NULL, TRUE},
510                                        {"List-Id:",        NULL, TRUE},
511                                        {"X-Sequence:",     NULL, TRUE},
512                                        {NULL,              NULL, FALSE}};
513         enum
514         {
515                 H_X_BEENTHERE    = 0,
516                 H_X_ML_NAME      = 1,
517                 H_X_LIST         = 2,
518                 H_X_MAILING_LIST = 3,
519                 H_LIST_ID        = 4,
520                 H_X_SEQUENCE     = 5
521         };
522
523         FILE *fp;
524
525         g_return_if_fail(msginfo != NULL);
526         g_return_if_fail(header != NULL);
527         g_return_if_fail(key != NULL);
528
529         *header = NULL;
530         *key = NULL;
531
532         switch (type) {
533         case FILTER_BY_NONE:
534                 return;
535         case FILTER_BY_AUTO:
536                 if ((fp = procmsg_open_message(msginfo)) == NULL)
537                         return;
538                 procheader_get_header_fields(fp, hentry);
539                 fclose(fp);
540
541 #define SET_FILTER_KEY(hstr, idx)       \
542 {                                       \
543         *header = g_strdup(hstr);       \
544         *key = hentry[idx].body;        \
545         hentry[idx].body = NULL;        \
546 }
547
548                 if (hentry[H_X_BEENTHERE].body != NULL) {
549                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
550                 } else if (hentry[H_X_ML_NAME].body != NULL) {
551                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
552                 } else if (hentry[H_X_LIST].body != NULL) {
553                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
554                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
555                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
556                 } else if (hentry[H_LIST_ID].body != NULL) {
557                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
558                         extract_list_id_str(*key);
559                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
560                         gchar *p;
561
562                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
563                         p = *key;
564                         while (*p != '\0') {
565                                 while (*p != '\0' && !isspace(*p)) p++;
566                                 while (isspace(*p)) p++;
567                                 if (isdigit(*p)) {
568                                         *p = '\0';
569                                         break;
570                                 }
571                         }
572                         g_strstrip(*key);
573                 } else if (msginfo->subject) {
574                         *header = g_strdup("subject");
575                         *key = g_strdup(msginfo->subject);
576                 }
577
578 #undef SET_FILTER_KEY
579
580                 g_free(hentry[H_X_BEENTHERE].body);
581                 hentry[H_X_BEENTHERE].body = NULL;
582                 g_free(hentry[H_X_ML_NAME].body);
583                 hentry[H_X_ML_NAME].body = NULL;
584                 g_free(hentry[H_X_LIST].body);
585                 hentry[H_X_LIST].body = NULL;
586                 g_free(hentry[H_X_MAILING_LIST].body);
587                 hentry[H_X_MAILING_LIST].body = NULL;
588                 g_free(hentry[H_LIST_ID].body);
589                 hentry[H_LIST_ID].body = NULL;
590
591                 break;
592         case FILTER_BY_FROM:
593                 *header = g_strdup("from");
594                 *key = g_strdup(msginfo->from);
595                 break;
596         case FILTER_BY_TO:
597                 *header = g_strdup("to");
598                 *key = g_strdup(msginfo->to);
599                 break;
600         case FILTER_BY_SUBJECT:
601                 *header = g_strdup("subject");
602                 *key = g_strdup(msginfo->subject);
603                 break;
604         default:
605                 break;
606         }
607 }
608
609 void procmsg_empty_trash(void)
610 {
611         FolderItem *trash;
612         GList *cur;
613
614         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
615                 trash = FOLDER(cur->data)->trash;
616                 if (trash && trash->total_msgs > 0)
617                         folder_item_remove_all_msg(trash);
618         }
619 }
620
621 /*!
622  *\brief        Send messages in queue
623  *
624  *\param        queue Queue folder to process
625  *\param        save_msgs Unused
626  *
627  *\return       Number of messages sent, negative if an error occurred
628  *              positive if no error occurred
629  */
630 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
631 {
632         gint ret = 1, count = 0;
633         GSList *list, *elem;
634
635         if (!queue)
636                 queue = folder_get_default_queue();
637         g_return_val_if_fail(queue != NULL, -1);
638
639         folder_item_scan(queue);
640         list = folder_item_get_msg_list(queue);
641
642         for (elem = list; elem != NULL; elem = elem->next) {
643                 gchar *file;
644                 MsgInfo *msginfo;
645                 
646                 msginfo = (MsgInfo *)(elem->data);
647                 if (!MSG_IS_LOCKED(msginfo->flags)) {
648                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
649                         if (file) {
650                                 if (procmsg_send_message_queue(file) < 0) {
651                                         g_warning("Sending queued message %d failed.\n", 
652                                                   msginfo->msgnum);
653                                         ret = -1;
654                                 } else {
655                                         /* CLAWS: 
656                                          * We save in procmsg_send_message_queue because
657                                          * we need the destination folder from the queue
658                                          * header
659                                                         
660                                         if (save_msgs)
661                                                 procmsg_save_to_outbox
662                                                         (queue->folder->outbox,
663                                                          file, TRUE);
664                                          */
665                                         count++; 
666                                         folder_item_remove_msg(queue, msginfo->msgnum);
667                                 }
668                                 g_free(file);
669                         }
670                 }
671                 /* FIXME: supposedly if only one message is locked, and queue
672                  * is being flushed, the following free says something like 
673                  * "freeing msg ## in folder (nil)". */
674                 procmsg_msginfo_free(msginfo);
675         }
676
677         return ret * count;
678 }
679
680 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
681 {
682         FILE *fp, *outfp;
683         gchar buf[BUFFSIZE];
684         
685         if ((fp = fopen(in, "rb")) == NULL) {
686                 FILE_OP_ERROR(in, "fopen");
687                 return -1;
688         }
689         if ((outfp = fopen(out, "wb")) == NULL) {
690                 FILE_OP_ERROR(out, "fopen");
691                 fclose(fp);
692                 return -1;
693         }
694         while (fgets(buf, sizeof(buf), fp) != NULL)
695                 if (buf[0] == '\r' || buf[0] == '\n') break;
696         while (fgets(buf, sizeof(buf), fp) != NULL)
697                 fputs(buf, outfp);
698         fclose(outfp);
699         fclose(fp);
700         return 0;
701
702 }
703 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
704                             gboolean is_queued)
705 {
706         gint num;
707         MsgInfo *msginfo, *tmp_msginfo;
708         MsgFlags flag = {0, 0};
709
710         debug_print("saving sent message...\n");
711
712         if (!outbox)
713                 outbox = folder_get_default_outbox();
714         g_return_val_if_fail(outbox != NULL, -1);
715
716         /* remove queueing headers */
717         if (is_queued) {
718                 gchar tmp[MAXPATHLEN + 1];
719
720                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
721                            get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
722                 
723                 if (procmsg_remove_special_headers(file, tmp) !=0)
724                         return -1;
725
726                 folder_item_scan(outbox);
727                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
728                         g_warning("can't save message\n");
729                         unlink(tmp);
730                         return -1;
731                 }
732         } else {
733                 folder_item_scan(outbox);
734                 if ((num = folder_item_add_msg
735                         (outbox, file, &flag, FALSE)) < 0) {
736                         g_warning("can't save message\n");
737                         return -1;
738                 }
739                 return -1;
740         }
741         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
742         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
743         if (msginfo != NULL) {
744                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
745                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
746                 /* tmp_msginfo == msginfo */
747                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
748                     msginfo->returnreceiptto)) {
749                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
750                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
751                 }       
752         }
753
754         return 0;
755 }
756
757 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
758 {
759         static const gchar *def_cmd = "lpr %s";
760         static guint id = 0;
761         gchar *prtmp;
762         FILE *tmpfp, *prfp;
763         gchar buf[1024];
764         gchar *p;
765
766         g_return_if_fail(msginfo);
767
768         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
769                 g_warning("Can't get text part\n");
770                 return;
771         }
772
773         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
774                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
775
776         if ((prfp = fopen(prtmp, "wb")) == NULL) {
777                 FILE_OP_ERROR(prtmp, "fopen");
778                 g_free(prtmp);
779                 fclose(tmpfp);
780                 return;
781         }
782
783         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
784         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
785         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
786         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
787         if (msginfo->newsgroups)
788                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
789         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
790         fputc('\n', prfp);
791
792         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
793                 fputs(buf, prfp);
794
795         fclose(prfp);
796         fclose(tmpfp);
797
798         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
799             !strchr(p + 2, '%'))
800                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
801         else {
802                 if (cmdline)
803                         g_warning("Print command line is invalid: `%s'\n",
804                                   cmdline);
805                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
806         }
807
808         g_free(prtmp);
809
810         g_strchomp(buf);
811         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
812         system(buf);
813 }
814
815 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
816 {
817         msginfo->refcnt++;
818         
819         return msginfo;
820 }
821
822 MsgInfo *procmsg_msginfo_new(void)
823 {
824         MsgInfo *newmsginfo;
825
826         newmsginfo = g_new0(MsgInfo, 1);
827         newmsginfo->refcnt = 1;
828         
829         return newmsginfo;
830 }
831
832 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
833 {
834         MsgInfo *newmsginfo;
835
836         if (msginfo == NULL) return NULL;
837
838         newmsginfo = g_new0(MsgInfo, 1);
839
840         newmsginfo->refcnt = 1;
841
842 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
843 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
844                         g_strdup(msginfo->mmb) : NULL
845
846         MEMBCOPY(msgnum);
847         MEMBCOPY(size);
848         MEMBCOPY(mtime);
849         MEMBCOPY(date_t);
850         MEMBCOPY(flags);
851
852         MEMBDUP(fromname);
853
854         MEMBDUP(date);
855         MEMBDUP(from);
856         MEMBDUP(to);
857         MEMBDUP(cc);
858         MEMBDUP(newsgroups);
859         MEMBDUP(subject);
860         MEMBDUP(msgid);
861         MEMBDUP(inreplyto);
862         MEMBDUP(xref);
863
864         MEMBCOPY(folder);
865         MEMBCOPY(to_folder);
866
867         MEMBDUP(xface);
868         MEMBDUP(dispositionnotificationto);
869         MEMBDUP(returnreceiptto);
870         MEMBDUP(references);
871
872         MEMBCOPY(score);
873         MEMBCOPY(threadscore);
874
875         return newmsginfo;
876 }
877
878 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
879 {
880         MsgInfo *full_msginfo;
881         gchar *file;
882
883         if (msginfo == NULL) return NULL;
884
885         file = procmsg_get_message_file(msginfo);
886         if (!file) {
887                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
888                 return NULL;
889         }
890
891         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
892         g_free(file);
893         if (!full_msginfo) return NULL;
894
895         /* CLAWS: make sure we add the missing members; see: 
896          * procheader.c::procheader_get_headernames() */
897         if (!msginfo->xface)
898                 msginfo->xface = g_strdup(full_msginfo->xface);
899         if (!msginfo->dispositionnotificationto)
900                 msginfo->dispositionnotificationto = 
901                         g_strdup(full_msginfo->dispositionnotificationto);
902         if (!msginfo->returnreceiptto)
903                 msginfo->returnreceiptto = g_strdup
904                         (full_msginfo->returnreceiptto);
905         procmsg_msginfo_free(full_msginfo);
906
907         return procmsg_msginfo_new_ref(msginfo);
908 }
909
910 void procmsg_msginfo_free(MsgInfo *msginfo)
911 {
912         if (msginfo == NULL) return;
913
914         msginfo->refcnt--;
915         if (msginfo->refcnt > 0)
916                 return;
917
918         debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
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 }