2004-09-08 [colin] 0.9.12cvs99.1
[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
854         MEMBCOPY(flags);
855
856         MEMBDUP(fromname);
857
858         MEMBDUP(date);
859         MEMBDUP(from);
860         MEMBDUP(to);
861         MEMBDUP(cc);
862         MEMBDUP(newsgroups);
863         MEMBDUP(subject);
864         MEMBDUP(msgid);
865         MEMBDUP(inreplyto);
866         MEMBDUP(xref);
867
868         MEMBCOPY(folder);
869         MEMBCOPY(to_folder);
870
871         MEMBDUP(xface);
872         MEMBDUP(dispositionnotificationto);
873         MEMBDUP(returnreceiptto);
874         MEMBDUP(references);
875
876         MEMBCOPY(score);
877         MEMBCOPY(threadscore);
878         MEMBDUP(plaintext_file);
879
880         return newmsginfo;
881 }
882
883 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
884 {
885         MsgInfo *full_msginfo;
886         gchar *file;
887
888         if (msginfo == NULL) return NULL;
889
890         file = procmsg_get_message_file(msginfo);
891         if (!file) {
892                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
893                 return NULL;
894         }
895
896         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
897         g_free(file);
898         if (!full_msginfo) return NULL;
899
900         /* CLAWS: make sure we add the missing members; see: 
901          * procheader.c::procheader_get_headernames() */
902         if (!msginfo->xface)
903                 msginfo->xface = g_strdup(full_msginfo->xface);
904         if (!msginfo->dispositionnotificationto)
905                 msginfo->dispositionnotificationto = 
906                         g_strdup(full_msginfo->dispositionnotificationto);
907         if (!msginfo->returnreceiptto)
908                 msginfo->returnreceiptto = g_strdup
909                         (full_msginfo->returnreceiptto);
910         if (!msginfo->partial_recv && full_msginfo->partial_recv)
911                 msginfo->partial_recv = g_strdup
912                         (full_msginfo->partial_recv);
913         msginfo->total_size = full_msginfo->total_size;
914         if (!msginfo->account_server && full_msginfo->account_server)
915                 msginfo->account_server = g_strdup
916                         (full_msginfo->account_server);
917         if (!msginfo->account_login && full_msginfo->account_login)
918                 msginfo->account_login = g_strdup
919                         (full_msginfo->account_login);
920         msginfo->planned_download = full_msginfo->planned_download;
921         procmsg_msginfo_free(full_msginfo);
922
923         return procmsg_msginfo_new_ref(msginfo);
924 }
925
926 void procmsg_msginfo_free(MsgInfo *msginfo)
927 {
928         if (msginfo == NULL) return;
929
930         msginfo->refcnt--;
931         if (msginfo->refcnt > 0)
932                 return;
933
934         if (msginfo->to_folder) {
935                 msginfo->to_folder->op_count--;
936                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
937         }
938
939         g_free(msginfo->fromspace);
940         g_free(msginfo->references);
941         g_free(msginfo->returnreceiptto);
942         g_free(msginfo->dispositionnotificationto);
943         g_free(msginfo->xface);
944
945         g_free(msginfo->fromname);
946
947         g_free(msginfo->date);
948         g_free(msginfo->from);
949         g_free(msginfo->to);
950         g_free(msginfo->cc);
951         g_free(msginfo->newsgroups);
952         g_free(msginfo->subject);
953         g_free(msginfo->msgid);
954         g_free(msginfo->inreplyto);
955         g_free(msginfo->xref);
956
957         g_free(msginfo->partial_recv);
958         g_free(msginfo->account_server);
959         g_free(msginfo->account_login);
960         
961         g_free(msginfo->plaintext_file);
962
963         g_free(msginfo);
964 }
965
966 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
967 {
968         guint memusage = 0;
969         
970         memusage += sizeof(MsgInfo);
971         if (msginfo->fromname)
972                 memusage += strlen(msginfo->fromname);
973         if (msginfo->date)
974                 memusage += strlen(msginfo->date);
975         if (msginfo->from)
976                 memusage += strlen(msginfo->from);
977         if (msginfo->to)
978                 memusage += strlen(msginfo->to);
979         if (msginfo->cc)
980                 memusage += strlen(msginfo->cc);
981         if (msginfo->newsgroups)
982                 memusage += strlen(msginfo->newsgroups);
983         if (msginfo->subject)
984                 memusage += strlen(msginfo->subject);
985         if (msginfo->msgid)
986                 memusage += strlen(msginfo->msgid);
987         if (msginfo->inreplyto)
988                 memusage += strlen(msginfo->inreplyto);
989         if (msginfo->xface)
990                 memusage += strlen(msginfo->xface);
991         if (msginfo->dispositionnotificationto)
992                 memusage += strlen(msginfo->dispositionnotificationto);
993         if (msginfo->returnreceiptto)
994                 memusage += strlen(msginfo->returnreceiptto);
995         if (msginfo->references)
996                 memusage += strlen(msginfo->references);
997         if (msginfo->fromspace)
998                 memusage += strlen(msginfo->fromspace);
999
1000         return memusage;
1001 }
1002
1003 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1004 {
1005         const MsgInfo *msginfo1 = a;
1006         const MsgInfo *msginfo2 = b;
1007
1008         if (!msginfo1)
1009                 return -1;
1010         if (!msginfo2)
1011                 return -1;
1012
1013         return msginfo1->msgnum - msginfo2->msgnum;
1014 }
1015
1016 enum
1017 {
1018         Q_SENDER           = 0,
1019         Q_SMTPSERVER       = 1,
1020         Q_RECIPIENTS       = 2,
1021         Q_NEWSGROUPS       = 3,
1022         Q_MAIL_ACCOUNT_ID  = 4,
1023         Q_NEWS_ACCOUNT_ID  = 5,
1024         Q_SAVE_COPY_FOLDER = 6,
1025         Q_REPLY_MESSAGE_ID = 7,
1026         Q_FWD_MESSAGE_ID   = 8
1027 };
1028
1029 gint procmsg_send_message_queue(const gchar *file)
1030 {
1031         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1032                                        {"SSV:",  NULL, FALSE},
1033                                        {"R:",    NULL, FALSE},
1034                                        {"NG:",   NULL, FALSE},
1035                                        {"MAID:", NULL, FALSE},
1036                                        {"NAID:", NULL, FALSE},
1037                                        {"SCF:",  NULL, FALSE},
1038                                        {"RMID:", NULL, FALSE},
1039                                        {"FMID:", NULL, FALSE},
1040                                        {NULL,    NULL, FALSE}};
1041         FILE *fp;
1042         gint filepos;
1043         gint mailval = 0, newsval = 0;
1044         gchar *from = NULL;
1045         gchar *smtpserver = NULL;
1046         GSList *to_list = NULL;
1047         GSList *newsgroup_list = NULL;
1048         gchar *savecopyfolder = NULL;
1049         gchar *replymessageid = NULL;
1050         gchar *fwdmessageid = NULL;
1051         gchar buf[BUFFSIZE];
1052         gint hnum;
1053         PrefsAccount *mailac = NULL, *newsac = NULL;
1054         int local = 0;
1055
1056         g_return_val_if_fail(file != NULL, -1);
1057
1058         if ((fp = fopen(file, "rb")) == NULL) {
1059                 FILE_OP_ERROR(file, "fopen");
1060                 return -1;
1061         }
1062
1063         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1064                != -1) {
1065                 gchar *p = buf + strlen(qentry[hnum].name);
1066
1067                 switch (hnum) {
1068                 case Q_SENDER:
1069                         if (!from) from = g_strdup(p);
1070                         break;
1071                 case Q_SMTPSERVER:
1072                         if (!smtpserver) smtpserver = g_strdup(p);
1073                         break;
1074                 case Q_RECIPIENTS:
1075                         to_list = address_list_append(to_list, p);
1076                         break;
1077                 case Q_NEWSGROUPS:
1078                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1079                         break;
1080                 case Q_MAIL_ACCOUNT_ID:
1081                         mailac = account_find_from_id(atoi(p));
1082                         break;
1083                 case Q_NEWS_ACCOUNT_ID:
1084                         newsac = account_find_from_id(atoi(p));
1085                         break;
1086                 case Q_SAVE_COPY_FOLDER:
1087                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1088                         break;
1089                 case Q_REPLY_MESSAGE_ID:
1090                         if (!replymessageid) replymessageid = g_strdup(p);
1091                         break;
1092                 case Q_FWD_MESSAGE_ID:
1093                         if (!fwdmessageid) fwdmessageid = g_strdup(p);
1094                         break;
1095                 }
1096         }
1097         filepos = ftell(fp);
1098
1099         if (to_list) {
1100                 debug_print("Sending message by mail\n");
1101                 if (!from) {
1102                         g_warning("Queued message header is broken.\n");
1103                         mailval = -1;
1104                 } else if (mailac && mailac->use_mail_command &&
1105                            mailac->mail_command && (* mailac->mail_command)) {
1106                         mailval = send_message_local(mailac->mail_command, fp);
1107                         local = 1;
1108                 } else {
1109                         if (!mailac) {
1110                                 mailac = account_find_from_smtp_server(from, smtpserver);
1111                                 if (!mailac) {
1112                                         g_warning("Account not found. "
1113                                                     "Using current account...\n");
1114                                         mailac = cur_account;
1115                                 }
1116                         }
1117
1118                         if (mailac)
1119                                 mailval = send_message_smtp(mailac, to_list, fp);
1120                         else {
1121                                 PrefsAccount tmp_ac;
1122
1123                                 g_warning("Account not found.\n");
1124
1125                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1126                                 tmp_ac.address = from;
1127                                 tmp_ac.smtp_server = smtpserver;
1128                                 tmp_ac.smtpport = SMTP_PORT;
1129                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1130                         }
1131                 }
1132         }
1133
1134         fseek(fp, filepos, SEEK_SET);
1135         if (newsgroup_list && (newsval == 0)) {
1136                 Folder *folder;
1137                 gchar *tmp = NULL;
1138                 FILE *tmpfp;
1139
1140                 /* write to temporary file */
1141                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1142                             G_DIR_SEPARATOR, (gint)file);
1143                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1144                         FILE_OP_ERROR(tmp, "fopen");
1145                         newsval = -1;
1146                         alertpanel_error(_("Could not create temporary file for news sending."));
1147                 } else {
1148                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1149                                 FILE_OP_ERROR(tmp, "chmod");
1150                                 g_warning("can't change file mode\n");
1151                         }
1152
1153                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1154                                 if (fputs(buf, tmpfp) == EOF) {
1155                                         FILE_OP_ERROR(tmp, "fputs");
1156                                         newsval = -1;
1157                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1158                                 }
1159                         }
1160                         fclose(tmpfp);
1161
1162                         if (newsval == 0) {
1163                                 debug_print("Sending message by news\n");
1164
1165                                 folder = FOLDER(newsac->folder);
1166
1167                                 newsval = news_post(folder, tmp);
1168                                 if (newsval < 0) {
1169                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1170                                                  newsac->nntp_server);
1171                                 }
1172                         }
1173                         unlink(tmp);
1174                 }
1175                 g_free(tmp);
1176         }
1177
1178         slist_free_strings(to_list);
1179         g_slist_free(to_list);
1180         slist_free_strings(newsgroup_list);
1181         g_slist_free(newsgroup_list);
1182         g_free(from);
1183         g_free(smtpserver);
1184         fclose(fp);
1185
1186         /* save message to outbox */
1187         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1188                 FolderItem *outbox;
1189
1190                 debug_print("saving sent message...\n");
1191
1192                 outbox = folder_find_item_from_identifier(savecopyfolder);
1193                 if (!outbox)
1194                         outbox = folder_get_default_outbox();
1195
1196                 procmsg_save_to_outbox(outbox, file, TRUE);
1197         }
1198
1199         if (replymessageid != NULL || fwdmessageid != NULL) {
1200                 gchar **tokens;
1201                 FolderItem *item;
1202                 
1203                 if (replymessageid != NULL)
1204                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1205                 else
1206                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1207                 item = folder_find_item_from_identifier(tokens[0]);
1208
1209                 /* check if queued message has valid folder and message id */
1210                 if (item != NULL && tokens[2] != NULL) {
1211                         MsgInfo *msginfo;
1212                         
1213                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1214                 
1215                         /* check if referring message exists and has a message id */
1216                         if ((msginfo != NULL) && 
1217                             (msginfo->msgid != NULL) &&
1218                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1219                                 procmsg_msginfo_free(msginfo);
1220                                 msginfo = NULL;
1221                         }
1222                         
1223                         if (msginfo == NULL) {
1224                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1225                         }
1226                         
1227                         if (msginfo != NULL) {
1228                                 if (replymessageid != NULL) {
1229                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1230                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1231                                 }  else {
1232                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1233                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1234                                 }
1235                                 procmsg_msginfo_free(msginfo);
1236                         }
1237                 }
1238                 g_strfreev(tokens);
1239         }
1240
1241         g_free(savecopyfolder);
1242         g_free(replymessageid);
1243         g_free(fwdmessageid);
1244         
1245         return (newsval != 0 ? newsval : mailval);
1246 }
1247
1248 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1249 {
1250         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1251
1252         /* NEW flag */
1253         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1254                 item->new_msgs++;
1255         }
1256
1257         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1258                 item->new_msgs--;
1259         }
1260
1261         /* UNREAD flag */
1262         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1263                 item->unread_msgs++;
1264                 if (procmsg_msg_has_marked_parent(msginfo))
1265                         item->unreadmarked_msgs++;
1266         }
1267
1268         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1269                 item->unread_msgs--;
1270                 if (procmsg_msg_has_marked_parent(msginfo))
1271                         item->unreadmarked_msgs--;
1272         }
1273         
1274         /* MARK flag */
1275         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1276                 procmsg_update_unread_children(msginfo, TRUE);
1277         }
1278
1279         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1280                 procmsg_update_unread_children(msginfo, FALSE);
1281         }
1282 }
1283
1284 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1285 {
1286         FolderItem *item;
1287         MsgInfoUpdate msginfo_update;
1288         MsgPermFlags perm_flags_new, perm_flags_old;
1289         MsgTmpFlags tmp_flags_old;
1290
1291         g_return_if_fail(msginfo != NULL);
1292         item = msginfo->folder;
1293         g_return_if_fail(item != NULL);
1294         
1295         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1296
1297         /* Perm Flags handling */
1298         perm_flags_old = msginfo->flags.perm_flags;
1299         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1300         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1301                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1302         }
1303
1304         if (perm_flags_old != perm_flags_new) {
1305                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1306
1307                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1308
1309         }
1310
1311         /* Tmp flags handling */
1312         tmp_flags_old = msginfo->flags.tmp_flags;
1313         msginfo->flags.tmp_flags |= tmp_flags;
1314
1315         /* update notification */
1316         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1317                 msginfo_update.msginfo = msginfo;
1318                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1319                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1320                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1321         }
1322 }
1323
1324 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1325 {
1326         FolderItem *item;
1327         MsgInfoUpdate msginfo_update;
1328         MsgPermFlags perm_flags_new, perm_flags_old;
1329         MsgTmpFlags tmp_flags_old;
1330
1331         g_return_if_fail(msginfo != NULL);
1332         item = msginfo->folder;
1333         g_return_if_fail(item != NULL);
1334         
1335         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1336
1337         /* Perm Flags handling */
1338         perm_flags_old = msginfo->flags.perm_flags;
1339         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1340         
1341         if (perm_flags_old != perm_flags_new) {
1342                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1343
1344                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1345
1346                 msginfo_update.msginfo = msginfo;
1347                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1348                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1349                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1350         }
1351
1352         /* Tmp flags hanlding */
1353         tmp_flags_old = msginfo->flags.tmp_flags;
1354         msginfo->flags.tmp_flags &= ~tmp_flags;
1355
1356         /* update notification */
1357         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1358                 msginfo_update.msginfo = msginfo;
1359                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1360                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1361                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1362         }
1363 }
1364
1365 /*!
1366  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1367  *
1368  *\param        info Current message
1369  *\param        perm_flags Flags to be checked
1370  *\param        parentmsgs Hash of prior msgs to avoid loops
1371  *
1372  *\return       gboolean TRUE if perm_flags are found
1373  */
1374 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1375                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1376 {
1377         MsgInfo *tmp;
1378
1379         g_return_val_if_fail(info != NULL, FALSE);
1380
1381         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1382                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1383                                 info->inreplyto);
1384                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1385                         procmsg_msginfo_free(tmp);
1386                         return TRUE;
1387                 } else if (tmp != NULL) {
1388                         gboolean result;
1389
1390                         if (g_hash_table_lookup(parentmsgs, info)) {
1391                                 debug_print("loop detected: %s%c%d\n",
1392                                         folder_item_get_path(info->folder),
1393                                         G_DIR_SEPARATOR, info->msgnum);
1394                                 result = FALSE;
1395                         } else {
1396                                 g_hash_table_insert(parentmsgs, info, "1");
1397                                 result = procmsg_msg_has_flagged_parent_real(
1398                                     tmp, perm_flags, parentmsgs);
1399                         }
1400                         procmsg_msginfo_free(tmp);
1401                         return result;
1402                 } else {
1403                         return FALSE;
1404                 }
1405         } else
1406                 return FALSE;
1407 }
1408
1409 /*!
1410  *\brief        Callback for cleaning up hash of parentmsgs
1411  */
1412 gboolean parentmsgs_hash_remove(gpointer key,
1413                             gpointer value,
1414                             gpointer user_data)
1415 {
1416         return TRUE;
1417 }
1418
1419 /*!
1420  *\brief        Set up list of parentmsgs
1421  *              See procmsg_msg_has_flagged_parent_real()
1422  */
1423 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1424 {
1425         gboolean result;
1426         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1427
1428         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1429         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1430         g_hash_table_destroy(parentmsgs);
1431         return result;
1432 }
1433
1434 /*!
1435  *\brief        Check if msgs prior in thread are marked
1436  *              See procmsg_msg_has_flagged_parent_real()
1437  */
1438 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1439 {
1440         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1441 }
1442
1443
1444 GSList *procmsg_find_children_func(MsgInfo *info, 
1445                                    GSList *children, GSList *all)
1446 {
1447         GSList *cur;
1448
1449         g_return_val_if_fail(info!=NULL, children);
1450         if (info->msgid == NULL)
1451                 return children;
1452
1453         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1454                 MsgInfo *tmp = (MsgInfo *)cur->data;
1455                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1456                         /* Check if message is already in the list */
1457                         if ((children == NULL) || 
1458                             (g_slist_index(children, tmp) == -1)) {
1459                                 children = g_slist_prepend(children,
1460                                                 procmsg_msginfo_new_ref(tmp));
1461                                 children = procmsg_find_children_func(tmp, 
1462                                                         children, 
1463                                                         all);
1464                         }
1465                 }
1466         }
1467         return children;
1468 }
1469
1470 GSList *procmsg_find_children (MsgInfo *info)
1471 {
1472         GSList *children;
1473         GSList *all, *cur;
1474
1475         g_return_val_if_fail(info!=NULL, NULL);
1476         all = folder_item_get_msg_list(info->folder);
1477         children = procmsg_find_children_func(info, NULL, all);
1478         if (children != NULL) {
1479                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1480                         /* this will not free the used pointers
1481                            created with procmsg_msginfo_new_ref */
1482                         procmsg_msginfo_free((MsgInfo *)cur->data);
1483                 }
1484         }
1485         g_slist_free(all);
1486
1487         return children;
1488 }
1489
1490 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1491 {
1492         GSList *children = procmsg_find_children(info);
1493         GSList *cur;
1494         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1495                 MsgInfo *tmp = (MsgInfo *)cur->data;
1496                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1497                         if(newly_marked) 
1498                                 info->folder->unreadmarked_msgs++;
1499                         else
1500                                 info->folder->unreadmarked_msgs--;
1501                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1502                 }
1503                 procmsg_msginfo_free(tmp);
1504         }
1505         g_slist_free(children);
1506 }
1507
1508 /**
1509  * Set the destination folder for a copy or move operation
1510  *
1511  * \param msginfo The message which's destination folder is changed
1512  * \param to_folder The destination folder for the operation
1513  */
1514 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1515 {
1516         if(msginfo->to_folder != NULL) {
1517                 msginfo->to_folder->op_count--;
1518                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1519         }
1520         msginfo->to_folder = to_folder;
1521         if(to_folder != NULL) {
1522                 to_folder->op_count++;
1523                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1524         }
1525 }
1526
1527 /**
1528  * Apply filtering actions to the msginfo
1529  *
1530  * \param msginfo The MsgInfo describing the message that should be filtered
1531  * \return TRUE if the message was moved and MsgInfo is now invalid,
1532  *         FALSE otherwise
1533  */
1534 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1535 {
1536         MailFilteringData mail_filtering_data;
1537                         
1538         mail_filtering_data.msginfo = msginfo;                  
1539         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1540                 return TRUE;
1541
1542         /* filter if enabled in prefs or move to inbox if not */
1543         if((filtering_rules != NULL) &&
1544            filter_message_by_msginfo(filtering_rules, msginfo))
1545                 return TRUE;
1546
1547         return FALSE;
1548 }