2005-03-18 [paul] 1.0.3cvs2.5
[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 <glib/gi18n.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27
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(FolderItem *trash)
604 {
605         if (trash && trash->total_msgs > 0) {
606                 GSList *mlist = folder_item_get_msg_list(trash);
607                 GSList *cur;
608                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
609                         MsgInfo * msginfo = (MsgInfo *) cur->data;
610                         partial_mark_for_delete(msginfo);
611                         procmsg_msginfo_free(msginfo);
612                 }
613
614                 folder_item_remove_all_msg(trash);
615         }
616 }
617
618 void procmsg_empty_all_trash(void)
619 {
620         FolderItem *trash;
621         GList *cur;
622
623         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
624                 trash = FOLDER(cur->data)->trash;
625                 procmsg_empty_trash(trash);
626         }
627 }
628
629 /*!
630  *\brief        Send messages in queue
631  *
632  *\param        queue Queue folder to process
633  *\param        save_msgs Unused
634  *
635  *\return       Number of messages sent, negative if an error occurred
636  *              positive if no error occurred
637  */
638 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
639 {
640         gint sent = 0, err = 0;
641         GSList *list, *elem;
642
643         if (!queue)
644                 queue = folder_get_default_queue();
645         g_return_val_if_fail(queue != NULL, -1);
646
647         folder_item_scan(queue);
648         list = folder_item_get_msg_list(queue);
649
650         for (elem = list; elem != NULL; elem = elem->next) {
651                 gchar *file;
652                 MsgInfo *msginfo;
653                 
654                 msginfo = (MsgInfo *)(elem->data);
655                 if (!MSG_IS_LOCKED(msginfo->flags)) {
656                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
657                         if (file) {
658                                 if (procmsg_send_message_queue(file) < 0) {
659                                         g_warning("Sending queued message %d failed.\n", 
660                                                   msginfo->msgnum);
661                                         err++;
662                                 } else {
663                                         /* CLAWS: 
664                                          * We save in procmsg_send_message_queue because
665                                          * we need the destination folder from the queue
666                                          * header
667                                                         
668                                         if (save_msgs)
669                                                 procmsg_save_to_outbox
670                                                         (queue->folder->outbox,
671                                                          file, TRUE);
672                                          */
673                                         sent++; 
674                                         folder_item_remove_msg(queue, msginfo->msgnum);
675                                 }
676                                 g_free(file);
677                         }
678                 }
679                 /* FIXME: supposedly if only one message is locked, and queue
680                  * is being flushed, the following free says something like 
681                  * "freeing msg ## in folder (nil)". */
682                 procmsg_msginfo_free(msginfo);
683         }
684
685         return (err != 0 ? -err : sent);
686 }
687
688 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
689 {
690         FILE *fp, *outfp;
691         gchar buf[BUFFSIZE];
692         
693         if ((fp = fopen(in, "rb")) == NULL) {
694                 FILE_OP_ERROR(in, "fopen");
695                 return -1;
696         }
697         if ((outfp = fopen(out, "wb")) == NULL) {
698                 FILE_OP_ERROR(out, "fopen");
699                 fclose(fp);
700                 return -1;
701         }
702         while (fgets(buf, sizeof(buf), fp) != NULL)
703                 if (buf[0] == '\r' || buf[0] == '\n') break;
704         while (fgets(buf, sizeof(buf), fp) != NULL)
705                 fputs(buf, outfp);
706         fclose(outfp);
707         fclose(fp);
708         return 0;
709
710 }
711 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
712                             gboolean is_queued)
713 {
714         gint num;
715         MsgInfo *msginfo, *tmp_msginfo;
716         MsgFlags flag = {0, 0};
717
718         debug_print("saving sent message...\n");
719
720         if (!outbox)
721                 outbox = folder_get_default_outbox();
722         g_return_val_if_fail(outbox != NULL, -1);
723
724         /* remove queueing headers */
725         if (is_queued) {
726                 gchar tmp[MAXPATHLEN + 1];
727
728                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
729                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
730                 
731                 if (procmsg_remove_special_headers(file, tmp) !=0)
732                         return -1;
733
734                 folder_item_scan(outbox);
735                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
736                         g_warning("can't save message\n");
737                         unlink(tmp);
738                         return -1;
739                 }
740         } else {
741                 folder_item_scan(outbox);
742                 if ((num = folder_item_add_msg
743                         (outbox, file, &flag, FALSE)) < 0) {
744                         g_warning("can't save message\n");
745                         return -1;
746                 }
747                 return -1;
748         }
749         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
750         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
751         if (msginfo != NULL) {
752                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
753                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
754                 /* tmp_msginfo == msginfo */
755                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
756                     msginfo->returnreceiptto)) {
757                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
758                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
759                 }       
760         }
761
762         return 0;
763 }
764
765 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
766 {
767         static const gchar *def_cmd = "lpr %s";
768         static guint id = 0;
769         gchar *prtmp;
770         FILE *tmpfp, *prfp;
771         gchar buf[1024];
772         gchar *p;
773
774         g_return_if_fail(msginfo);
775
776         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
777                 g_warning("Can't get text part\n");
778                 return;
779         }
780
781         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
782                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
783
784         if ((prfp = fopen(prtmp, "wb")) == NULL) {
785                 FILE_OP_ERROR(prtmp, "fopen");
786                 g_free(prtmp);
787                 fclose(tmpfp);
788                 return;
789         }
790
791         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
792         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
793         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
794         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
795         if (msginfo->newsgroups)
796                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
797         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
798         fputc('\n', prfp);
799
800         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
801                 fputs(buf, prfp);
802
803         fclose(prfp);
804         fclose(tmpfp);
805
806         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
807             !strchr(p + 2, '%'))
808                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
809         else {
810                 if (cmdline)
811                         g_warning("Print command line is invalid: `%s'\n",
812                                   cmdline);
813                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
814         }
815
816         g_free(prtmp);
817
818         g_strchomp(buf);
819         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
820         system(buf);
821 }
822
823 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
824 {
825         msginfo->refcnt++;
826         
827         return msginfo;
828 }
829
830 MsgInfo *procmsg_msginfo_new(void)
831 {
832         MsgInfo *newmsginfo;
833
834         newmsginfo = g_new0(MsgInfo, 1);
835         newmsginfo->refcnt = 1;
836         
837         return newmsginfo;
838 }
839
840 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
841 {
842         MsgInfo *newmsginfo;
843
844         if (msginfo == NULL) return NULL;
845
846         newmsginfo = g_new0(MsgInfo, 1);
847
848         newmsginfo->refcnt = 1;
849
850 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
851 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
852                         g_strdup(msginfo->mmb) : NULL
853
854         MEMBCOPY(msgnum);
855         MEMBCOPY(size);
856         MEMBCOPY(mtime);
857         MEMBCOPY(date_t);
858
859         MEMBCOPY(flags);
860
861         MEMBDUP(fromname);
862
863         MEMBDUP(date);
864         MEMBDUP(from);
865         MEMBDUP(to);
866         MEMBDUP(cc);
867         MEMBDUP(newsgroups);
868         MEMBDUP(subject);
869         MEMBDUP(msgid);
870         MEMBDUP(inreplyto);
871         MEMBDUP(xref);
872
873         MEMBCOPY(folder);
874         MEMBCOPY(to_folder);
875
876         MEMBDUP(xface);
877         MEMBDUP(dispositionnotificationto);
878         MEMBDUP(returnreceiptto);
879         MEMBDUP(references);
880
881         MEMBCOPY(score);
882         MEMBCOPY(threadscore);
883         MEMBDUP(plaintext_file);
884
885         return newmsginfo;
886 }
887
888 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
889 {
890         MsgInfo *full_msginfo;
891         gchar *file;
892
893         if (msginfo == NULL) return NULL;
894
895         file = procmsg_get_message_file_path(msginfo);
896         if (!file || !is_file_exist(file)) {
897                 g_free(file);
898                 file = procmsg_get_message_file(msginfo);
899         }
900         if (!file || !is_file_exist(file)) {
901                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
902                 return NULL;
903         }
904
905         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
906         g_free(file);
907         if (!full_msginfo) return NULL;
908
909         /* CLAWS: make sure we add the missing members; see: 
910          * procheader.c::procheader_get_headernames() */
911         if (!msginfo->xface)
912                 msginfo->xface = g_strdup(full_msginfo->xface);
913         if (!msginfo->dispositionnotificationto)
914                 msginfo->dispositionnotificationto = 
915                         g_strdup(full_msginfo->dispositionnotificationto);
916         if (!msginfo->returnreceiptto)
917                 msginfo->returnreceiptto = g_strdup
918                         (full_msginfo->returnreceiptto);
919         if (!msginfo->partial_recv && full_msginfo->partial_recv)
920                 msginfo->partial_recv = g_strdup
921                         (full_msginfo->partial_recv);
922         msginfo->total_size = full_msginfo->total_size;
923         if (!msginfo->account_server && full_msginfo->account_server)
924                 msginfo->account_server = g_strdup
925                         (full_msginfo->account_server);
926         if (!msginfo->account_login && full_msginfo->account_login)
927                 msginfo->account_login = g_strdup
928                         (full_msginfo->account_login);
929         msginfo->planned_download = full_msginfo->planned_download;
930         procmsg_msginfo_free(full_msginfo);
931
932         return procmsg_msginfo_new_ref(msginfo);
933 }
934
935 void procmsg_msginfo_free(MsgInfo *msginfo)
936 {
937         if (msginfo == NULL) return;
938
939         msginfo->refcnt--;
940         if (msginfo->refcnt > 0)
941                 return;
942
943         if (msginfo->to_folder) {
944                 msginfo->to_folder->op_count--;
945                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
946         }
947
948         g_free(msginfo->fromspace);
949         g_free(msginfo->references);
950         g_free(msginfo->returnreceiptto);
951         g_free(msginfo->dispositionnotificationto);
952         g_free(msginfo->xface);
953
954         g_free(msginfo->fromname);
955
956         g_free(msginfo->date);
957         g_free(msginfo->from);
958         g_free(msginfo->to);
959         g_free(msginfo->cc);
960         g_free(msginfo->newsgroups);
961         g_free(msginfo->subject);
962         g_free(msginfo->msgid);
963         g_free(msginfo->inreplyto);
964         g_free(msginfo->xref);
965
966         g_free(msginfo->partial_recv);
967         g_free(msginfo->account_server);
968         g_free(msginfo->account_login);
969         
970         g_free(msginfo->plaintext_file);
971
972         g_free(msginfo);
973 }
974
975 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
976 {
977         guint memusage = 0;
978         
979         memusage += sizeof(MsgInfo);
980         if (msginfo->fromname)
981                 memusage += strlen(msginfo->fromname);
982         if (msginfo->date)
983                 memusage += strlen(msginfo->date);
984         if (msginfo->from)
985                 memusage += strlen(msginfo->from);
986         if (msginfo->to)
987                 memusage += strlen(msginfo->to);
988         if (msginfo->cc)
989                 memusage += strlen(msginfo->cc);
990         if (msginfo->newsgroups)
991                 memusage += strlen(msginfo->newsgroups);
992         if (msginfo->subject)
993                 memusage += strlen(msginfo->subject);
994         if (msginfo->msgid)
995                 memusage += strlen(msginfo->msgid);
996         if (msginfo->inreplyto)
997                 memusage += strlen(msginfo->inreplyto);
998         if (msginfo->xface)
999                 memusage += strlen(msginfo->xface);
1000         if (msginfo->dispositionnotificationto)
1001                 memusage += strlen(msginfo->dispositionnotificationto);
1002         if (msginfo->returnreceiptto)
1003                 memusage += strlen(msginfo->returnreceiptto);
1004         if (msginfo->references)
1005                 memusage += strlen(msginfo->references);
1006         if (msginfo->fromspace)
1007                 memusage += strlen(msginfo->fromspace);
1008
1009         return memusage;
1010 }
1011
1012 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1013 {
1014         const MsgInfo *msginfo1 = a;
1015         const MsgInfo *msginfo2 = b;
1016
1017         if (!msginfo1)
1018                 return -1;
1019         if (!msginfo2)
1020                 return -1;
1021
1022         return msginfo1->msgnum - msginfo2->msgnum;
1023 }
1024
1025 enum
1026 {
1027         Q_SENDER           = 0,
1028         Q_SMTPSERVER       = 1,
1029         Q_RECIPIENTS       = 2,
1030         Q_NEWSGROUPS       = 3,
1031         Q_MAIL_ACCOUNT_ID  = 4,
1032         Q_NEWS_ACCOUNT_ID  = 5,
1033         Q_SAVE_COPY_FOLDER = 6,
1034         Q_REPLY_MESSAGE_ID = 7,
1035         Q_FWD_MESSAGE_ID   = 8,
1036         Q_PRIVACY_SYSTEM   = 9,
1037         Q_ENCRYPT          = 10,
1038         Q_ENCRYPT_DATA     = 11,
1039 };
1040
1041 gint procmsg_send_message_queue(const gchar *file)
1042 {
1043         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1044                                        {"SSV:",  NULL, FALSE},
1045                                        {"R:",    NULL, FALSE},
1046                                        {"NG:",   NULL, FALSE},
1047                                        {"MAID:", NULL, FALSE},
1048                                        {"NAID:", NULL, FALSE},
1049                                        {"SCF:",  NULL, FALSE},
1050                                        {"RMID:", NULL, FALSE},
1051                                        {"FMID:", NULL, FALSE},
1052                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1053                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1054                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1055                                        {NULL,    NULL, FALSE}};
1056         FILE *fp;
1057         gint filepos;
1058         gint mailval = 0, newsval = 0;
1059         gchar *from = NULL;
1060         gchar *smtpserver = NULL;
1061         GSList *to_list = NULL;
1062         GSList *newsgroup_list = NULL;
1063         gchar *savecopyfolder = NULL;
1064         gchar *replymessageid = NULL;
1065         gchar *fwdmessageid = NULL;
1066         gchar *privacy_system = NULL;
1067         gboolean encrypt = FALSE;
1068         gchar *encrypt_data = NULL;
1069         gchar buf[BUFFSIZE];
1070         gint hnum;
1071         PrefsAccount *mailac = NULL, *newsac = NULL;
1072         gboolean save_clear_text = TRUE;
1073         gchar *tmp_enc_file = NULL;
1074
1075         int local = 0;
1076
1077         g_return_val_if_fail(file != NULL, -1);
1078
1079         if ((fp = fopen(file, "rb")) == NULL) {
1080                 FILE_OP_ERROR(file, "fopen");
1081                 return -1;
1082         }
1083
1084         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1085                != -1) {
1086                 gchar *p = buf + strlen(qentry[hnum].name);
1087
1088                 switch (hnum) {
1089                 case Q_SENDER:
1090                         if (from == NULL) 
1091                                 from = g_strdup(p);
1092                         break;
1093                 case Q_SMTPSERVER:
1094                         if (smtpserver == NULL) 
1095                                 smtpserver = g_strdup(p);
1096                         break;
1097                 case Q_RECIPIENTS:
1098                         to_list = address_list_append(to_list, p);
1099                         break;
1100                 case Q_NEWSGROUPS:
1101                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1102                         break;
1103                 case Q_MAIL_ACCOUNT_ID:
1104                         mailac = account_find_from_id(atoi(p));
1105                         break;
1106                 case Q_NEWS_ACCOUNT_ID:
1107                         newsac = account_find_from_id(atoi(p));
1108                         break;
1109                 case Q_SAVE_COPY_FOLDER:
1110                         if (savecopyfolder == NULL) 
1111                                 savecopyfolder = g_strdup(p);
1112                         break;
1113                 case Q_REPLY_MESSAGE_ID:
1114                         if (replymessageid == NULL) 
1115                                 replymessageid = g_strdup(p);
1116                         break;
1117                 case Q_FWD_MESSAGE_ID:
1118                         if (fwdmessageid == NULL) 
1119                                 fwdmessageid = g_strdup(p);
1120                         break;
1121                 case Q_PRIVACY_SYSTEM:
1122                         if (privacy_system == NULL) 
1123                                 privacy_system = g_strdup(p);
1124                         break;
1125                 case Q_ENCRYPT:
1126                         if (p[0] == '1') 
1127                                 encrypt = TRUE;
1128                         break;
1129                 case Q_ENCRYPT_DATA:
1130                         if (encrypt_data == NULL) 
1131                                 encrypt_data = g_strdup(p);
1132                         break;
1133                 }
1134         }
1135         filepos = ftell(fp);
1136
1137         if (encrypt) {
1138                 MimeInfo *mimeinfo;
1139
1140                 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1141
1142                 fclose(fp);
1143                 fp = NULL;
1144
1145                 mimeinfo = procmime_scan_queue_file(file);
1146                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1147                 || (fp = my_tmpfile()) == NULL
1148                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1149                         if (fp)
1150                                 fclose(fp);
1151                         procmime_mimeinfo_free_all(mimeinfo);
1152                         g_free(from);
1153                         g_free(smtpserver);
1154                         slist_free_strings(to_list);
1155                         g_slist_free(to_list);
1156                         slist_free_strings(newsgroup_list);
1157                         g_slist_free(newsgroup_list);
1158                         g_free(savecopyfolder);
1159                         g_free(replymessageid);
1160                         g_free(fwdmessageid);
1161                         g_free(privacy_system);
1162                         g_free(encrypt_data);
1163                         return -1;
1164                 }
1165                 
1166                 rewind(fp);
1167                 if (!save_clear_text) {
1168                         gchar *content = NULL;
1169                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1170                         if (tmpfp) {
1171                                 fclose(tmpfp);
1172
1173                                 content = file_read_stream_to_str(fp);
1174                                 rewind(fp);
1175
1176                                 str_write_to_file(content, tmp_enc_file);
1177                                 g_free(content);
1178                         } else {
1179                                 g_warning("couldn't get tempfile\n");
1180                         }
1181                 } 
1182                 
1183                 procmime_mimeinfo_free_all(mimeinfo);
1184                 
1185                 filepos = 0;
1186         }
1187
1188         if (to_list) {
1189                 debug_print("Sending message by mail\n");
1190                 if (!from) {
1191                         g_warning("Queued message header is broken.\n");
1192                         mailval = -1;
1193                 } else if (mailac && mailac->use_mail_command &&
1194                            mailac->mail_command && (* mailac->mail_command)) {
1195                         mailval = send_message_local(mailac->mail_command, fp);
1196                         local = 1;
1197                 } else {
1198                         if (!mailac) {
1199                                 mailac = account_find_from_smtp_server(from, smtpserver);
1200                                 if (!mailac) {
1201                                         g_warning("Account not found. "
1202                                                     "Using current account...\n");
1203                                         mailac = cur_account;
1204                                 }
1205                         }
1206
1207                         if (mailac)
1208                                 mailval = send_message_smtp(mailac, to_list, fp);
1209                         else {
1210                                 PrefsAccount tmp_ac;
1211
1212                                 g_warning("Account not found.\n");
1213
1214                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1215                                 tmp_ac.address = from;
1216                                 tmp_ac.smtp_server = smtpserver;
1217                                 tmp_ac.smtpport = SMTP_PORT;
1218                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1219                         }
1220                 }
1221         }
1222
1223         fseek(fp, filepos, SEEK_SET);
1224         if (newsgroup_list && (mailval == 0)) {
1225                 Folder *folder;
1226                 gchar *tmp = NULL;
1227                 FILE *tmpfp;
1228
1229                 /* write to temporary file */
1230                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1231                             G_DIR_SEPARATOR, (gint)file);
1232                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1233                         FILE_OP_ERROR(tmp, "fopen");
1234                         newsval = -1;
1235                         alertpanel_error(_("Could not create temporary file for news sending."));
1236                 } else {
1237                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1238                                 FILE_OP_ERROR(tmp, "chmod");
1239                                 g_warning("can't change file mode\n");
1240                         }
1241
1242                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1243                                 if (fputs(buf, tmpfp) == EOF) {
1244                                         FILE_OP_ERROR(tmp, "fputs");
1245                                         newsval = -1;
1246                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1247                                 }
1248                         }
1249                         fclose(tmpfp);
1250
1251                         if (newsval == 0) {
1252                                 debug_print("Sending message by news\n");
1253
1254                                 folder = FOLDER(newsac->folder);
1255
1256                                 newsval = news_post(folder, tmp);
1257                                 if (newsval < 0) {
1258                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1259                                                  newsac->nntp_server);
1260                                 }
1261                         }
1262                         unlink(tmp);
1263                 }
1264                 g_free(tmp);
1265         }
1266
1267         fclose(fp);
1268
1269         /* save message to outbox */
1270         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1271                 FolderItem *outbox;
1272
1273                 debug_print("saving sent message...\n");
1274
1275                 outbox = folder_find_item_from_identifier(savecopyfolder);
1276                 if (!outbox)
1277                         outbox = folder_get_default_outbox();
1278                         
1279                 if (save_clear_text || tmp_enc_file == NULL) {
1280                         procmsg_save_to_outbox(outbox, file, TRUE);
1281                 } else {
1282                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1283                 }
1284         }
1285
1286         if (tmp_enc_file != NULL) {
1287                 unlink(tmp_enc_file);
1288                 free(tmp_enc_file);
1289                 tmp_enc_file = NULL;
1290         }
1291
1292         if (replymessageid != NULL || fwdmessageid != NULL) {
1293                 gchar **tokens;
1294                 FolderItem *item;
1295                 
1296                 if (replymessageid != NULL)
1297                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1298                 else
1299                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1300                 item = folder_find_item_from_identifier(tokens[0]);
1301
1302                 /* check if queued message has valid folder and message id */
1303                 if (item != NULL && tokens[2] != NULL) {
1304                         MsgInfo *msginfo;
1305                         
1306                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1307                 
1308                         /* check if referring message exists and has a message id */
1309                         if ((msginfo != NULL) && 
1310                             (msginfo->msgid != NULL) &&
1311                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1312                                 procmsg_msginfo_free(msginfo);
1313                                 msginfo = NULL;
1314                         }
1315                         
1316                         if (msginfo == NULL) {
1317                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1318                         }
1319                         
1320                         if (msginfo != NULL) {
1321                                 if (replymessageid != NULL) {
1322                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1323                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1324                                 }  else {
1325                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1326                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1327                                 }
1328                                 procmsg_msginfo_free(msginfo);
1329                         }
1330                 }
1331                 g_strfreev(tokens);
1332         }
1333
1334         g_free(from);
1335         g_free(smtpserver);
1336         slist_free_strings(to_list);
1337         g_slist_free(to_list);
1338         slist_free_strings(newsgroup_list);
1339         g_slist_free(newsgroup_list);
1340         g_free(savecopyfolder);
1341         g_free(replymessageid);
1342         g_free(fwdmessageid);
1343         g_free(privacy_system);
1344         g_free(encrypt_data);
1345
1346         return (newsval != 0 ? newsval : mailval);
1347 }
1348
1349 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1350 {
1351         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1352
1353         /* NEW flag */
1354         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1355                 item->new_msgs++;
1356         }
1357
1358         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1359                 item->new_msgs--;
1360         }
1361
1362         /* UNREAD flag */
1363         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1364                 item->unread_msgs++;
1365                 if (procmsg_msg_has_marked_parent(msginfo))
1366                         item->unreadmarked_msgs++;
1367         }
1368
1369         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1370                 item->unread_msgs--;
1371                 if (procmsg_msg_has_marked_parent(msginfo))
1372                         item->unreadmarked_msgs--;
1373         }
1374         
1375         /* MARK flag */
1376         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1377                 procmsg_update_unread_children(msginfo, TRUE);
1378         }
1379
1380         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1381                 procmsg_update_unread_children(msginfo, FALSE);
1382         }
1383 }
1384
1385 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1386 {
1387         FolderItem *item;
1388         MsgInfoUpdate msginfo_update;
1389         MsgPermFlags perm_flags_new, perm_flags_old;
1390         MsgTmpFlags tmp_flags_old;
1391
1392         g_return_if_fail(msginfo != NULL);
1393         item = msginfo->folder;
1394         g_return_if_fail(item != NULL);
1395         
1396         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1397
1398         /* Perm Flags handling */
1399         perm_flags_old = msginfo->flags.perm_flags;
1400         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1401         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1402                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1403         }
1404
1405         if (perm_flags_old != perm_flags_new) {
1406                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1407
1408                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1409
1410         }
1411
1412         /* Tmp flags handling */
1413         tmp_flags_old = msginfo->flags.tmp_flags;
1414         msginfo->flags.tmp_flags |= tmp_flags;
1415
1416         /* update notification */
1417         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1418                 msginfo_update.msginfo = msginfo;
1419                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1420                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1421                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1422         }
1423 }
1424
1425 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1426 {
1427         FolderItem *item;
1428         MsgInfoUpdate msginfo_update;
1429         MsgPermFlags perm_flags_new, perm_flags_old;
1430         MsgTmpFlags tmp_flags_old;
1431
1432         g_return_if_fail(msginfo != NULL);
1433         item = msginfo->folder;
1434         g_return_if_fail(item != NULL);
1435         
1436         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1437
1438         /* Perm Flags handling */
1439         perm_flags_old = msginfo->flags.perm_flags;
1440         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1441         
1442         if (perm_flags_old != perm_flags_new) {
1443                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1444
1445                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1446
1447                 msginfo_update.msginfo = msginfo;
1448                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1449                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1450                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1451         }
1452
1453         /* Tmp flags hanlding */
1454         tmp_flags_old = msginfo->flags.tmp_flags;
1455         msginfo->flags.tmp_flags &= ~tmp_flags;
1456
1457         /* update notification */
1458         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1459                 msginfo_update.msginfo = msginfo;
1460                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1461                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1462                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1463         }
1464 }
1465
1466 /*!
1467  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1468  *
1469  *\param        info Current message
1470  *\param        perm_flags Flags to be checked
1471  *\param        parentmsgs Hash of prior msgs to avoid loops
1472  *
1473  *\return       gboolean TRUE if perm_flags are found
1474  */
1475 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1476                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1477 {
1478         MsgInfo *tmp;
1479
1480         g_return_val_if_fail(info != NULL, FALSE);
1481
1482         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1483                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1484                                 info->inreplyto);
1485                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1486                         procmsg_msginfo_free(tmp);
1487                         return TRUE;
1488                 } else if (tmp != NULL) {
1489                         gboolean result;
1490
1491                         if (g_hash_table_lookup(parentmsgs, info)) {
1492                                 debug_print("loop detected: %s%c%d\n",
1493                                         folder_item_get_path(info->folder),
1494                                         G_DIR_SEPARATOR, info->msgnum);
1495                                 result = FALSE;
1496                         } else {
1497                                 g_hash_table_insert(parentmsgs, info, "1");
1498                                 result = procmsg_msg_has_flagged_parent_real(
1499                                     tmp, perm_flags, parentmsgs);
1500                         }
1501                         procmsg_msginfo_free(tmp);
1502                         return result;
1503                 } else {
1504                         return FALSE;
1505                 }
1506         } else
1507                 return FALSE;
1508 }
1509
1510 /*!
1511  *\brief        Callback for cleaning up hash of parentmsgs
1512  */
1513 gboolean parentmsgs_hash_remove(gpointer key,
1514                             gpointer value,
1515                             gpointer user_data)
1516 {
1517         return TRUE;
1518 }
1519
1520 /*!
1521  *\brief        Set up list of parentmsgs
1522  *              See procmsg_msg_has_flagged_parent_real()
1523  */
1524 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1525 {
1526         gboolean result;
1527         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1528
1529         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1530         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1531         g_hash_table_destroy(parentmsgs);
1532         return result;
1533 }
1534
1535 /*!
1536  *\brief        Check if msgs prior in thread are marked
1537  *              See procmsg_msg_has_flagged_parent_real()
1538  */
1539 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1540 {
1541         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1542 }
1543
1544
1545 GSList *procmsg_find_children_func(MsgInfo *info, 
1546                                    GSList *children, GSList *all)
1547 {
1548         GSList *cur;
1549
1550         g_return_val_if_fail(info!=NULL, children);
1551         if (info->msgid == NULL)
1552                 return children;
1553
1554         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1555                 MsgInfo *tmp = (MsgInfo *)cur->data;
1556                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1557                         /* Check if message is already in the list */
1558                         if ((children == NULL) || 
1559                             (g_slist_index(children, tmp) == -1)) {
1560                                 children = g_slist_prepend(children,
1561                                                 procmsg_msginfo_new_ref(tmp));
1562                                 children = procmsg_find_children_func(tmp, 
1563                                                         children, 
1564                                                         all);
1565                         }
1566                 }
1567         }
1568         return children;
1569 }
1570
1571 GSList *procmsg_find_children (MsgInfo *info)
1572 {
1573         GSList *children;
1574         GSList *all, *cur;
1575
1576         g_return_val_if_fail(info!=NULL, NULL);
1577         all = folder_item_get_msg_list(info->folder);
1578         children = procmsg_find_children_func(info, NULL, all);
1579         if (children != NULL) {
1580                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1581                         /* this will not free the used pointers
1582                            created with procmsg_msginfo_new_ref */
1583                         procmsg_msginfo_free((MsgInfo *)cur->data);
1584                 }
1585         }
1586         g_slist_free(all);
1587
1588         return children;
1589 }
1590
1591 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1592 {
1593         GSList *children = procmsg_find_children(info);
1594         GSList *cur;
1595         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1596                 MsgInfo *tmp = (MsgInfo *)cur->data;
1597                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1598                         if(newly_marked) 
1599                                 info->folder->unreadmarked_msgs++;
1600                         else
1601                                 info->folder->unreadmarked_msgs--;
1602                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1603                 }
1604                 procmsg_msginfo_free(tmp);
1605         }
1606         g_slist_free(children);
1607 }
1608
1609 /**
1610  * Set the destination folder for a copy or move operation
1611  *
1612  * \param msginfo The message which's destination folder is changed
1613  * \param to_folder The destination folder for the operation
1614  */
1615 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1616 {
1617         if(msginfo->to_folder != NULL) {
1618                 msginfo->to_folder->op_count--;
1619                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1620         }
1621         msginfo->to_folder = to_folder;
1622         if(to_folder != NULL) {
1623                 to_folder->op_count++;
1624                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1625         }
1626 }
1627
1628 /**
1629  * Apply filtering actions to the msginfo
1630  *
1631  * \param msginfo The MsgInfo describing the message that should be filtered
1632  * \return TRUE if the message was moved and MsgInfo is now invalid,
1633  *         FALSE otherwise
1634  */
1635 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1636 {
1637         MailFilteringData mail_filtering_data;
1638                         
1639         mail_filtering_data.msginfo = msginfo;                  
1640         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1641                 return TRUE;
1642
1643         /* filter if enabled in prefs or move to inbox if not */
1644         if((filtering_rules != NULL) &&
1645            filter_message_by_msginfo(filtering_rules, msginfo))
1646                 return TRUE;
1647
1648         return FALSE;
1649 }
1650
1651 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
1652 {
1653         MsgInfo *tmp_msginfo = NULL;
1654         MsgFlags flags = {0, 0};
1655         
1656         
1657         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
1658             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
1659                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
1660                 return NULL;
1661         }
1662                     
1663         if (mimeinfo->content == MIMECONTENT_MEM) {
1664                 gchar *tmpfile = get_tmp_file();
1665                 str_write_to_file(mimeinfo->data.mem, tmpfile);
1666                 g_free(mimeinfo->data.mem);
1667                 mimeinfo->content == MIMECONTENT_FILE;
1668                 mimeinfo->data.filename = g_strdup(tmpfile);
1669                 g_free(tmpfile);
1670         }
1671
1672         tmp_msginfo = procheader_parse_file(mimeinfo->data.filename,
1673                                 flags, TRUE, FALSE);
1674
1675         if (tmp_msginfo != NULL) {
1676                 tmp_msginfo->folder = src_msginfo->folder;
1677                 tmp_msginfo->plaintext_file = g_strdup(mimeinfo->data.filename);
1678         } else {
1679                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
1680         }
1681         
1682         return tmp_msginfo;
1683 }