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