2006-08-22 [colin] 2.4.0cvs73
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 #include "mainwindow.h"
46 #include "summaryview.h"
47 #include "log.h"
48 #include "timing.h"
49 #include "inc.h"
50
51 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr);
52
53 enum
54 {
55         Q_SENDER           = 0,
56         Q_SMTPSERVER       = 1,
57         Q_RECIPIENTS       = 2,
58         Q_NEWSGROUPS       = 3,
59         Q_MAIL_ACCOUNT_ID  = 4,
60         Q_NEWS_ACCOUNT_ID  = 5,
61         Q_SAVE_COPY_FOLDER = 6,
62         Q_REPLY_MESSAGE_ID = 7,
63         Q_FWD_MESSAGE_ID   = 8,
64         Q_PRIVACY_SYSTEM   = 9,
65         Q_ENCRYPT          = 10,
66         Q_ENCRYPT_DATA     = 11,
67         Q_SYLPHEED_HDRS    = 12,
68 };
69
70 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
71 {
72         GHashTable *msg_table;
73
74         if (mlist == NULL) return NULL;
75
76         msg_table = g_hash_table_new(NULL, g_direct_equal);
77         procmsg_msg_hash_table_append(msg_table, mlist);
78
79         return msg_table;
80 }
81
82 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
83 {
84         GSList *cur;
85         MsgInfo *msginfo;
86
87         if (msg_table == NULL || mlist == NULL) return;
88
89         for (cur = mlist; cur != NULL; cur = cur->next) {
90                 msginfo = (MsgInfo *)cur->data;
91
92                 g_hash_table_insert(msg_table,
93                                     GUINT_TO_POINTER(msginfo->msgnum),
94                                     msginfo);
95         }
96 }
97
98 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
99 {
100         GHashTable *msg_table;
101         GSList *cur;
102         MsgInfo *msginfo;
103
104         if (mlist == NULL) return NULL;
105
106         msg_table = g_hash_table_new(NULL, g_direct_equal);
107
108         for (cur = mlist; cur != NULL; cur = cur->next) {
109                 msginfo = (MsgInfo *)cur->data;
110                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
111         }
112
113         return msg_table;
114 }
115
116 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
117 {
118         GSList *cur;
119         MsgInfo *msginfo;
120         gint last = 0;
121
122         for (cur = mlist; cur != NULL; cur = cur->next) {
123                 msginfo = (MsgInfo *)cur->data;
124                 if (msginfo && msginfo->msgnum > last)
125                         last = msginfo->msgnum;
126         }
127
128         return last;
129 }
130
131 void procmsg_msg_list_free(GSList *mlist)
132 {
133         GSList *cur;
134         MsgInfo *msginfo;
135
136         for (cur = mlist; cur != NULL; cur = cur->next) {
137                 msginfo = (MsgInfo *)cur->data;
138                 procmsg_msginfo_free(msginfo);
139         }
140         g_slist_free(mlist);
141 }
142
143 struct MarkSum {
144         gint *new_msgs;
145         gint *unread_msgs;
146         gint *total_msgs;
147         gint *min;
148         gint *max;
149         gint first;
150 };
151
152 /* CLAWS subject threading:
153   
154   in the first round it inserts subject lines in a 
155   relation (subject <-> node)
156
157   the second round finishes the threads by attaching
158   matching subject lines to the one found in the
159   relation. will use the oldest node with the same
160   subject that is not more then thread_by_subject_max_age
161   days old (see subject_relation_lookup)
162 */  
163
164 static void subject_relation_insert(GRelation *relation, GNode *node)
165 {
166         gchar *subject;
167         MsgInfo *msginfo;
168
169         g_return_if_fail(relation != NULL);
170         g_return_if_fail(node != NULL);
171         msginfo = (MsgInfo *) node->data;
172         g_return_if_fail(msginfo != NULL);
173
174         subject = msginfo->subject;
175         if (subject == NULL)
176                 return;
177         subject += subject_get_prefix_length(subject);
178
179         g_relation_insert(relation, subject, node);
180 }
181
182 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
183 {
184         gchar *subject;
185         GTuples *tuples;
186         GNode *node = NULL;
187         gint prefix_length;
188     
189         g_return_val_if_fail(relation != NULL, NULL);
190
191         subject = msginfo->subject;
192         if (subject == NULL)
193                 return NULL;
194         prefix_length = subject_get_prefix_length(subject);
195         if (prefix_length <= 0)
196                 return NULL;
197         subject += prefix_length;
198         
199         tuples = g_relation_select(relation, subject, 0);
200         if (tuples == NULL)
201                 return NULL;
202
203         if (tuples->len > 0) {
204                 int i;
205                 GNode *relation_node;
206                 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
207                 gboolean match;
208
209                 /* check all nodes with the same subject to find the best parent */
210                 for (i = 0; i < tuples->len; i++) {
211                         relation_node = (GNode *) g_tuples_index(tuples, i, 1);
212                         relation_msginfo = (MsgInfo *) relation_node->data;
213                         match = FALSE;
214
215                         /* best node should be the oldest in the found nodes */
216                         /* parent node must not be older then msginfo */
217                         if ((relation_msginfo->date_t < msginfo->date_t) &&
218                             ((best_msginfo == NULL) ||
219                              (best_msginfo->date_t > relation_msginfo->date_t)))
220                                 match = TRUE;
221
222                         /* parent node must not be more then thread_by_subject_max_age
223                            days older then msginfo */
224                         if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
225                             prefs_common.thread_by_subject_max_age * 3600 * 24)
226                                 match = FALSE;
227
228                         /* can add new tests for all matching
229                            nodes found by subject */
230
231                         if (match) {
232                                 node = relation_node;
233                                 best_msginfo = relation_msginfo;
234                         }
235                 }           
236         }
237
238         g_tuples_destroy(tuples);
239         return node;
240 }
241
242 /* return the reversed thread tree */
243 GNode *procmsg_get_thread_tree(GSList *mlist)
244 {
245         GNode *root, *parent, *node, *next;
246         GHashTable *msgid_table;
247         GRelation *subject_relation;
248         MsgInfo *msginfo;
249         const gchar *msgid;
250         GSList *reflist;
251         START_TIMING("procmsg_get_thread_tree");
252         root = g_node_new(NULL);
253         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
254         
255         if (prefs_common.thread_by_subject) {
256                 subject_relation = g_relation_new(2);
257                 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
258         }
259
260         for (; mlist != NULL; mlist = mlist->next) {
261                 msginfo = (MsgInfo *)mlist->data;
262                 parent = root;
263
264                 if (msginfo->inreplyto) {
265                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
266                         if (parent == NULL) {
267                                 parent = root;
268                         }
269                 }
270                 node = g_node_insert_data_before
271                         (parent, parent == root ? parent->children : NULL,
272                          msginfo);
273                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
274                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
275
276                 /* CLAWS: add subject to relation (without prefix) */
277                 if (prefs_common.thread_by_subject) {
278                         subject_relation_insert(subject_relation, node);
279                 }
280         }
281
282         /* complete the unfinished threads */
283         for (node = root->children; node != NULL; ) {
284                 next = node->next;
285                 msginfo = (MsgInfo *)node->data;
286                 parent = NULL;
287                 
288                 if (msginfo->inreplyto)
289                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
290
291                 /* try looking for the indirect parent */
292                 if (!parent && msginfo->references) {
293                         for (reflist = msginfo->references;
294                              reflist != NULL; reflist = reflist->next)
295                                 if ((parent = g_hash_table_lookup
296                                         (msgid_table, reflist->data)) != NULL)
297                                         break;
298                 }                                        
299               
300                 /* node should not be the parent, and node should not
301                    be an ancestor of parent (circular reference) */
302                 if (parent && parent != node &&
303                     !g_node_is_ancestor(node, parent)) {
304                         g_node_unlink(node);
305                         g_node_insert_before
306                                 (parent, parent->children, node);
307                 }
308                
309                 node = next;
310         }
311
312         if (prefs_common.thread_by_subject) {
313                 START_TIMING("procmsg_get_thread_tree(1)");
314                 for (node = root->children; node && node != NULL;) {
315                         next = node->next;
316                         msginfo = (MsgInfo *) node->data;
317                         
318                         parent = subject_relation_lookup(subject_relation, msginfo);
319                         
320                         /* the node may already be threaded by IN-REPLY-TO, so go up 
321                          * in the tree to 
322                            find the parent node */
323                         if (parent != NULL) {
324                                 if (g_node_is_ancestor(node, parent))
325                                         parent = NULL;
326                                 if (parent == node)
327                                         parent = NULL;
328                         }
329                         
330                         if (parent) {
331                                 g_node_unlink(node);
332                                 g_node_append(parent, node);
333                         }
334
335                         node = next;
336                 }       
337                 END_TIMING();
338         }
339         
340         if (prefs_common.thread_by_subject)
341                 g_relation_destroy(subject_relation);
342
343         g_hash_table_destroy(msgid_table);
344         END_TIMING();
345         return root;
346 }
347
348 gint procmsg_move_messages(GSList *mlist)
349 {
350         GSList *cur, *movelist = NULL;
351         MsgInfo *msginfo;
352         FolderItem *dest = NULL;
353         gint retval = 0;
354         gboolean finished = TRUE;
355         if (!mlist) return 0;
356
357         folder_item_update_freeze();
358
359 next_folder:
360         for (cur = mlist; cur != NULL; cur = cur->next) {
361                 msginfo = (MsgInfo *)cur->data;
362                 if (!msginfo->to_folder) {
363                         continue;
364                 } else {
365                         finished = FALSE;
366                 }
367                 if (!dest) {
368                         dest = msginfo->to_folder;
369                         movelist = g_slist_prepend(movelist, msginfo);
370                 } else if (dest == msginfo->to_folder) {
371                         movelist = g_slist_prepend(movelist, msginfo);
372                 } else {
373                         continue;
374                 }
375                 procmsg_msginfo_set_to_folder(msginfo, NULL);
376         }
377         if (movelist) {
378                 movelist = g_slist_reverse(movelist);
379                 retval |= folder_item_move_msgs(dest, movelist);
380                 g_slist_free(movelist);
381                 movelist = NULL;
382         }
383         if (finished == FALSE) {
384                 finished = TRUE;
385                 dest = NULL;
386                 goto next_folder;
387         }
388
389         folder_item_update_thaw();
390         return retval;
391 }
392
393 void procmsg_copy_messages(GSList *mlist)
394 {
395         GSList *cur, *copylist = NULL;
396         MsgInfo *msginfo;
397         FolderItem *dest = NULL;
398         gboolean finished = TRUE;
399         if (!mlist) return;
400
401         folder_item_update_freeze();
402
403 next_folder:
404         for (cur = mlist; cur != NULL; cur = cur->next) {
405                 msginfo = (MsgInfo *)cur->data;
406                 if (!msginfo->to_folder) {
407                         continue;
408                 } else {
409                         finished = FALSE;
410                 }
411                 if (!dest) {
412                         dest = msginfo->to_folder;
413                         copylist = g_slist_prepend(copylist, msginfo);
414                 } else if (dest == msginfo->to_folder) {
415                         copylist = g_slist_prepend(copylist, msginfo);
416                 } else {
417                         continue;
418                 }
419                 procmsg_msginfo_set_to_folder(msginfo, NULL);
420         }
421         if (copylist) {
422                 copylist = g_slist_reverse(copylist);
423                 folder_item_copy_msgs(dest, copylist);
424                 g_slist_free(copylist);
425                 copylist = NULL;
426         }
427         if (finished == FALSE) {
428                 finished = TRUE;
429                 dest = NULL;
430                 goto next_folder;
431         }
432
433         folder_item_update_thaw();
434 }
435
436 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
437 {
438         gchar *file;
439
440         g_return_val_if_fail(msginfo != NULL, NULL);
441
442         if (msginfo->plaintext_file)
443                 file = g_strdup(msginfo->plaintext_file);
444         else {
445                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
446         }
447
448         return file;
449 }
450
451 gchar *procmsg_get_message_file(MsgInfo *msginfo)
452 {
453         gchar *filename = NULL;
454
455         g_return_val_if_fail(msginfo != NULL, NULL);
456
457         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
458         if (!filename)
459                 debug_print("can't fetch message %d\n", msginfo->msgnum);
460
461         return filename;
462 }
463
464 gchar *procmsg_get_message_file_full(MsgInfo *msginfo, gboolean headers, gboolean body)
465 {
466         gchar *filename = NULL;
467
468         g_return_val_if_fail(msginfo != NULL, NULL);
469
470         filename = folder_item_fetch_msg_full(msginfo->folder, msginfo->msgnum,
471                                                 headers, body);
472         if (!filename)
473                 debug_print("can't fetch message %d\n", msginfo->msgnum);
474
475         return filename;
476 }
477
478 GSList *procmsg_get_message_file_list(GSList *mlist)
479 {
480         GSList *file_list = NULL;
481         MsgInfo *msginfo;
482         MsgFileInfo *fileinfo;
483         gchar *file;
484
485         while (mlist != NULL) {
486                 msginfo = (MsgInfo *)mlist->data;
487                 file = procmsg_get_message_file(msginfo);
488                 if (!file) {
489                         procmsg_message_file_list_free(file_list);
490                         return NULL;
491                 }
492                 fileinfo = g_new(MsgFileInfo, 1);
493                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
494                 fileinfo->file = file;
495                 fileinfo->flags = g_new(MsgFlags, 1);
496                 *fileinfo->flags = msginfo->flags;
497                 file_list = g_slist_prepend(file_list, fileinfo);
498                 mlist = mlist->next;
499         }
500
501         file_list = g_slist_reverse(file_list);
502
503         return file_list;
504 }
505
506 void procmsg_message_file_list_free(MsgInfoList *file_list)
507 {
508         GSList *cur;
509         MsgFileInfo *fileinfo;
510
511         for (cur = file_list; cur != NULL; cur = cur->next) {
512                 fileinfo = (MsgFileInfo *)cur->data;
513                 procmsg_msginfo_free(fileinfo->msginfo);
514                 g_free(fileinfo->file);
515                 g_free(fileinfo->flags);
516                 g_free(fileinfo);
517         }
518
519         g_slist_free(file_list);
520 }
521
522 FILE *procmsg_open_message(MsgInfo *msginfo)
523 {
524         FILE *fp;
525         gchar *file;
526
527         g_return_val_if_fail(msginfo != NULL, NULL);
528
529         file = procmsg_get_message_file_path(msginfo);
530         g_return_val_if_fail(file != NULL, NULL);
531
532         if (!is_file_exist(file)) {
533                 g_free(file);
534                 file = procmsg_get_message_file(msginfo);
535                 if (!file)
536                         return NULL;
537         }
538
539         if ((fp = g_fopen(file, "rb")) == NULL) {
540                 FILE_OP_ERROR(file, "fopen");
541                 g_free(file);
542                 return NULL;
543         }
544
545         g_free(file);
546
547         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
548                 gchar buf[BUFFSIZE];
549
550                 while (fgets(buf, sizeof(buf), fp) != NULL) {
551                         /* new way */
552                         if (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
553                                 strlen("X-Sylpheed-End-Special-Headers:")))
554                                 break;
555                         /* old way */
556                         if (buf[0] == '\r' || buf[0] == '\n') break;
557                         /* from other mailers */
558                         if (!strncmp(buf, "Date: ", 6)
559                         ||  !strncmp(buf, "To: ", 4)
560                         ||  !strncmp(buf, "From: ", 6)
561                         ||  !strncmp(buf, "Subject: ", 9)) {
562                                 rewind(fp);
563                                 break;
564                         }
565                 }
566         }
567
568         return fp;
569 }
570
571 gboolean procmsg_msg_exist(MsgInfo *msginfo)
572 {
573         gchar *path;
574         gboolean ret;
575
576         if (!msginfo) return FALSE;
577
578         path = folder_item_get_path(msginfo->folder);
579         change_dir(path);
580         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
581         g_free(path);
582
583         return ret;
584 }
585
586 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
587                                 PrefsFilterType type)
588 {
589         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
590                                        {"X-ML-Name:",      NULL, TRUE},
591                                        {"X-List:",         NULL, TRUE},
592                                        {"X-Mailing-list:", NULL, TRUE},
593                                        {"List-Id:",        NULL, TRUE},
594                                        {"X-Sequence:",     NULL, TRUE},
595                                        {"Sender:",         NULL, TRUE},
596                                        {"List-Post:",      NULL, TRUE},
597                                        {NULL,              NULL, FALSE}};
598         enum
599         {
600                 H_X_BEENTHERE    = 0,
601                 H_X_ML_NAME      = 1,
602                 H_X_LIST         = 2,
603                 H_X_MAILING_LIST = 3,
604                 H_LIST_ID        = 4,
605                 H_X_SEQUENCE     = 5,
606                 H_SENDER         = 6,
607                 H_LIST_POST      = 7
608         };
609
610         FILE *fp;
611
612         g_return_if_fail(msginfo != NULL);
613         g_return_if_fail(header != NULL);
614         g_return_if_fail(key != NULL);
615
616         *header = NULL;
617         *key = NULL;
618
619         switch (type) {
620         case FILTER_BY_NONE:
621                 return;
622         case FILTER_BY_AUTO:
623                 if ((fp = procmsg_open_message(msginfo)) == NULL)
624                         return;
625                 procheader_get_header_fields(fp, hentry);
626                 fclose(fp);
627
628 #define SET_FILTER_KEY(hstr, idx)       \
629 {                                       \
630         *header = g_strdup(hstr);       \
631         *key = hentry[idx].body;        \
632         hentry[idx].body = NULL;        \
633 }
634
635                 if (hentry[H_X_BEENTHERE].body != NULL) {
636                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
637                 } else if (hentry[H_X_ML_NAME].body != NULL) {
638                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
639                 } else if (hentry[H_X_LIST].body != NULL) {
640                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
641                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
642                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
643                 } else if (hentry[H_LIST_ID].body != NULL) {
644                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
645                         extract_list_id_str(*key);
646                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
647                         gchar *p;
648
649                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
650                         p = *key;
651                         while (*p != '\0') {
652                                 while (*p != '\0' && !g_ascii_isspace(*p)) p++;
653                                 while (g_ascii_isspace(*p)) p++;
654                                 if (g_ascii_isdigit(*p)) {
655                                         *p = '\0';
656                                         break;
657                                 }
658                         }
659                         g_strstrip(*key);
660                 } else if (hentry[H_SENDER].body != NULL) {
661                         SET_FILTER_KEY("header \"Sender\"", H_SENDER);
662                 } else if (hentry[H_LIST_POST].body != NULL) {
663                         SET_FILTER_KEY("header \"List-Post\"", H_LIST_POST);
664                 } else if (msginfo->to) {
665                         *header = g_strdup("to");
666                         *key = g_strdup(msginfo->to);
667                 } else if (msginfo->subject) {
668                         *header = g_strdup("subject");
669                         *key = g_strdup(msginfo->subject);
670                 }
671
672 #undef SET_FILTER_KEY
673
674                 g_free(hentry[H_X_BEENTHERE].body);
675                 hentry[H_X_BEENTHERE].body = NULL;
676                 g_free(hentry[H_X_ML_NAME].body);
677                 hentry[H_X_ML_NAME].body = NULL;
678                 g_free(hentry[H_X_LIST].body);
679                 hentry[H_X_LIST].body = NULL;
680                 g_free(hentry[H_X_MAILING_LIST].body);
681                 hentry[H_X_MAILING_LIST].body = NULL;
682                 g_free(hentry[H_LIST_ID].body);
683                 hentry[H_LIST_ID].body = NULL;
684                 g_free(hentry[H_SENDER].body);
685                 hentry[H_SENDER].body = NULL;
686                 g_free(hentry[H_LIST_POST].body);
687                 hentry[H_LIST_POST].body = NULL;
688
689                 break;
690         case FILTER_BY_FROM:
691                 *header = g_strdup("from");
692                 *key = g_strdup(msginfo->from);
693                 break;
694         case FILTER_BY_TO:
695                 *header = g_strdup("to");
696                 *key = g_strdup(msginfo->to);
697                 break;
698         case FILTER_BY_SUBJECT:
699                 *header = g_strdup("subject");
700                 *key = g_strdup(msginfo->subject);
701                 break;
702         default:
703                 break;
704         }
705 }
706
707 void procmsg_empty_trash(FolderItem *trash)
708 {
709         GNode *node, *next;
710
711         if (!trash || 
712             (trash->stype != F_TRASH && 
713              !folder_has_parent_of_type(trash, F_TRASH)))
714                 return;
715
716         if (trash && trash->total_msgs > 0) {
717                 GSList *mlist = folder_item_get_msg_list(trash);
718                 GSList *cur;
719                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
720                         MsgInfo * msginfo = (MsgInfo *) cur->data;
721                         if (MSG_IS_LOCKED(msginfo->flags))
722                                 continue;
723                         if (msginfo->total_size != 0 && 
724                             msginfo->size != (off_t)msginfo->total_size)
725                                 partial_mark_for_delete(msginfo);
726
727                         procmsg_msginfo_free(msginfo);
728                 }
729                 g_slist_free(mlist);
730                 folder_item_remove_all_msg(trash);
731         }
732
733         if (!trash->node || !trash->node->children)
734                 return;
735
736         node = trash->node->children;
737         while (node != NULL) {
738                 next = node->next;
739                 procmsg_empty_trash(FOLDER_ITEM(node->data));
740                 node = next;
741         }
742 }
743
744 void procmsg_empty_all_trash(void)
745 {
746         FolderItem *trash;
747         GList *cur;
748
749         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
750                 Folder *folder = FOLDER(cur->data);
751                 trash = folder->trash;
752                 procmsg_empty_trash(trash);
753                 if (folder->account && folder->account->set_trash_folder && 
754                     folder_find_item_from_identifier(folder->account->trash_folder))
755                         procmsg_empty_trash(
756                                 folder_find_item_from_identifier(folder->account->trash_folder));
757         }
758 }
759
760 static PrefsAccount *procmsg_get_account_from_file(const gchar *file)
761 {
762         PrefsAccount *mailac = NULL;
763         FILE *fp;
764         int hnum;
765         gchar buf[BUFFSIZE];
766         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
767                                        {"SSV:",  NULL, FALSE},
768                                        {"R:",    NULL, FALSE},
769                                        {"NG:",   NULL, FALSE},
770                                        {"MAID:", NULL, FALSE},
771                                        {"NAID:", NULL, FALSE},
772                                        {"SCF:",  NULL, FALSE},
773                                        {"RMID:", NULL, FALSE},
774                                        {"FMID:", NULL, FALSE},
775                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
776                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
777                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
778                                        {NULL,    NULL, FALSE}};
779         
780         g_return_val_if_fail(file != NULL, NULL);
781
782         if ((fp = g_fopen(file, "rb")) == NULL) {
783                 FILE_OP_ERROR(file, "fopen");
784                 return NULL;
785         }
786
787         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
788                != -1) {
789                 gchar *p = buf + strlen(qentry[hnum].name);
790
791                 if (hnum == Q_MAIL_ACCOUNT_ID) {
792                         mailac = account_find_from_id(atoi(p));
793                         break;
794                 }
795         }
796         fclose(fp);
797         return mailac;
798 }
799
800 static GSList *procmsg_list_sort_by_account(FolderItem *queue, GSList *list)
801 {
802         GSList *result = NULL;
803         GSList *orig = NULL;
804         PrefsAccount *last_account = NULL;
805         MsgInfo *msg = NULL;
806         GSList *cur = NULL;
807         gboolean nothing_to_sort = TRUE;
808
809         if (!list)
810                 return NULL;
811
812         orig = g_slist_copy(list);
813         
814         msg = (MsgInfo *)orig->data;
815         
816         for (cur = orig; cur; cur = cur->next)
817                 debug_print("sort before %s\n", ((MsgInfo *)cur->data)->from);
818         
819         debug_print("\n");
820
821 parse_again:    
822         nothing_to_sort = TRUE;
823         cur = orig;
824         while (cur) {
825                 gchar *file = NULL;
826                 PrefsAccount *ac = NULL;
827                 msg = (MsgInfo *)cur->data;
828                 file = folder_item_fetch_msg(queue, msg->msgnum);
829                 ac = procmsg_get_account_from_file(file);
830                 g_free(file);
831
832                 if (last_account == NULL || (ac != NULL && ac == last_account)) {
833                         result = g_slist_append(result, msg);
834                         orig = g_slist_remove(orig, msg);
835                         last_account = ac;
836                         nothing_to_sort = FALSE;
837                         goto parse_again;
838                 }
839                 cur = cur->next;
840         }
841         
842         if (orig || g_slist_length(orig)) {
843                 if (!last_account && nothing_to_sort) {
844                         /* can't find an account for the rest of the list */
845                         cur = orig;
846                         while (cur) {
847                                 result = g_slist_append(result, cur->data);
848                                 cur = cur->next;
849                         }
850                 } else {
851                         last_account = NULL;
852                         goto parse_again;
853                 }
854         }
855         
856         g_slist_free(orig);
857         
858         for (cur = result; cur; cur = cur->next)
859                 debug_print("sort after  %s\n", ((MsgInfo *)cur->data)->from);
860
861         debug_print("\n");
862
863         return result;
864 }
865
866 static gboolean procmsg_is_last_for_account(FolderItem *queue, MsgInfo *msginfo, GSList *elem)
867 {
868         gchar *file = folder_item_fetch_msg(queue, msginfo->msgnum);
869         PrefsAccount *ac = procmsg_get_account_from_file(file);
870         GSList *cur = elem;
871         g_free(file);
872         for (cur = elem; cur; cur = cur->next) {
873                 MsgInfo *cur_msginfo = (MsgInfo *)cur->data;
874                 file = folder_item_fetch_msg(queue, cur_msginfo->msgnum);
875                 
876                 if (cur_msginfo != msginfo && !MSG_IS_LOCKED(cur_msginfo->flags)) {
877                         if (procmsg_get_account_from_file(file) == ac) {
878                                 g_free(file);
879                                 return FALSE;
880                         }
881                 }
882                 
883                 g_free(file);
884         }
885         return TRUE;
886 }
887
888 static gboolean send_queue_lock = FALSE;
889 /*!
890  *\brief        Send messages in queue
891  *
892  *\param        queue Queue folder to process
893  *\param        save_msgs Unused
894  *
895  *\return       Number of messages sent, negative if an error occurred
896  *              positive if no error occurred
897  */
898 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs, gchar **errstr)
899 {
900         gint sent = 0, err = 0;
901         GSList *list, *elem;
902         GSList *sorted_list = NULL;
903         GNode *node, *next;
904         
905         if (send_queue_lock) {
906                 log_error(_("Already trying to send\n"));
907                 if (errstr) {
908                         if (*errstr) g_free(*errstr);
909                         *errstr = g_strdup_printf(_("Already trying to send."));
910                 }
911                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
912                 return -1;
913         }
914         send_queue_lock = TRUE;
915         inc_lock();
916         if (!queue)
917                 queue = folder_get_default_queue();
918         
919         if (queue == NULL) {
920                 send_queue_lock = FALSE;
921                 inc_unlock();
922                 return -1;
923         }
924
925         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
926
927         folder_item_scan(queue);
928         list = folder_item_get_msg_list(queue);
929
930         /* sort the list per sender account; this helps reusing the same SMTP server */
931         sorted_list = procmsg_list_sort_by_account(queue, list);
932         
933         for (elem = sorted_list; elem != NULL; elem = elem->next) {
934                 gchar *file;
935                 MsgInfo *msginfo;
936                         
937                 msginfo = (MsgInfo *)(elem->data);
938                 if (!MSG_IS_LOCKED(msginfo->flags)) {
939                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
940                         if (file) {
941                                 if (procmsg_send_message_queue_full(file, 
942                                                 !procmsg_is_last_for_account(queue, msginfo, elem),
943                                                 errstr) < 0) {
944                                         g_warning("Sending queued message %d failed.\n", 
945                                                   msginfo->msgnum);
946                                         err++;
947                                 } else {
948                                         sent++; 
949                                         folder_item_remove_msg(queue, msginfo->msgnum);
950                                 }
951                                 g_free(file);
952                         }
953                 }
954                 /* FIXME: supposedly if only one message is locked, and queue
955                  * is being flushed, the following free says something like 
956                  * "freeing msg ## in folder (nil)". */
957                 procmsg_msginfo_free(msginfo);
958         }
959
960         g_slist_free(sorted_list);
961         folder_item_scan(queue);
962
963         if (queue->node && queue->node->children) {
964                 node = queue->node->children;
965                 while (node != NULL) {
966                         int res = 0;
967                         next = node->next;
968                         res = procmsg_send_queue(FOLDER_ITEM(node->data), save_msgs, errstr);
969                         if (res < 0) 
970                                 err = -res;
971                         else
972                                 sent += res;
973                         node = next;
974                 }
975         }
976         send_queue_lock = FALSE;
977         inc_unlock();
978         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
979
980         return (err != 0 ? -err : sent);
981 }
982
983 gboolean procmsg_is_sending(void)
984 {
985         return send_queue_lock;
986 }
987
988 /*!
989  *\brief        Determine if a queue folder is empty
990  *
991  *\param        queue Queue folder to process
992  *
993  *\return       TRUE if the queue folder is empty, otherwise return FALSE
994  */
995 gboolean procmsg_queue_is_empty(FolderItem *queue)
996 {
997         GSList *list;
998         gboolean res = FALSE;
999         if (!queue)
1000                 queue = folder_get_default_queue();
1001         g_return_val_if_fail(queue != NULL, TRUE);
1002
1003         folder_item_scan(queue);
1004         list = folder_item_get_msg_list(queue);
1005         res = (list == NULL);
1006         procmsg_msg_list_free(list);
1007
1008         if (res == TRUE) {
1009                 GNode *node, *next;
1010                 if (queue->node && queue->node->children) {
1011                         node = queue->node->children;
1012                         while (node != NULL) {
1013                                 next = node->next;
1014                                 if (!procmsg_queue_is_empty(FOLDER_ITEM(node->data)))
1015                                         return FALSE;
1016                                 node = next;
1017                         }
1018                 }
1019         }
1020         return res;
1021 }
1022
1023 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
1024 {
1025         FILE *fp, *outfp;
1026         gchar buf[BUFFSIZE];
1027         
1028         if ((fp = g_fopen(in, "rb")) == NULL) {
1029                 FILE_OP_ERROR(in, "fopen");
1030                 return -1;
1031         }
1032         if ((outfp = g_fopen(out, "wb")) == NULL) {
1033                 FILE_OP_ERROR(out, "fopen");
1034                 fclose(fp);
1035                 return -1;
1036         }
1037         while (fgets(buf, sizeof(buf), fp) != NULL) {
1038                 /* new way */
1039                 if (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
1040                         strlen("X-Sylpheed-End-Special-Headers:")))
1041                         break;
1042                 /* old way */
1043                 if (buf[0] == '\r' || buf[0] == '\n') break;
1044                 /* from other mailers */
1045                 if (!strncmp(buf, "Date: ", 6)
1046                 ||  !strncmp(buf, "To: ", 4)
1047                 ||  !strncmp(buf, "From: ", 6)
1048                 ||  !strncmp(buf, "Subject: ", 9)) {
1049                         rewind(fp);
1050                         break;
1051                 }
1052         }
1053         while (fgets(buf, sizeof(buf), fp) != NULL)
1054                 fputs(buf, outfp);
1055         fclose(outfp);
1056         fclose(fp);
1057         return 0;
1058 }
1059
1060 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
1061                             gboolean is_queued)
1062 {
1063         gint num;
1064         MsgInfo *msginfo, *tmp_msginfo;
1065         MsgFlags flag = {0, 0};
1066
1067         debug_print("saving sent message...\n");
1068
1069         if (!outbox)
1070                 outbox = folder_get_default_outbox();
1071         g_return_val_if_fail(outbox != NULL, -1);
1072
1073         /* remove queueing headers */
1074         if (is_queued) {
1075                 gchar tmp[MAXPATHLEN + 1];
1076
1077                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
1078                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
1079                 
1080                 if (procmsg_remove_special_headers(file, tmp) !=0)
1081                         return -1;
1082
1083                 folder_item_scan(outbox);
1084                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
1085                         g_warning("can't save message\n");
1086                         g_unlink(tmp);
1087                         return -1;
1088                 }
1089         } else {
1090                 folder_item_scan(outbox);
1091                 if ((num = folder_item_add_msg
1092                         (outbox, file, &flag, FALSE)) < 0) {
1093                         g_warning("can't save message\n");
1094                         return -1;
1095                 }
1096         }
1097         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
1098         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
1099         if (msginfo != NULL) {
1100                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1101                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
1102                 /* tmp_msginfo == msginfo */
1103                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
1104                     msginfo->returnreceiptto)) {
1105                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
1106                 }       
1107                 procmsg_msginfo_free(tmp_msginfo);              /* refcnt-- */
1108         }
1109
1110         return 0;
1111 }
1112
1113 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
1114 {
1115         static const gchar *def_cmd = "lpr %s";
1116         static guint id = 0;
1117         gchar *prtmp;
1118         FILE *tmpfp, *prfp;
1119         gchar buf[1024];
1120         gchar *p;
1121
1122         g_return_if_fail(msginfo);
1123
1124         if (procmime_msginfo_is_encrypted(msginfo))
1125                 tmpfp = procmime_get_first_encrypted_text_content(msginfo);
1126         else
1127                 tmpfp = procmime_get_first_text_content(msginfo);
1128         if (tmpfp == NULL) {
1129                 g_warning("Can't get text part\n");
1130                 return;
1131         }
1132
1133         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
1134                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
1135
1136         if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
1137                 FILE_OP_ERROR(prtmp, "fopen");
1138                 g_free(prtmp);
1139                 fclose(tmpfp);
1140                 return;
1141         }
1142
1143         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
1144         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
1145         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
1146         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
1147         if (msginfo->newsgroups)
1148                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
1149         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
1150         fputc('\n', prfp);
1151
1152         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1153                 fputs(buf, prfp);
1154
1155         fclose(prfp);
1156         fclose(tmpfp);
1157
1158         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
1159             !strchr(p + 2, '%'))
1160                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
1161         else {
1162                 if (cmdline)
1163                         g_warning("Print command line is invalid: '%s'\n",
1164                                   cmdline);
1165                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
1166         }
1167
1168         g_free(prtmp);
1169
1170         g_strchomp(buf);
1171         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
1172         system(buf);
1173 }
1174
1175 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1176 {
1177         msginfo->refcnt++;
1178         
1179         return msginfo;
1180 }
1181
1182 MsgInfo *procmsg_msginfo_new(void)
1183 {
1184         MsgInfo *newmsginfo;
1185
1186         newmsginfo = g_new0(MsgInfo, 1);
1187         newmsginfo->refcnt = 1;
1188         
1189         return newmsginfo;
1190 }
1191
1192 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1193 {
1194         MsgInfo *newmsginfo;
1195         GSList *refs;
1196
1197         if (msginfo == NULL) return NULL;
1198
1199         newmsginfo = g_new0(MsgInfo, 1);
1200
1201         newmsginfo->refcnt = 1;
1202
1203 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1204 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1205                         g_strdup(msginfo->mmb) : NULL
1206
1207         MEMBCOPY(msgnum);
1208         MEMBCOPY(size);
1209         MEMBCOPY(mtime);
1210         MEMBCOPY(date_t);
1211
1212         MEMBCOPY(flags);
1213
1214         MEMBDUP(fromname);
1215
1216         MEMBDUP(date);
1217         MEMBDUP(from);
1218         MEMBDUP(to);
1219         MEMBDUP(cc);
1220         MEMBDUP(newsgroups);
1221         MEMBDUP(subject);
1222         MEMBDUP(msgid);
1223         MEMBDUP(inreplyto);
1224         MEMBDUP(xref);
1225
1226         MEMBCOPY(folder);
1227         MEMBCOPY(to_folder);
1228
1229         MEMBDUP(face);
1230         MEMBDUP(xface);
1231         MEMBDUP(dispositionnotificationto);
1232         MEMBDUP(returnreceiptto);
1233
1234         refs = msginfo->references;
1235         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1236                 newmsginfo->references = g_slist_prepend
1237                         (newmsginfo->references, g_strdup(refs->data)); 
1238         }
1239         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1240
1241         MEMBCOPY(score);
1242         MEMBDUP(plaintext_file);
1243
1244         return newmsginfo;
1245 }
1246
1247 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1248 {
1249         MsgInfo *full_msginfo;
1250         gchar *file;
1251
1252         if (msginfo == NULL) return NULL;
1253
1254         file = procmsg_get_message_file_path(msginfo);
1255         if (!file || !is_file_exist(file)) {
1256                 g_free(file);
1257                 file = procmsg_get_message_file(msginfo);
1258         }
1259         if (!file || !is_file_exist(file)) {
1260                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
1261                 return NULL;
1262         }
1263
1264         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1265         g_free(file);
1266         if (!full_msginfo) return NULL;
1267
1268         /* CLAWS: make sure we add the missing members; see: 
1269          * procheader.c::procheader_get_headernames() */
1270         if (!msginfo->list_post)
1271                 msginfo->list_post = g_strdup(full_msginfo->list_post);
1272         if (!msginfo->list_subscribe)
1273                 msginfo->list_subscribe = g_strdup(full_msginfo->list_subscribe);
1274         if (!msginfo->list_unsubscribe)
1275                 msginfo->list_unsubscribe = g_strdup(full_msginfo->list_unsubscribe);
1276         if (!msginfo->list_help)
1277                 msginfo->list_help = g_strdup(full_msginfo->list_help);
1278         if (!msginfo->list_archive)
1279                 msginfo->list_archive= g_strdup(full_msginfo->list_archive);
1280         if (!msginfo->list_owner)
1281                 msginfo->list_owner = g_strdup(full_msginfo->list_owner);
1282         if (!msginfo->xface)
1283                 msginfo->xface = g_strdup(full_msginfo->xface);
1284         if (!msginfo->face)
1285                 msginfo->face = g_strdup(full_msginfo->face);
1286         if (!msginfo->dispositionnotificationto)
1287                 msginfo->dispositionnotificationto = 
1288                         g_strdup(full_msginfo->dispositionnotificationto);
1289         if (!msginfo->returnreceiptto)
1290                 msginfo->returnreceiptto = g_strdup
1291                         (full_msginfo->returnreceiptto);
1292         if (!msginfo->partial_recv && full_msginfo->partial_recv)
1293                 msginfo->partial_recv = g_strdup
1294                         (full_msginfo->partial_recv);
1295         msginfo->total_size = full_msginfo->total_size;
1296         if (!msginfo->account_server && full_msginfo->account_server)
1297                 msginfo->account_server = g_strdup
1298                         (full_msginfo->account_server);
1299         if (!msginfo->account_login && full_msginfo->account_login)
1300                 msginfo->account_login = g_strdup
1301                         (full_msginfo->account_login);
1302         msginfo->planned_download = full_msginfo->planned_download;
1303         procmsg_msginfo_free(full_msginfo);
1304
1305         return procmsg_msginfo_new_ref(msginfo);
1306 }
1307
1308 void procmsg_msginfo_free(MsgInfo *msginfo)
1309 {
1310         if (msginfo == NULL) return;
1311
1312         msginfo->refcnt--;
1313         if (msginfo->refcnt > 0)
1314                 return;
1315
1316         if (msginfo->to_folder) {
1317                 msginfo->to_folder->op_count--;
1318                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1319         }
1320
1321         g_free(msginfo->fromspace);
1322         g_free(msginfo->returnreceiptto);
1323         g_free(msginfo->dispositionnotificationto);
1324         g_free(msginfo->xface);
1325         g_free(msginfo->face);
1326
1327         g_free(msginfo->fromname);
1328
1329         g_free(msginfo->date);
1330         g_free(msginfo->from);
1331         g_free(msginfo->to);
1332         g_free(msginfo->cc);
1333         g_free(msginfo->newsgroups);
1334         g_free(msginfo->subject);
1335         g_free(msginfo->msgid);
1336         g_free(msginfo->inreplyto);
1337         g_free(msginfo->xref);
1338
1339         g_free(msginfo->list_post);
1340         g_free(msginfo->list_subscribe);
1341         g_free(msginfo->list_unsubscribe);
1342         g_free(msginfo->list_help);
1343         g_free(msginfo->list_archive);
1344         g_free(msginfo->list_owner);
1345
1346         g_free(msginfo->partial_recv);
1347         g_free(msginfo->account_server);
1348         g_free(msginfo->account_login);
1349         
1350         slist_free_strings(msginfo->references);
1351         g_slist_free(msginfo->references);
1352
1353         g_free(msginfo->plaintext_file);
1354
1355         g_free(msginfo);
1356 }
1357
1358 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1359 {
1360         guint memusage = 0;
1361         GSList *refs;
1362         
1363         memusage += sizeof(MsgInfo);
1364         if (msginfo->fromname)
1365                 memusage += strlen(msginfo->fromname);
1366         if (msginfo->date)
1367                 memusage += strlen(msginfo->date);
1368         if (msginfo->from)
1369                 memusage += strlen(msginfo->from);
1370         if (msginfo->to)
1371                 memusage += strlen(msginfo->to);
1372         if (msginfo->cc)
1373                 memusage += strlen(msginfo->cc);
1374         if (msginfo->newsgroups)
1375                 memusage += strlen(msginfo->newsgroups);
1376         if (msginfo->subject)
1377                 memusage += strlen(msginfo->subject);
1378         if (msginfo->msgid)
1379                 memusage += strlen(msginfo->msgid);
1380         if (msginfo->inreplyto)
1381                 memusage += strlen(msginfo->inreplyto);
1382         if (msginfo->xface)
1383                 memusage += strlen(msginfo->xface);
1384         if (msginfo->face)
1385                 memusage += strlen(msginfo->face);
1386         if (msginfo->dispositionnotificationto)
1387                 memusage += strlen(msginfo->dispositionnotificationto);
1388         if (msginfo->returnreceiptto)
1389                 memusage += strlen(msginfo->returnreceiptto);
1390         for (refs = msginfo->references; refs; refs=refs->next) {
1391                 gchar *r = (gchar *)refs->data;
1392                 memusage += r?strlen(r):0;
1393         }
1394         if (msginfo->fromspace)
1395                 memusage += strlen(msginfo->fromspace);
1396
1397         return memusage;
1398 }
1399
1400 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1401 {
1402         const MsgInfo *msginfo1 = a;
1403         const MsgInfo *msginfo2 = b;
1404
1405         if (!msginfo1)
1406                 return -1;
1407         if (!msginfo2)
1408                 return -1;
1409
1410         return msginfo1->msgnum - msginfo2->msgnum;
1411 }
1412
1413 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr)
1414 {
1415         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1416                                        {"SSV:",  NULL, FALSE},
1417                                        {"R:",    NULL, FALSE},
1418                                        {"NG:",   NULL, FALSE},
1419                                        {"MAID:", NULL, FALSE},
1420                                        {"NAID:", NULL, FALSE},
1421                                        {"SCF:",  NULL, FALSE},
1422                                        {"RMID:", NULL, FALSE},
1423                                        {"FMID:", NULL, FALSE},
1424                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1425                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1426                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1427                                        {"X-Sylpheed-End-Special-Headers:", NULL, FALSE},
1428                                        {NULL,    NULL, FALSE}};
1429         FILE *fp;
1430         gint filepos;
1431         gint mailval = 0, newsval = 0;
1432         gchar *from = NULL;
1433         gchar *smtpserver = NULL;
1434         GSList *to_list = NULL;
1435         GSList *newsgroup_list = NULL;
1436         gchar *savecopyfolder = NULL;
1437         gchar *replymessageid = NULL;
1438         gchar *fwdmessageid = NULL;
1439         gchar *privacy_system = NULL;
1440         gboolean encrypt = FALSE;
1441         gchar *encrypt_data = NULL;
1442         gchar buf[BUFFSIZE];
1443         gint hnum;
1444         PrefsAccount *mailac = NULL, *newsac = NULL;
1445         gboolean save_clear_text = TRUE;
1446         gchar *tmp_enc_file = NULL;
1447
1448         int local = 0;
1449
1450         g_return_val_if_fail(file != NULL, -1);
1451
1452         if ((fp = g_fopen(file, "rb")) == NULL) {
1453                 FILE_OP_ERROR(file, "fopen");
1454                 if (errstr) {
1455                         if (*errstr) g_free(*errstr);
1456                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1457                 }
1458                 return -1;
1459         }
1460
1461         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1462                != -1) {
1463                 gchar *p = buf + strlen(qentry[hnum].name);
1464
1465                 switch (hnum) {
1466                 case Q_SENDER:
1467                         if (from == NULL) 
1468                                 from = g_strdup(p);
1469                         break;
1470                 case Q_SMTPSERVER:
1471                         if (smtpserver == NULL) 
1472                                 smtpserver = g_strdup(p);
1473                         break;
1474                 case Q_RECIPIENTS:
1475                         to_list = address_list_append(to_list, p);
1476                         break;
1477                 case Q_NEWSGROUPS:
1478                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1479                         break;
1480                 case Q_MAIL_ACCOUNT_ID:
1481                         mailac = account_find_from_id(atoi(p));
1482                         break;
1483                 case Q_NEWS_ACCOUNT_ID:
1484                         newsac = account_find_from_id(atoi(p));
1485                         break;
1486                 case Q_SAVE_COPY_FOLDER:
1487                         if (savecopyfolder == NULL) 
1488                                 savecopyfolder = g_strdup(p);
1489                         break;
1490                 case Q_REPLY_MESSAGE_ID:
1491                         if (replymessageid == NULL) 
1492                                 replymessageid = g_strdup(p);
1493                         break;
1494                 case Q_FWD_MESSAGE_ID:
1495                         if (fwdmessageid == NULL) 
1496                                 fwdmessageid = g_strdup(p);
1497                         break;
1498                 case Q_PRIVACY_SYSTEM:
1499                         if (privacy_system == NULL) 
1500                                 privacy_system = g_strdup(p);
1501                         break;
1502                 case Q_ENCRYPT:
1503                         if (p[0] == '1') 
1504                                 encrypt = TRUE;
1505                         break;
1506                 case Q_ENCRYPT_DATA:
1507                         if (encrypt_data == NULL) 
1508                                 encrypt_data = g_strdup(p);
1509                         break;
1510                 case Q_SYLPHEED_HDRS:
1511                         /* end of special headers reached */
1512                         goto send_mail; /* can't "break;break;" */
1513                 }
1514         }
1515 send_mail:
1516         filepos = ftell(fp);
1517
1518         if (encrypt) {
1519                 MimeInfo *mimeinfo;
1520
1521                 if (mailac && mailac->save_encrypted_as_clear_text 
1522                 &&  !mailac->encrypt_to_self)
1523                         save_clear_text = TRUE;
1524                 else
1525                         save_clear_text = FALSE;
1526
1527                 fclose(fp);
1528                 fp = NULL;
1529
1530                 mimeinfo = procmime_scan_queue_file(file);
1531                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1532                 || (fp = my_tmpfile()) == NULL
1533                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1534                         if (fp)
1535                                 fclose(fp);
1536                         procmime_mimeinfo_free_all(mimeinfo);
1537                         g_free(from);
1538                         g_free(smtpserver);
1539                         slist_free_strings(to_list);
1540                         g_slist_free(to_list);
1541                         slist_free_strings(newsgroup_list);
1542                         g_slist_free(newsgroup_list);
1543                         g_free(savecopyfolder);
1544                         g_free(replymessageid);
1545                         g_free(fwdmessageid);
1546                         g_free(privacy_system);
1547                         g_free(encrypt_data);
1548                         if (errstr) {
1549                                 if (*errstr) g_free(*errstr);
1550                                 *errstr = g_strdup_printf(_("Couldn't encrypt the email: %s"),
1551                                                 privacy_get_error());
1552                         }
1553                         return -1;
1554                 }
1555                 
1556                 rewind(fp);
1557                 if (!save_clear_text) {
1558                         gchar *content = NULL;
1559                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1560                         if (tmpfp) {
1561                                 fclose(tmpfp);
1562
1563                                 content = file_read_stream_to_str(fp);
1564                                 rewind(fp);
1565
1566                                 str_write_to_file(content, tmp_enc_file);
1567                                 g_free(content);
1568                         } else {
1569                                 g_warning("couldn't get tempfile\n");
1570                         }
1571                 } 
1572                 
1573                 procmime_mimeinfo_free_all(mimeinfo);
1574                 
1575                 filepos = 0;
1576         }
1577
1578         if (to_list) {
1579                 debug_print("Sending message by mail\n");
1580                 if (!from) {
1581                         if (errstr) {
1582                                 if (*errstr) g_free(*errstr);
1583                                 *errstr = g_strdup_printf(_("Queued message header is broken."));
1584                         }
1585                         mailval = -1;
1586                 } else if (mailac && mailac->use_mail_command &&
1587                            mailac->mail_command && (* mailac->mail_command)) {
1588                         mailval = send_message_local(mailac->mail_command, fp);
1589                         local = 1;
1590                 } else {
1591                         if (!mailac) {
1592                                 mailac = account_find_from_smtp_server(from, smtpserver);
1593                                 if (!mailac) {
1594                                         g_warning("Account not found. "
1595                                                     "Using current account...\n");
1596                                         mailac = cur_account;
1597                                 }
1598                         }
1599
1600                         if (mailac) {
1601                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1602                                 if (mailval == -1 && errstr) {
1603                                         if (*errstr) g_free(*errstr);
1604                                         *errstr = g_strdup_printf(_("An error happened during SMTP session."));
1605                                 }
1606                         } else {
1607                                 PrefsAccount tmp_ac;
1608
1609                                 g_warning("Account not found.\n");
1610
1611                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1612                                 tmp_ac.address = from;
1613                                 tmp_ac.smtp_server = smtpserver;
1614                                 tmp_ac.smtpport = SMTP_PORT;
1615                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1616                                 if (mailval == -1 && errstr) {
1617                                         if (*errstr) g_free(*errstr);
1618                                         *errstr = g_strdup_printf(_("No specific account has been found to "
1619                                                         "send, and an error happened during SMTP session."));
1620                                 }
1621                         }
1622                 }
1623         } else if (!to_list && !newsgroup_list) {
1624                 if (errstr) {
1625                         if (*errstr) g_free(*errstr);
1626                         *errstr = g_strdup(_("Couldn't determine sending informations. "
1627                                 "Maybe the email hasn't been generated by Sylpheed-Claws."));
1628                 }
1629                 mailval = -1;
1630         }
1631
1632         fseek(fp, filepos, SEEK_SET);
1633         if (newsgroup_list && (mailval == 0)) {
1634                 Folder *folder;
1635                 gchar *tmp = NULL;
1636                 FILE *tmpfp;
1637
1638                 /* write to temporary file */
1639                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1640                             G_DIR_SEPARATOR, (gint)file);
1641                 if ((tmpfp = g_fopen(tmp, "wb")) == NULL) {
1642                         FILE_OP_ERROR(tmp, "fopen");
1643                         newsval = -1;
1644                         alertpanel_error(_("Could not create temporary file for news sending."));
1645                 } else {
1646                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1647                                 FILE_OP_ERROR(tmp, "chmod");
1648                                 g_warning("can't change file mode\n");
1649                         }
1650
1651                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1652                                 if (fputs(buf, tmpfp) == EOF) {
1653                                         FILE_OP_ERROR(tmp, "fputs");
1654                                         newsval = -1;
1655                                         if (errstr) {
1656                                                 if (*errstr) g_free(*errstr);
1657                                                 *errstr = g_strdup_printf(_("Error when writing temporary file for news sending."));
1658                                         }
1659                                 }
1660                         }
1661                         fclose(tmpfp);
1662
1663                         if (newsval == 0) {
1664                                 debug_print("Sending message by news\n");
1665
1666                                 folder = FOLDER(newsac->folder);
1667
1668                                 newsval = news_post(folder, tmp);
1669                                 if (newsval < 0 && errstr)  {
1670                                         if (*errstr) g_free(*errstr);
1671                                         *errstr = g_strdup_printf(_("Error occurred while posting the message to %s."),
1672                                          newsac->nntp_server);
1673                                 }
1674                         }
1675                         g_unlink(tmp);
1676                 }
1677                 g_free(tmp);
1678         }
1679
1680         fclose(fp);
1681
1682         /* save message to outbox */
1683         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1684                 FolderItem *outbox;
1685
1686                 debug_print("saving sent message...\n");
1687
1688                 outbox = folder_find_item_from_identifier(savecopyfolder);
1689                 if (!outbox)
1690                         outbox = folder_get_default_outbox();
1691                         
1692                 if (save_clear_text || tmp_enc_file == NULL) {
1693                         procmsg_save_to_outbox(outbox, file, TRUE);
1694                 } else {
1695                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1696                 }
1697         }
1698
1699         if (tmp_enc_file != NULL) {
1700                 g_unlink(tmp_enc_file);
1701                 free(tmp_enc_file);
1702                 tmp_enc_file = NULL;
1703         }
1704
1705         if (replymessageid != NULL || fwdmessageid != NULL) {
1706                 gchar **tokens;
1707                 FolderItem *item;
1708                 
1709                 if (replymessageid != NULL)
1710                         tokens = g_strsplit(replymessageid, "\t", 0);
1711                 else
1712                         tokens = g_strsplit(fwdmessageid, "\t", 0);
1713                 item = folder_find_item_from_identifier(tokens[0]);
1714
1715                 /* check if queued message has valid folder and message id */
1716                 if (item != NULL && tokens[2] != NULL) {
1717                         MsgInfo *msginfo;
1718                         
1719                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1720                 
1721                         /* check if referring message exists and has a message id */
1722                         if ((msginfo != NULL) && 
1723                             (msginfo->msgid != NULL) &&
1724                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1725                                 procmsg_msginfo_free(msginfo);
1726                                 msginfo = NULL;
1727                         }
1728                         
1729                         if (msginfo == NULL) {
1730                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1731                         }
1732                         
1733                         if (msginfo != NULL) {
1734                                 if (replymessageid != NULL) {
1735                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1736                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1737                                 }  else {
1738                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1739                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1740                                 }
1741                                 procmsg_msginfo_free(msginfo);
1742                         }
1743                 }
1744                 g_strfreev(tokens);
1745         }
1746
1747         g_free(from);
1748         g_free(smtpserver);
1749         slist_free_strings(to_list);
1750         g_slist_free(to_list);
1751         slist_free_strings(newsgroup_list);
1752         g_slist_free(newsgroup_list);
1753         g_free(savecopyfolder);
1754         g_free(replymessageid);
1755         g_free(fwdmessageid);
1756         g_free(privacy_system);
1757         g_free(encrypt_data);
1758
1759         return (newsval != 0 ? newsval : mailval);
1760 }
1761
1762 gint procmsg_send_message_queue(const gchar *file, gchar **errstr)
1763 {
1764         gint result = procmsg_send_message_queue_full(file, FALSE, errstr);
1765         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1766         return result;
1767 }
1768
1769 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1770 {
1771         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1772
1773         /* NEW flag */
1774         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1775                 item->new_msgs++;
1776         }
1777
1778         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1779                 item->new_msgs--;
1780         }
1781
1782         /* UNREAD flag */
1783         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1784                 item->unread_msgs++;
1785                 if (procmsg_msg_has_marked_parent(msginfo))
1786                         item->unreadmarked_msgs++;
1787         }
1788
1789         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1790                 item->unread_msgs--;
1791                 if (procmsg_msg_has_marked_parent(msginfo))
1792                         item->unreadmarked_msgs--;
1793         }
1794         
1795         /* MARK flag */
1796         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1797                 procmsg_update_unread_children(msginfo, TRUE);
1798                 item->marked_msgs++;
1799         }
1800
1801         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1802                 procmsg_update_unread_children(msginfo, FALSE);
1803                 item->marked_msgs--;
1804         }
1805 }
1806
1807 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1808 {
1809         FolderItem *item;
1810         MsgInfoUpdate msginfo_update;
1811         MsgPermFlags perm_flags_new, perm_flags_old;
1812         MsgTmpFlags tmp_flags_old;
1813
1814         g_return_if_fail(msginfo != NULL);
1815         item = msginfo->folder;
1816         g_return_if_fail(item != NULL);
1817         
1818         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1819
1820         /* Perm Flags handling */
1821         perm_flags_old = msginfo->flags.perm_flags;
1822         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1823         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1824                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1825         }
1826
1827         if (perm_flags_old != perm_flags_new) {
1828                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1829
1830                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1831
1832         }
1833
1834         /* Tmp flags handling */
1835         tmp_flags_old = msginfo->flags.tmp_flags;
1836         msginfo->flags.tmp_flags |= tmp_flags;
1837
1838         /* update notification */
1839         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1840                 msginfo_update.msginfo = msginfo;
1841                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1842                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1843                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1844         }
1845 }
1846
1847 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1848 {
1849         FolderItem *item;
1850         MsgInfoUpdate msginfo_update;
1851         MsgPermFlags perm_flags_new, perm_flags_old;
1852         MsgTmpFlags tmp_flags_old;
1853
1854         g_return_if_fail(msginfo != NULL);
1855         item = msginfo->folder;
1856         g_return_if_fail(item != NULL);
1857         
1858         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1859
1860         /* Perm Flags handling */
1861         perm_flags_old = msginfo->flags.perm_flags;
1862         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1863         
1864         if (perm_flags_old != perm_flags_new) {
1865                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1866
1867                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1868         }
1869
1870         /* Tmp flags hanlding */
1871         tmp_flags_old = msginfo->flags.tmp_flags;
1872         msginfo->flags.tmp_flags &= ~tmp_flags;
1873
1874         /* update notification */
1875         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1876                 msginfo_update.msginfo = msginfo;
1877                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1878                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1879                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1880         }
1881 }
1882
1883 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1884                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1885                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1886 {
1887         FolderItem *item;
1888         MsgInfoUpdate msginfo_update;
1889         MsgPermFlags perm_flags_new, perm_flags_old;
1890         MsgTmpFlags tmp_flags_old;
1891
1892         g_return_if_fail(msginfo != NULL);
1893         item = msginfo->folder;
1894         g_return_if_fail(item != NULL);
1895         
1896         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1897
1898         /* Perm Flags handling */
1899         perm_flags_old = msginfo->flags.perm_flags;
1900         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1901         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1902                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1903         }
1904
1905         if (perm_flags_old != perm_flags_new) {
1906                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1907
1908                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1909
1910         }
1911
1912         /* Tmp flags handling */
1913         tmp_flags_old = msginfo->flags.tmp_flags;
1914         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1915         msginfo->flags.tmp_flags |= add_tmp_flags;
1916
1917         /* update notification */
1918         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1919                 msginfo_update.msginfo = msginfo;
1920                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1921                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1922                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1923         }
1924 }
1925
1926 /*!
1927  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1928  *
1929  *\param        info Current message
1930  *\param        perm_flags Flags to be checked
1931  *\param        parentmsgs Hash of prior msgs to avoid loops
1932  *
1933  *\return       gboolean TRUE if perm_flags are found
1934  */
1935 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1936                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1937 {
1938         MsgInfo *tmp;
1939
1940         g_return_val_if_fail(info != NULL, FALSE);
1941
1942         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1943                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1944                                 info->inreplyto);
1945                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1946                         procmsg_msginfo_free(tmp);
1947                         return TRUE;
1948                 } else if (tmp != NULL) {
1949                         gboolean result;
1950
1951                         if (g_hash_table_lookup(parentmsgs, info)) {
1952                                 debug_print("loop detected: %d\n",
1953                                         info->msgnum);
1954                                 result = FALSE;
1955                         } else {
1956                                 g_hash_table_insert(parentmsgs, info, "1");
1957                                 result = procmsg_msg_has_flagged_parent_real(
1958                                     tmp, perm_flags, parentmsgs);
1959                         }
1960                         procmsg_msginfo_free(tmp);
1961                         return result;
1962                 } else {
1963                         return FALSE;
1964                 }
1965         } else
1966                 return FALSE;
1967 }
1968
1969 /*!
1970  *\brief        Callback for cleaning up hash of parentmsgs
1971  */
1972 gboolean parentmsgs_hash_remove(gpointer key,
1973                             gpointer value,
1974                             gpointer user_data)
1975 {
1976         return TRUE;
1977 }
1978
1979 /*!
1980  *\brief        Set up list of parentmsgs
1981  *              See procmsg_msg_has_flagged_parent_real()
1982  */
1983 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1984 {
1985         gboolean result;
1986         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1987
1988         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1989         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1990         g_hash_table_destroy(parentmsgs);
1991         return result;
1992 }
1993
1994 /*!
1995  *\brief        Check if msgs prior in thread are marked
1996  *              See procmsg_msg_has_flagged_parent_real()
1997  */
1998 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1999 {
2000         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2001 }
2002
2003
2004 GSList *procmsg_find_children_func(MsgInfo *info, 
2005                                    GSList *children, GSList *all)
2006 {
2007         GSList *cur;
2008
2009         g_return_val_if_fail(info!=NULL, children);
2010         if (info->msgid == NULL)
2011                 return children;
2012
2013         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2014                 MsgInfo *tmp = (MsgInfo *)cur->data;
2015                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2016                         /* Check if message is already in the list */
2017                         if ((children == NULL) || 
2018                             (g_slist_index(children, tmp) == -1)) {
2019                                 children = g_slist_prepend(children,
2020                                                 procmsg_msginfo_new_ref(tmp));
2021                                 children = procmsg_find_children_func(tmp, 
2022                                                         children, 
2023                                                         all);
2024                         }
2025                 }
2026         }
2027         return children;
2028 }
2029
2030 GSList *procmsg_find_children (MsgInfo *info)
2031 {
2032         GSList *children;
2033         GSList *all, *cur;
2034
2035         g_return_val_if_fail(info!=NULL, NULL);
2036         all = folder_item_get_msg_list(info->folder);
2037         children = procmsg_find_children_func(info, NULL, all);
2038         if (children != NULL) {
2039                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2040                         /* this will not free the used pointers
2041                            created with procmsg_msginfo_new_ref */
2042                         procmsg_msginfo_free((MsgInfo *)cur->data);
2043                 }
2044         }
2045         g_slist_free(all);
2046
2047         return children;
2048 }
2049
2050 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2051 {
2052         GSList *children = procmsg_find_children(info);
2053         GSList *cur;
2054         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2055                 MsgInfo *tmp = (MsgInfo *)cur->data;
2056                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2057                         if(newly_marked) 
2058                                 info->folder->unreadmarked_msgs++;
2059                         else
2060                                 info->folder->unreadmarked_msgs--;
2061                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2062                 }
2063                 procmsg_msginfo_free(tmp);
2064         }
2065         g_slist_free(children);
2066 }
2067
2068 /**
2069  * Set the destination folder for a copy or move operation
2070  *
2071  * \param msginfo The message which's destination folder is changed
2072  * \param to_folder The destination folder for the operation
2073  */
2074 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2075 {
2076         if(msginfo->to_folder != NULL) {
2077                 msginfo->to_folder->op_count--;
2078                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2079         }
2080         msginfo->to_folder = to_folder;
2081         if(to_folder != NULL) {
2082                 to_folder->op_count++;
2083                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2084         }
2085 }
2086
2087 /**
2088  * Apply filtering actions to the msginfo
2089  *
2090  * \param msginfo The MsgInfo describing the message that should be filtered
2091  * \return TRUE if the message was moved and MsgInfo is now invalid,
2092  *         FALSE otherwise
2093  */
2094 gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2095 {
2096         MailFilteringData mail_filtering_data;
2097                         
2098         mail_filtering_data.msginfo = msginfo;                  
2099         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data)) {
2100                 return TRUE;
2101         }
2102
2103         /* filter if enabled in prefs or move to inbox if not */
2104         if((filtering_rules != NULL) &&
2105            filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs)) {
2106                 return TRUE;
2107         }
2108                 
2109         return FALSE;
2110 }
2111
2112 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2113 {
2114         MsgInfo *tmp_msginfo = NULL;
2115         MsgFlags flags = {0, 0};
2116         gchar *tmpfile = get_tmp_file();
2117         FILE *fp = g_fopen(tmpfile, "wb");
2118         
2119         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2120             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2121                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2122                 if (fp) 
2123                         fclose(fp);
2124                 g_free(tmpfile);
2125                 return NULL;
2126         }
2127         
2128         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2129                 fclose(fp);
2130                 fp = NULL;
2131                 tmp_msginfo = procheader_parse_file(
2132                         tmpfile, flags, 
2133                         TRUE, FALSE);
2134         }
2135         if (fp)
2136                 fclose(fp);
2137
2138         if (tmp_msginfo != NULL) {
2139                 if (src_msginfo)
2140                         tmp_msginfo->folder = src_msginfo->folder;
2141                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2142         } else {
2143                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2144         }
2145
2146         g_free(tmpfile);
2147
2148         return tmp_msginfo;
2149 }
2150
2151 static GSList *spam_learners = NULL;
2152
2153 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2154 {
2155         if (!g_slist_find(spam_learners, learn_func))
2156                 spam_learners = g_slist_append(spam_learners, learn_func);
2157         if (mainwindow_get_mainwindow()) {
2158                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2159                 summary_set_menu_sensitive(
2160                         mainwindow_get_mainwindow()->summaryview);
2161                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2162         }
2163 }
2164
2165 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2166 {
2167         spam_learners = g_slist_remove(spam_learners, learn_func);
2168         if (mainwindow_get_mainwindow()) {
2169                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2170                 summary_set_menu_sensitive(
2171                         mainwindow_get_mainwindow()->summaryview);
2172                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2173         }
2174 }
2175
2176 gboolean procmsg_spam_can_learn(void)
2177 {
2178         return g_slist_length(spam_learners) > 0;
2179 }
2180
2181 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2182 {
2183         GSList *cur = spam_learners;
2184         int ret = 0;
2185         for (; cur; cur = cur->next) {
2186                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2187                 ret |= func(info, list, spam);
2188         }
2189         return ret;
2190 }
2191
2192 static gchar *spam_folder_item = NULL;
2193 void procmsg_spam_set_folder (const char *item_identifier)
2194 {
2195         g_free(spam_folder_item);
2196         if (item_identifier)
2197                 spam_folder_item = g_strdup(item_identifier);
2198         else
2199                 spam_folder_item = NULL;
2200 }
2201
2202 FolderItem *procmsg_spam_get_folder (void)
2203 {
2204         FolderItem *item = spam_folder_item ? folder_find_item_from_identifier(spam_folder_item) : NULL;
2205         return item ? item : folder_get_default_trash();
2206 }
2207
2208 static void item_has_queued_mails(FolderItem *item, gpointer data)
2209 {
2210         gboolean *result = (gboolean *)data;
2211         if (*result == TRUE)
2212                 return;
2213         if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0)
2214                 *result = TRUE;
2215 }
2216
2217 gboolean procmsg_have_queued_mails_fast (void)
2218 {
2219         gboolean result = FALSE;
2220         folder_func_to_all_folders(item_has_queued_mails, &result);
2221         return result;
2222 }