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