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