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