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