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