sync with 0.8.11cvs20
[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         int header_offset;
478         int hentry_offset;
479
480         g_return_if_fail(msginfo != NULL);
481         g_return_if_fail(header != NULL);
482         g_return_if_fail(key != NULL);
483
484         *header = NULL;
485         *key = NULL;
486
487         header_offset = 1;
488         hentry_offset = 5;
489
490         switch (type) {
491         case FILTER_BY_NONE:
492                 return;
493         case FILTER_BY_AUTO:
494                 if ((fp = procmsg_open_message(msginfo)) == NULL)
495                         return;
496                 procheader_get_header_fields(fp, hentry);
497                 fclose(fp);
498
499                 if (hentry[H_X_BEENTHERE].body != NULL) {
500                         *header = g_strdup("header \"X-BeenThere\"");
501                         *key = hentry[H_X_BEENTHERE].body;
502                         hentry[H_X_BEENTHERE].body = NULL;
503                 } else if (hentry[H_X_ML_NAME].body != NULL) {
504                         *header = g_strdup("header \"X-ML-Name\"");
505                         *key = hentry[H_X_ML_NAME].body;
506                         hentry[H_X_ML_NAME].body = NULL;
507                 } else if (hentry[H_X_LIST].body != NULL) {
508                         *header = g_strdup("header \"X-List\"");
509                         *key = hentry[H_X_LIST].body;
510                         hentry[H_X_LIST].body = NULL;
511                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
512                         *header = g_strdup("header \"X-Mailing-List\"");
513                         *key = hentry[H_X_MAILING_LIST].body;
514                         hentry[H_X_MAILING_LIST].body = NULL;
515                 } else if (hentry[H_LIST_ID].body != NULL) {
516                         *header = g_strdup("header \"List-Id\"");
517                         *key = hentry[H_LIST_ID].body;
518                         hentry[H_LIST_ID].body = NULL;
519                 } else if (msginfo->subject) {
520                         *header = g_strdup("subject");
521                         *key = g_strdup(msginfo->subject);
522                 }
523
524                 g_free(hentry[H_X_BEENTHERE].body);
525                 hentry[H_X_BEENTHERE].body = NULL;
526                 g_free(hentry[H_X_ML_NAME].body);
527                 hentry[H_X_ML_NAME].body = NULL;
528                 g_free(hentry[H_X_LIST].body);
529                 hentry[H_X_LIST].body = NULL;
530                 g_free(hentry[H_X_MAILING_LIST].body);
531                 hentry[H_X_MAILING_LIST].body = NULL;
532                 g_free(hentry[H_LIST_ID].body);
533                 hentry[H_LIST_ID].body = NULL;
534
535                 break;
536         case FILTER_BY_FROM:
537                 *header = g_strdup("from");
538                 *key = g_strdup(msginfo->from);
539                 break;
540         case FILTER_BY_TO:
541                 *header = g_strdup("to");
542                 *key = g_strdup(msginfo->to);
543                 break;
544         case FILTER_BY_SUBJECT:
545                 *header = g_strdup("subject");
546                 *key = g_strdup(msginfo->subject);
547                 break;
548         default:
549                 break;
550         }
551 }
552
553 void procmsg_empty_trash(void)
554 {
555         FolderItem *trash;
556         GList *cur;
557
558         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
559                 trash = FOLDER(cur->data)->trash;
560                 if (trash && trash->total_msgs > 0)
561                         folder_item_remove_all_msg(trash);
562         }
563 }
564
565 /*!
566  *\brief        Send messages in queue
567  *
568  *\param        queue Queue folder to process
569  *\param        save_msgs Unused
570  *
571  *\return       Number of messages sent, negative if an error occurred
572  *              positive if no error occurred
573  */
574 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
575 {
576         gint ret = 1, count = 0;
577         GSList *list, *elem;
578
579         if (!queue)
580                 queue = folder_get_default_queue();
581         g_return_val_if_fail(queue != NULL, -1);
582
583         folder_item_scan(queue);
584         list = folder_item_get_msg_list(queue);
585
586         for (elem = list; elem != NULL; elem = elem->next) {
587                 gchar *file;
588                 MsgInfo *msginfo;
589                 
590                 msginfo = (MsgInfo *)(elem->data);
591                 if (!MSG_IS_LOCKED(msginfo->flags)) {
592                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
593                         if (file) {
594                                 if (procmsg_send_message_queue(file) < 0) {
595                                         g_warning("Sending queued message %d failed.\n", 
596                                                   msginfo->msgnum);
597                                         ret = -1;
598                                 } else {
599                                         /* CLAWS: 
600                                          * We save in procmsg_send_message_queue because
601                                          * we need the destination folder from the queue
602                                          * header
603                                                         
604                                         if (save_msgs)
605                                                 procmsg_save_to_outbox
606                                                         (queue->folder->outbox,
607                                                          file, TRUE);
608                                          */
609                                         count++; 
610                                         folder_item_remove_msg(queue, msginfo->msgnum);
611                                 }
612                                 g_free(file);
613                         }
614                 }
615                 /* FIXME: supposedly if only one message is locked, and queue
616                  * is being flushed, the following free says something like 
617                  * "freeing msg ## in folder (nil)". */
618                 procmsg_msginfo_free(msginfo);
619         }
620
621         return ret * count;
622 }
623
624 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
625 {
626         FILE *fp, *outfp;
627         gchar buf[BUFFSIZE];
628         
629         if ((fp = fopen(in, "rb")) == NULL) {
630                 FILE_OP_ERROR(in, "fopen");
631                 return -1;
632         }
633         if ((outfp = fopen(out, "wb")) == NULL) {
634                 FILE_OP_ERROR(out, "fopen");
635                 fclose(fp);
636                 return -1;
637         }
638         while (fgets(buf, sizeof(buf), fp) != NULL)
639                 if (buf[0] == '\r' || buf[0] == '\n') break;
640         while (fgets(buf, sizeof(buf), fp) != NULL)
641                 fputs(buf, outfp);
642         fclose(outfp);
643         fclose(fp);
644         return 0;
645
646 }
647 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
648                             gboolean is_queued)
649 {
650         gint num;
651         MsgInfo *msginfo;
652
653         debug_print("saving sent message...\n");
654
655         if (!outbox)
656                 outbox = folder_get_default_outbox();
657         g_return_val_if_fail(outbox != NULL, -1);
658
659         /* remove queueing headers */
660         if (is_queued) {
661                 gchar tmp[MAXPATHLEN + 1];
662
663                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
664                            get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
665                 
666                 if (procmsg_remove_special_headers(file, tmp) !=0)
667                         return -1;
668
669                 folder_item_scan(outbox);
670                 if ((num = folder_item_add_msg(outbox, tmp, TRUE)) < 0) {
671                         g_warning("can't save message\n");
672                         unlink(tmp);
673                         return -1;
674                 }
675         } else {
676                 folder_item_scan(outbox);
677                 if ((num = folder_item_add_msg(outbox, file, FALSE)) < 0) {
678                         g_warning("can't save message\n");
679                         return -1;
680                 }
681                 return -1;
682         }
683         msginfo = folder_item_get_msginfo(outbox, num);
684         if (msginfo != NULL) {
685             procmsg_msginfo_unset_flags(msginfo, ~0, 0);
686             procmsg_msginfo_free(msginfo);
687         }
688         folder_item_update(outbox, TRUE);
689
690         return 0;
691 }
692
693 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
694 {
695         static const gchar *def_cmd = "lpr %s";
696         static guint id = 0;
697         gchar *prtmp;
698         FILE *tmpfp, *prfp;
699         gchar buf[1024];
700         gchar *p;
701
702         g_return_if_fail(msginfo);
703
704         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
705                 g_warning("Can't get text part\n");
706                 return;
707         }
708
709         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
710                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
711
712         if ((prfp = fopen(prtmp, "wb")) == NULL) {
713                 FILE_OP_ERROR(prtmp, "fopen");
714                 g_free(prtmp);
715                 fclose(tmpfp);
716                 return;
717         }
718
719         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
720         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
721         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
722         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
723         if (msginfo->newsgroups)
724                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
725         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
726         fputc('\n', prfp);
727
728         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
729                 fputs(buf, prfp);
730
731         fclose(prfp);
732         fclose(tmpfp);
733
734         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
735             !strchr(p + 2, '%'))
736                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
737         else {
738                 if (cmdline)
739                         g_warning("Print command line is invalid: `%s'\n",
740                                   cmdline);
741                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
742         }
743
744         g_free(prtmp);
745
746         g_strchomp(buf);
747         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
748         system(buf);
749 }
750
751 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
752 {
753         msginfo->refcnt++;
754         
755         return msginfo;
756 }
757
758 MsgInfo *procmsg_msginfo_new()
759 {
760         MsgInfo *newmsginfo;
761
762         newmsginfo = g_new0(MsgInfo, 1);
763         newmsginfo->refcnt = 1;
764         
765         return newmsginfo;
766 }
767
768 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
769 {
770         MsgInfo *newmsginfo;
771
772         if (msginfo == NULL) return NULL;
773
774         newmsginfo = g_new0(MsgInfo, 1);
775
776         newmsginfo->refcnt = 1;
777
778 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
779 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
780                         g_strdup(msginfo->mmb) : NULL
781
782         MEMBCOPY(msgnum);
783         MEMBCOPY(size);
784         MEMBCOPY(mtime);
785         MEMBCOPY(date_t);
786         MEMBCOPY(flags);
787
788         MEMBDUP(fromname);
789
790         MEMBDUP(date);
791         MEMBDUP(from);
792         MEMBDUP(to);
793         MEMBDUP(cc);
794         MEMBDUP(newsgroups);
795         MEMBDUP(subject);
796         MEMBDUP(msgid);
797         MEMBDUP(inreplyto);
798         MEMBDUP(xref);
799
800         MEMBCOPY(folder);
801         MEMBCOPY(to_folder);
802
803         MEMBDUP(xface);
804         MEMBDUP(dispositionnotificationto);
805         MEMBDUP(returnreceiptto);
806         MEMBDUP(references);
807
808         MEMBCOPY(score);
809         MEMBCOPY(threadscore);
810
811         return newmsginfo;
812 }
813
814 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
815 {
816         MsgInfo *full_msginfo;
817         gchar *file;
818
819         if (msginfo == NULL) return NULL;
820
821         file = procmsg_get_message_file(msginfo);
822         if (!file) {
823                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
824                 return NULL;
825         }
826
827         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
828         g_free(file);
829         if (!full_msginfo) return NULL;
830
831         full_msginfo->msgnum = msginfo->msgnum;
832         full_msginfo->size = msginfo->size;
833         full_msginfo->mtime = msginfo->mtime;
834         full_msginfo->folder = msginfo->folder;
835 #if USE_GPGME
836         full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
837         full_msginfo->decryption_failed = msginfo->decryption_failed;
838 #endif
839         procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
840
841         return full_msginfo;
842 }
843
844 void procmsg_msginfo_free(MsgInfo *msginfo)
845 {
846         if (msginfo == NULL) return;
847
848         msginfo->refcnt--;
849         if (msginfo->refcnt > 0)
850                 return;
851
852         debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
853
854         if (msginfo->to_folder) {
855                 msginfo->to_folder->op_count--;
856                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
857         }
858
859         g_free(msginfo->fromspace);
860         g_free(msginfo->references);
861         g_free(msginfo->returnreceiptto);
862         g_free(msginfo->dispositionnotificationto);
863         g_free(msginfo->xface);
864
865         g_free(msginfo->fromname);
866
867         g_free(msginfo->date);
868         g_free(msginfo->from);
869         g_free(msginfo->to);
870         g_free(msginfo->cc);
871         g_free(msginfo->newsgroups);
872         g_free(msginfo->subject);
873         g_free(msginfo->msgid);
874         g_free(msginfo->inreplyto);
875         g_free(msginfo->xref);
876
877         g_free(msginfo);
878 }
879
880 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
881 {
882         guint memusage = 0;
883         
884         memusage += sizeof(MsgInfo);
885         if (msginfo->fromname)
886                 memusage += strlen(msginfo->fromname);
887         if (msginfo->date)
888                 memusage += strlen(msginfo->date);
889         if (msginfo->from)
890                 memusage += strlen(msginfo->from);
891         if (msginfo->to)
892                 memusage += strlen(msginfo->to);
893         if (msginfo->cc)
894                 memusage += strlen(msginfo->cc);
895         if (msginfo->newsgroups)
896                 memusage += strlen(msginfo->newsgroups);
897         if (msginfo->subject)
898                 memusage += strlen(msginfo->subject);
899         if (msginfo->msgid)
900                 memusage += strlen(msginfo->msgid);
901         if (msginfo->inreplyto)
902                 memusage += strlen(msginfo->inreplyto);
903         if (msginfo->xface)
904                 memusage += strlen(msginfo->xface);
905         if (msginfo->dispositionnotificationto)
906                 memusage += strlen(msginfo->dispositionnotificationto);
907         if (msginfo->returnreceiptto)
908                 memusage += strlen(msginfo->returnreceiptto);
909         if (msginfo->references)
910                 memusage += strlen(msginfo->references);
911         if (msginfo->fromspace)
912                 memusage += strlen(msginfo->fromspace);
913
914         return memusage;
915 }
916
917 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
918 {
919         const MsgInfo *msginfo1 = a;
920         const MsgInfo *msginfo2 = b;
921
922         if (!msginfo1)
923                 return -1;
924         if (!msginfo2)
925                 return -1;
926
927         return msginfo1->msgnum - msginfo2->msgnum;
928 }
929
930 enum
931 {
932         Q_SENDER           = 0,
933         Q_SMTPSERVER       = 1,
934         Q_RECIPIENTS       = 2,
935         Q_NEWSGROUPS       = 3,
936         Q_MAIL_ACCOUNT_ID  = 4,
937         Q_NEWS_ACCOUNT_ID  = 5,
938         Q_SAVE_COPY_FOLDER = 6,
939         Q_REPLY_MESSAGE_ID = 7,
940         Q_FWD_MESSAGE_ID   = 8
941 };
942
943 gint procmsg_send_message_queue(const gchar *file)
944 {
945         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
946                                        {"SSV:",  NULL, FALSE},
947                                        {"R:",    NULL, FALSE},
948                                        {"NG:",   NULL, FALSE},
949                                        {"MAID:", NULL, FALSE},
950                                        {"NAID:", NULL, FALSE},
951                                        {"SCF:",  NULL, FALSE},
952                                        {"RMID:", NULL, FALSE},
953                                        {"FMID:", NULL, FALSE},
954                                        {NULL,    NULL, FALSE}};
955         FILE *fp;
956         gint filepos;
957         gint mailval = 0, newsval = 0;
958         gchar *from = NULL;
959         gchar *smtpserver = NULL;
960         GSList *to_list = NULL;
961         GSList *newsgroup_list = NULL;
962         gchar *savecopyfolder = NULL;
963         gchar *replymessageid = NULL;
964         gchar *fwdmessageid = NULL;
965         gchar buf[BUFFSIZE];
966         gint hnum;
967         PrefsAccount *mailac = NULL, *newsac = NULL;
968         int local = 0;
969
970         g_return_val_if_fail(file != NULL, -1);
971
972         if ((fp = fopen(file, "rb")) == NULL) {
973                 FILE_OP_ERROR(file, "fopen");
974                 return -1;
975         }
976
977         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
978                != -1) {
979                 gchar *p = buf + strlen(qentry[hnum].name);
980
981                 switch (hnum) {
982                 case Q_SENDER:
983                         if (!from) from = g_strdup(p);
984                         break;
985                 case Q_SMTPSERVER:
986                         if (!smtpserver) smtpserver = g_strdup(p);
987                         break;
988                 case Q_RECIPIENTS:
989                         to_list = address_list_append(to_list, p);
990                         break;
991                 case Q_NEWSGROUPS:
992                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
993                         break;
994                 case Q_MAIL_ACCOUNT_ID:
995                         mailac = account_find_from_id(atoi(p));
996                         break;
997                 case Q_NEWS_ACCOUNT_ID:
998                         newsac = account_find_from_id(atoi(p));
999                         break;
1000                 case Q_SAVE_COPY_FOLDER:
1001                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1002                         break;
1003                 case Q_REPLY_MESSAGE_ID:
1004                         if (!replymessageid) replymessageid = g_strdup(p);
1005                         break;
1006                 case Q_FWD_MESSAGE_ID:
1007                         if (!fwdmessageid) fwdmessageid = g_strdup(p);
1008                         break;
1009                 }
1010         }
1011         filepos = ftell(fp);
1012
1013         if (to_list) {
1014                 debug_print("Sending message by mail\n");
1015                 if (!from) {
1016                         g_warning("Queued message header is broken.\n");
1017                         mailval = -1;
1018                 } else if (mailac && mailac->use_mail_command &&
1019                            mailac->mail_command && (* mailac->mail_command)) {
1020                         mailval = send_message_local(mailac->mail_command, fp);
1021                         local = 1;
1022                 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1023                         mailval = send_message_local(prefs_common.extsend_cmd, fp);
1024                         local = 1;
1025                 } else {
1026                         if (!mailac) {
1027                                 mailac = account_find_from_smtp_server(from, smtpserver);
1028                                 if (!mailac) {
1029                                         g_warning("Account not found. "
1030                                                     "Using current account...\n");
1031                                         mailac = cur_account;
1032                                 }
1033                         }
1034
1035                         if (mailac)
1036                                 mailval = send_message_smtp(mailac, to_list, fp);
1037                         else {
1038                                 PrefsAccount tmp_ac;
1039
1040                                 g_warning("Account not found.\n");
1041
1042                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1043                                 tmp_ac.address = from;
1044                                 tmp_ac.smtp_server = smtpserver;
1045                                 tmp_ac.smtpport = SMTP_PORT;
1046                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1047                         }
1048                 }
1049                 if (mailval < 0) {
1050                         if (!local)
1051                                 alertpanel_error_log(
1052                                         _("Error occurred while sending the message to `%s'."),
1053                                         mailac ? mailac->smtp_server : smtpserver);
1054                         else
1055                                 alertpanel_error_log(
1056                                         _("Error occurred while sending the message with command `%s'."),
1057                                         (mailac && mailac->use_mail_command && 
1058                                          mailac->mail_command && (*mailac->mail_command)) ? 
1059                                                 mailac->mail_command : prefs_common.extsend_cmd);
1060                 }
1061         }
1062
1063         fseek(fp, filepos, SEEK_SET);
1064         if (newsgroup_list && (newsval == 0)) {
1065                 Folder *folder;
1066                 gchar *tmp = NULL;
1067                 FILE *tmpfp;
1068
1069                 /* write to temporary file */
1070                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1071                             G_DIR_SEPARATOR, (gint)file);
1072                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1073                         FILE_OP_ERROR(tmp, "fopen");
1074                         newsval = -1;
1075                         alertpanel_error(_("Could not create temporary file for news sending."));
1076                 } else {
1077                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1078                                 FILE_OP_ERROR(tmp, "chmod");
1079                                 g_warning("can't change file mode\n");
1080                         }
1081
1082                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1083                                 if (fputs(buf, tmpfp) == EOF) {
1084                                         FILE_OP_ERROR(tmp, "fputs");
1085                                         newsval = -1;
1086                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1087                                 }
1088                         }
1089                         fclose(tmpfp);
1090
1091                         if (newsval == 0) {
1092                                 debug_print("Sending message by news\n");
1093
1094                                 folder = FOLDER(newsac->folder);
1095
1096                                 newsval = news_post(folder, tmp);
1097                                 if (newsval < 0) {
1098                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1099                                                  newsac->nntp_server);
1100                                 }
1101                         }
1102                         unlink(tmp);
1103                 }
1104                 g_free(tmp);
1105         }
1106
1107         slist_free_strings(to_list);
1108         g_slist_free(to_list);
1109         slist_free_strings(newsgroup_list);
1110         g_slist_free(newsgroup_list);
1111         g_free(from);
1112         g_free(smtpserver);
1113         fclose(fp);
1114
1115         /* save message to outbox */
1116         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1117                 FolderItem *outbox;
1118
1119                 debug_print("saving sent message...\n");
1120
1121                 outbox = folder_find_item_from_identifier(savecopyfolder);
1122                 if (!outbox)
1123                         outbox = folder_get_default_outbox();
1124
1125                 procmsg_save_to_outbox(outbox, file, TRUE);
1126         }
1127
1128         if (replymessageid != NULL || fwdmessageid != NULL) {
1129                 gchar **tokens;
1130                 FolderItem *item;
1131                 
1132                 if (replymessageid != NULL)
1133                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1134                 else
1135                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1136                 item = folder_find_item_from_identifier(tokens[0]);
1137                 if (item != NULL) {
1138                         MsgInfo *msginfo;
1139                         
1140                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1141                         if ((msginfo != NULL) && (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1142                                 procmsg_msginfo_free(msginfo);
1143                                 msginfo = NULL;
1144                         }
1145                         
1146                         if (msginfo == NULL) {
1147                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1148                         }
1149                         
1150                         if (msginfo != NULL) {
1151                                 if (replymessageid != NULL) {
1152                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1153                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1154                                 } 
1155                                 else {
1156                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1157                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1158                                 }
1159                                 procmsg_msginfo_free(msginfo);
1160                         }
1161                 }
1162                 g_strfreev(tokens);
1163         }
1164
1165         g_free(savecopyfolder);
1166         g_free(replymessageid);
1167         g_free(fwdmessageid);
1168         
1169         return (newsval != 0 ? newsval : mailval);
1170 }
1171
1172 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1173 {
1174         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1175
1176         /* NEW flag */
1177         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1178                 item->new_msgs++;
1179         }
1180
1181         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1182                 item->new_msgs--;
1183         }
1184
1185         /* UNREAD flag */
1186         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1187                 item->unread_msgs++;
1188                 if (procmsg_msg_has_marked_parent(msginfo))
1189                         item->unreadmarked_msgs++;
1190         }
1191
1192         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1193                 item->unread_msgs--;
1194                 if (procmsg_msg_has_marked_parent(msginfo))
1195                         item->unreadmarked_msgs--;
1196         }
1197         
1198         /* MARK flag */
1199         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1200                 procmsg_update_unread_children(msginfo, TRUE);
1201         }
1202
1203         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1204                 procmsg_update_unread_children(msginfo, FALSE);
1205         }
1206 }
1207
1208 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1209 {
1210         FolderItem *item;
1211         MsgInfoUpdate msginfo_update;
1212         MsgPermFlags perm_flags_new, perm_flags_old;
1213
1214         g_return_if_fail(msginfo != NULL);
1215         item = msginfo->folder;
1216         g_return_if_fail(item != NULL);
1217         
1218         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1219
1220         /* Perm Flags handling */
1221         perm_flags_old = msginfo->flags.perm_flags;
1222         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1223         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1224                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1225         }
1226
1227         if (perm_flags_old != perm_flags_new) {
1228                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1229
1230                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1231
1232                 msginfo_update.msginfo = msginfo;
1233                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1234                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1235         }
1236
1237         /* Tmp flags hanlding */
1238         msginfo->flags.tmp_flags |= tmp_flags;
1239 }
1240
1241 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1242 {
1243         FolderItem *item;
1244         MsgInfoUpdate msginfo_update;
1245         MsgPermFlags perm_flags_new, perm_flags_old;
1246
1247         g_return_if_fail(msginfo != NULL);
1248         item = msginfo->folder;
1249         g_return_if_fail(item != NULL);
1250         
1251         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1252
1253         /* Perm Flags handling */
1254         perm_flags_old = msginfo->flags.perm_flags;
1255         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1256         
1257         if (perm_flags_old != perm_flags_new) {
1258                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1259
1260                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1261
1262                 msginfo_update.msginfo = msginfo;
1263                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1264                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1265         }
1266
1267         /* Tmp flags hanlding */
1268         msginfo->flags.tmp_flags &= ~tmp_flags;
1269 }
1270
1271 /*!
1272  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1273  *
1274  *\param        info Current message
1275  *\param        perm_flags Flags to be checked
1276  *\param        parentmsgs Hash of prior msgs to avoid loops
1277  *
1278  *\return       gboolean TRUE if perm_flags are found
1279  */
1280 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1281                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1282 {
1283         MsgInfo *tmp;
1284
1285         g_return_val_if_fail(info != NULL, FALSE);
1286
1287         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1288                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1289                                 info->inreplyto);
1290                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1291                         procmsg_msginfo_free(tmp);
1292                         return TRUE;
1293                 } else if (tmp != NULL) {
1294                         gboolean result;
1295
1296                         if (g_hash_table_lookup(parentmsgs, info)) {
1297                                 debug_print("loop detected: %s%c%d\n",
1298                                         folder_item_get_path(info->folder),
1299                                         G_DIR_SEPARATOR, info->msgnum);
1300                                 result = FALSE;
1301                         } else {
1302                                 g_hash_table_insert(parentmsgs, info, "1");
1303                                 result = procmsg_msg_has_flagged_parent_real(
1304                                     tmp, perm_flags, parentmsgs);
1305                         }
1306                         procmsg_msginfo_free(tmp);
1307                         return result;
1308                 } else {
1309                         return FALSE;
1310                 }
1311         } else
1312                 return FALSE;
1313 }
1314
1315 /*!
1316  *\brief        Callback for cleaning up hash of parentmsgs
1317  */
1318 gboolean parentmsgs_hash_remove(gpointer key,
1319                             gpointer value,
1320                             gpointer user_data)
1321 {
1322         return TRUE;
1323 }
1324
1325 /*!
1326  *\brief        Set up list of parentmsgs
1327  *              See procmsg_msg_has_flagged_parent_real()
1328  */
1329 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1330 {
1331         gboolean result;
1332         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1333
1334         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1335         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1336         g_hash_table_destroy(parentmsgs);
1337         return result;
1338 }
1339
1340 /*!
1341  *\brief        Check if msgs prior in thread are marked
1342  *              See procmsg_msg_has_flagged_parent_real()
1343  */
1344 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1345 {
1346         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1347 }
1348
1349
1350 GSList *procmsg_find_children_func(MsgInfo *info, 
1351                                    GSList *children, GSList *all)
1352 {
1353         GSList *cur;
1354
1355         g_return_val_if_fail(info!=NULL, children);
1356         if (info->msgid == NULL)
1357                 return children;
1358
1359         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1360                 MsgInfo *tmp = (MsgInfo *)cur->data;
1361                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1362                         /* Check if message is already in the list */
1363                         if ((children == NULL) || 
1364                             (g_slist_index(children, tmp) == -1)) {
1365                                 children = g_slist_prepend(children,
1366                                                 procmsg_msginfo_new_ref(tmp));
1367                                 children = procmsg_find_children_func(tmp, 
1368                                                         children, 
1369                                                         all);
1370                         }
1371                 }
1372         }
1373         return children;
1374 }
1375
1376 GSList *procmsg_find_children (MsgInfo *info)
1377 {
1378         GSList *children;
1379         GSList *all, *cur;
1380
1381         g_return_val_if_fail(info!=NULL, NULL);
1382         all = folder_item_get_msg_list(info->folder);
1383         children = procmsg_find_children_func(info, NULL, all);
1384         if (children != NULL) {
1385                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1386                         /* this will not free the used pointers
1387                            created with procmsg_msginfo_new_ref */
1388                         procmsg_msginfo_free((MsgInfo *)cur->data);
1389                 }
1390         }
1391         g_slist_free(all);
1392
1393         return children;
1394 }
1395
1396 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1397 {
1398         GSList *children = procmsg_find_children(info);
1399         GSList *cur;
1400         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1401                 MsgInfo *tmp = (MsgInfo *)cur->data;
1402                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1403                         if(newly_marked) 
1404                                 info->folder->unreadmarked_msgs++;
1405                         else
1406                                 info->folder->unreadmarked_msgs--;
1407                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1408                 }
1409                 procmsg_msginfo_free(tmp);
1410         }
1411         g_slist_free(children);
1412 }
1413
1414 /**
1415  * Set the destination folder for a copy or move operation
1416  *
1417  * \param msginfo The message which's destination folder is changed
1418  * \param to_folder The destination folder for the operation
1419  */
1420 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1421 {
1422         if(msginfo->to_folder != NULL) {
1423                 msginfo->to_folder->op_count--;
1424                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1425         }
1426         msginfo->to_folder = to_folder;
1427         if(to_folder != NULL) {
1428                 to_folder->op_count++;
1429                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1430         }
1431 }
1432
1433 /**
1434  * Apply filtering actions to the msginfo
1435  *
1436  * \param msginfo The MsgInfo describing the message that should be filtered
1437  * \return TRUE if the message was moved and MsgInfo is now invalid,
1438  *         FALSE otherwise
1439  */
1440 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1441 {
1442         MailFilteringData mail_filtering_data;
1443                         
1444         mail_filtering_data.msginfo = msginfo;                  
1445         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1446                 return TRUE;
1447
1448         /* filter if enabled in prefs or move to inbox if not */
1449         if((global_processing != NULL) &&
1450            filter_message_by_msginfo(global_processing, msginfo))
1451                 return TRUE;
1452
1453         return FALSE;
1454 }