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