2006-08-15 [colin] 2.4.0cvs51
[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                 return 0;
1088         }
1089         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
1090         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
1091         if (msginfo != NULL) {
1092                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1093                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
1094                 /* tmp_msginfo == msginfo */
1095                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
1096                     msginfo->returnreceiptto)) {
1097                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
1098                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
1099                 }       
1100         }
1101
1102         return 0;
1103 }
1104
1105 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
1106 {
1107         static const gchar *def_cmd = "lpr %s";
1108         static guint id = 0;
1109         gchar *prtmp;
1110         FILE *tmpfp, *prfp;
1111         gchar buf[1024];
1112         gchar *p;
1113
1114         g_return_if_fail(msginfo);
1115
1116         if (procmime_msginfo_is_encrypted(msginfo))
1117                 tmpfp = procmime_get_first_encrypted_text_content(msginfo);
1118         else
1119                 tmpfp = procmime_get_first_text_content(msginfo);
1120         if (tmpfp == NULL) {
1121                 g_warning("Can't get text part\n");
1122                 return;
1123         }
1124
1125         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
1126                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
1127
1128         if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
1129                 FILE_OP_ERROR(prtmp, "fopen");
1130                 g_free(prtmp);
1131                 fclose(tmpfp);
1132                 return;
1133         }
1134
1135         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
1136         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
1137         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
1138         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
1139         if (msginfo->newsgroups)
1140                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
1141         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
1142         fputc('\n', prfp);
1143
1144         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1145                 fputs(buf, prfp);
1146
1147         fclose(prfp);
1148         fclose(tmpfp);
1149
1150         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
1151             !strchr(p + 2, '%'))
1152                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
1153         else {
1154                 if (cmdline)
1155                         g_warning("Print command line is invalid: '%s'\n",
1156                                   cmdline);
1157                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
1158         }
1159
1160         g_free(prtmp);
1161
1162         g_strchomp(buf);
1163         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
1164         system(buf);
1165 }
1166
1167 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1168 {
1169         msginfo->refcnt++;
1170         
1171         return msginfo;
1172 }
1173
1174 MsgInfo *procmsg_msginfo_new(void)
1175 {
1176         MsgInfo *newmsginfo;
1177
1178         newmsginfo = g_new0(MsgInfo, 1);
1179         newmsginfo->refcnt = 1;
1180         
1181         return newmsginfo;
1182 }
1183
1184 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1185 {
1186         MsgInfo *newmsginfo;
1187         GSList *refs;
1188
1189         if (msginfo == NULL) return NULL;
1190
1191         newmsginfo = g_new0(MsgInfo, 1);
1192
1193         newmsginfo->refcnt = 1;
1194
1195 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1196 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1197                         g_strdup(msginfo->mmb) : NULL
1198
1199         MEMBCOPY(msgnum);
1200         MEMBCOPY(size);
1201         MEMBCOPY(mtime);
1202         MEMBCOPY(date_t);
1203
1204         MEMBCOPY(flags);
1205
1206         MEMBDUP(fromname);
1207
1208         MEMBDUP(date);
1209         MEMBDUP(from);
1210         MEMBDUP(to);
1211         MEMBDUP(cc);
1212         MEMBDUP(newsgroups);
1213         MEMBDUP(subject);
1214         MEMBDUP(msgid);
1215         MEMBDUP(inreplyto);
1216         MEMBDUP(xref);
1217
1218         MEMBCOPY(folder);
1219         MEMBCOPY(to_folder);
1220
1221         MEMBDUP(face);
1222         MEMBDUP(xface);
1223         MEMBDUP(dispositionnotificationto);
1224         MEMBDUP(returnreceiptto);
1225
1226         refs = msginfo->references;
1227         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1228                 newmsginfo->references = g_slist_prepend
1229                         (newmsginfo->references, g_strdup(refs->data)); 
1230         }
1231         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1232
1233         MEMBCOPY(score);
1234         MEMBDUP(plaintext_file);
1235
1236         return newmsginfo;
1237 }
1238
1239 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1240 {
1241         MsgInfo *full_msginfo;
1242         gchar *file;
1243
1244         if (msginfo == NULL) return NULL;
1245
1246         file = procmsg_get_message_file_path(msginfo);
1247         if (!file || !is_file_exist(file)) {
1248                 g_free(file);
1249                 file = procmsg_get_message_file(msginfo);
1250         }
1251         if (!file || !is_file_exist(file)) {
1252                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
1253                 return NULL;
1254         }
1255
1256         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1257         g_free(file);
1258         if (!full_msginfo) return NULL;
1259
1260         /* CLAWS: make sure we add the missing members; see: 
1261          * procheader.c::procheader_get_headernames() */
1262         if (!msginfo->list_post)
1263                 msginfo->list_post = g_strdup(full_msginfo->list_post);
1264         if (!msginfo->list_subscribe)
1265                 msginfo->list_subscribe = g_strdup(full_msginfo->list_subscribe);
1266         if (!msginfo->list_unsubscribe)
1267                 msginfo->list_unsubscribe = g_strdup(full_msginfo->list_unsubscribe);
1268         if (!msginfo->list_help)
1269                 msginfo->list_help = g_strdup(full_msginfo->list_help);
1270         if (!msginfo->list_archive)
1271                 msginfo->list_archive= g_strdup(full_msginfo->list_archive);
1272         if (!msginfo->list_owner)
1273                 msginfo->list_owner = g_strdup(full_msginfo->list_owner);
1274         if (!msginfo->xface)
1275                 msginfo->xface = g_strdup(full_msginfo->xface);
1276         if (!msginfo->face)
1277                 msginfo->face = g_strdup(full_msginfo->face);
1278         if (!msginfo->dispositionnotificationto)
1279                 msginfo->dispositionnotificationto = 
1280                         g_strdup(full_msginfo->dispositionnotificationto);
1281         if (!msginfo->returnreceiptto)
1282                 msginfo->returnreceiptto = g_strdup
1283                         (full_msginfo->returnreceiptto);
1284         if (!msginfo->partial_recv && full_msginfo->partial_recv)
1285                 msginfo->partial_recv = g_strdup
1286                         (full_msginfo->partial_recv);
1287         msginfo->total_size = full_msginfo->total_size;
1288         if (!msginfo->account_server && full_msginfo->account_server)
1289                 msginfo->account_server = g_strdup
1290                         (full_msginfo->account_server);
1291         if (!msginfo->account_login && full_msginfo->account_login)
1292                 msginfo->account_login = g_strdup
1293                         (full_msginfo->account_login);
1294         msginfo->planned_download = full_msginfo->planned_download;
1295         procmsg_msginfo_free(full_msginfo);
1296
1297         return procmsg_msginfo_new_ref(msginfo);
1298 }
1299
1300 void procmsg_msginfo_free(MsgInfo *msginfo)
1301 {
1302         if (msginfo == NULL) return;
1303
1304         msginfo->refcnt--;
1305         if (msginfo->refcnt > 0)
1306                 return;
1307
1308         if (msginfo->to_folder) {
1309                 msginfo->to_folder->op_count--;
1310                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1311         }
1312
1313         g_free(msginfo->fromspace);
1314         g_free(msginfo->returnreceiptto);
1315         g_free(msginfo->dispositionnotificationto);
1316         g_free(msginfo->xface);
1317         g_free(msginfo->face);
1318
1319         g_free(msginfo->fromname);
1320
1321         g_free(msginfo->date);
1322         g_free(msginfo->from);
1323         g_free(msginfo->to);
1324         g_free(msginfo->cc);
1325         g_free(msginfo->newsgroups);
1326         g_free(msginfo->subject);
1327         g_free(msginfo->msgid);
1328         g_free(msginfo->inreplyto);
1329         g_free(msginfo->xref);
1330
1331         g_free(msginfo->list_post);
1332         g_free(msginfo->list_subscribe);
1333         g_free(msginfo->list_unsubscribe);
1334         g_free(msginfo->list_help);
1335         g_free(msginfo->list_archive);
1336         g_free(msginfo->list_owner);
1337
1338         g_free(msginfo->partial_recv);
1339         g_free(msginfo->account_server);
1340         g_free(msginfo->account_login);
1341         
1342         slist_free_strings(msginfo->references);
1343         g_slist_free(msginfo->references);
1344
1345         g_free(msginfo->plaintext_file);
1346
1347         g_free(msginfo);
1348 }
1349
1350 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1351 {
1352         guint memusage = 0;
1353         GSList *refs;
1354         
1355         memusage += sizeof(MsgInfo);
1356         if (msginfo->fromname)
1357                 memusage += strlen(msginfo->fromname);
1358         if (msginfo->date)
1359                 memusage += strlen(msginfo->date);
1360         if (msginfo->from)
1361                 memusage += strlen(msginfo->from);
1362         if (msginfo->to)
1363                 memusage += strlen(msginfo->to);
1364         if (msginfo->cc)
1365                 memusage += strlen(msginfo->cc);
1366         if (msginfo->newsgroups)
1367                 memusage += strlen(msginfo->newsgroups);
1368         if (msginfo->subject)
1369                 memusage += strlen(msginfo->subject);
1370         if (msginfo->msgid)
1371                 memusage += strlen(msginfo->msgid);
1372         if (msginfo->inreplyto)
1373                 memusage += strlen(msginfo->inreplyto);
1374         if (msginfo->xface)
1375                 memusage += strlen(msginfo->xface);
1376         if (msginfo->face)
1377                 memusage += strlen(msginfo->face);
1378         if (msginfo->dispositionnotificationto)
1379                 memusage += strlen(msginfo->dispositionnotificationto);
1380         if (msginfo->returnreceiptto)
1381                 memusage += strlen(msginfo->returnreceiptto);
1382         for (refs = msginfo->references; refs; refs=refs->next) {
1383                 gchar *r = (gchar *)refs->data;
1384                 memusage += r?strlen(r):0;
1385         }
1386         if (msginfo->fromspace)
1387                 memusage += strlen(msginfo->fromspace);
1388
1389         return memusage;
1390 }
1391
1392 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1393 {
1394         const MsgInfo *msginfo1 = a;
1395         const MsgInfo *msginfo2 = b;
1396
1397         if (!msginfo1)
1398                 return -1;
1399         if (!msginfo2)
1400                 return -1;
1401
1402         return msginfo1->msgnum - msginfo2->msgnum;
1403 }
1404
1405 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr)
1406 {
1407         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1408                                        {"SSV:",  NULL, FALSE},
1409                                        {"R:",    NULL, FALSE},
1410                                        {"NG:",   NULL, FALSE},
1411                                        {"MAID:", NULL, FALSE},
1412                                        {"NAID:", NULL, FALSE},
1413                                        {"SCF:",  NULL, FALSE},
1414                                        {"RMID:", NULL, FALSE},
1415                                        {"FMID:", NULL, FALSE},
1416                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1417                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1418                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1419                                        {"X-Sylpheed-End-Special-Headers:", NULL, FALSE},
1420                                        {NULL,    NULL, FALSE}};
1421         FILE *fp;
1422         gint filepos;
1423         gint mailval = 0, newsval = 0;
1424         gchar *from = NULL;
1425         gchar *smtpserver = NULL;
1426         GSList *to_list = NULL;
1427         GSList *newsgroup_list = NULL;
1428         gchar *savecopyfolder = NULL;
1429         gchar *replymessageid = NULL;
1430         gchar *fwdmessageid = NULL;
1431         gchar *privacy_system = NULL;
1432         gboolean encrypt = FALSE;
1433         gchar *encrypt_data = NULL;
1434         gchar buf[BUFFSIZE];
1435         gint hnum;
1436         PrefsAccount *mailac = NULL, *newsac = NULL;
1437         gboolean save_clear_text = TRUE;
1438         gchar *tmp_enc_file = NULL;
1439
1440         int local = 0;
1441
1442         g_return_val_if_fail(file != NULL, -1);
1443
1444         if ((fp = g_fopen(file, "rb")) == NULL) {
1445                 FILE_OP_ERROR(file, "fopen");
1446                 if (errstr) {
1447                         if (*errstr) g_free(*errstr);
1448                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1449                 }
1450                 return -1;
1451         }
1452
1453         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1454                != -1) {
1455                 gchar *p = buf + strlen(qentry[hnum].name);
1456
1457                 switch (hnum) {
1458                 case Q_SENDER:
1459                         if (from == NULL) 
1460                                 from = g_strdup(p);
1461                         break;
1462                 case Q_SMTPSERVER:
1463                         if (smtpserver == NULL) 
1464                                 smtpserver = g_strdup(p);
1465                         break;
1466                 case Q_RECIPIENTS:
1467                         to_list = address_list_append(to_list, p);
1468                         break;
1469                 case Q_NEWSGROUPS:
1470                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1471                         break;
1472                 case Q_MAIL_ACCOUNT_ID:
1473                         mailac = account_find_from_id(atoi(p));
1474                         break;
1475                 case Q_NEWS_ACCOUNT_ID:
1476                         newsac = account_find_from_id(atoi(p));
1477                         break;
1478                 case Q_SAVE_COPY_FOLDER:
1479                         if (savecopyfolder == NULL) 
1480                                 savecopyfolder = g_strdup(p);
1481                         break;
1482                 case Q_REPLY_MESSAGE_ID:
1483                         if (replymessageid == NULL) 
1484                                 replymessageid = g_strdup(p);
1485                         break;
1486                 case Q_FWD_MESSAGE_ID:
1487                         if (fwdmessageid == NULL) 
1488                                 fwdmessageid = g_strdup(p);
1489                         break;
1490                 case Q_PRIVACY_SYSTEM:
1491                         if (privacy_system == NULL) 
1492                                 privacy_system = g_strdup(p);
1493                         break;
1494                 case Q_ENCRYPT:
1495                         if (p[0] == '1') 
1496                                 encrypt = TRUE;
1497                         break;
1498                 case Q_ENCRYPT_DATA:
1499                         if (encrypt_data == NULL) 
1500                                 encrypt_data = g_strdup(p);
1501                         break;
1502                 case Q_SYLPHEED_HDRS:
1503                         /* end of special headers reached */
1504                         goto send_mail; /* can't "break;break;" */
1505                 }
1506         }
1507 send_mail:
1508         filepos = ftell(fp);
1509
1510         if (encrypt) {
1511                 MimeInfo *mimeinfo;
1512
1513                 if (mailac && mailac->save_encrypted_as_clear_text 
1514                 &&  !mailac->encrypt_to_self)
1515                         save_clear_text = TRUE;
1516                 else
1517                         save_clear_text = FALSE;
1518
1519                 fclose(fp);
1520                 fp = NULL;
1521
1522                 mimeinfo = procmime_scan_queue_file(file);
1523                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1524                 || (fp = my_tmpfile()) == NULL
1525                 ||  procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1526                         if (fp)
1527                                 fclose(fp);
1528                         procmime_mimeinfo_free_all(mimeinfo);
1529                         g_free(from);
1530                         g_free(smtpserver);
1531                         slist_free_strings(to_list);
1532                         g_slist_free(to_list);
1533                         slist_free_strings(newsgroup_list);
1534                         g_slist_free(newsgroup_list);
1535                         g_free(savecopyfolder);
1536                         g_free(replymessageid);
1537                         g_free(fwdmessageid);
1538                         g_free(privacy_system);
1539                         g_free(encrypt_data);
1540                         if (errstr) {
1541                                 if (*errstr) g_free(*errstr);
1542                                 *errstr = g_strdup_printf(_("Couldn't encrypt the email: %s"),
1543                                                 privacy_get_error());
1544                         }
1545                         return -1;
1546                 }
1547                 
1548                 rewind(fp);
1549                 if (!save_clear_text) {
1550                         gchar *content = NULL;
1551                         FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1552                         if (tmpfp) {
1553                                 fclose(tmpfp);
1554
1555                                 content = file_read_stream_to_str(fp);
1556                                 rewind(fp);
1557
1558                                 str_write_to_file(content, tmp_enc_file);
1559                                 g_free(content);
1560                         } else {
1561                                 g_warning("couldn't get tempfile\n");
1562                         }
1563                 } 
1564                 
1565                 procmime_mimeinfo_free_all(mimeinfo);
1566                 
1567                 filepos = 0;
1568         }
1569
1570         if (to_list) {
1571                 debug_print("Sending message by mail\n");
1572                 if (!from) {
1573                         if (errstr) {
1574                                 if (*errstr) g_free(*errstr);
1575                                 *errstr = g_strdup_printf(_("Queued message header is broken."));
1576                         }
1577                         mailval = -1;
1578                 } else if (mailac && mailac->use_mail_command &&
1579                            mailac->mail_command && (* mailac->mail_command)) {
1580                         mailval = send_message_local(mailac->mail_command, fp);
1581                         local = 1;
1582                 } else {
1583                         if (!mailac) {
1584                                 mailac = account_find_from_smtp_server(from, smtpserver);
1585                                 if (!mailac) {
1586                                         g_warning("Account not found. "
1587                                                     "Using current account...\n");
1588                                         mailac = cur_account;
1589                                 }
1590                         }
1591
1592                         if (mailac) {
1593                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1594                                 if (mailval == -1 && errstr) {
1595                                         if (*errstr) g_free(*errstr);
1596                                         *errstr = g_strdup_printf(_("An error happened during SMTP session."));
1597                                 }
1598                         } else {
1599                                 PrefsAccount tmp_ac;
1600
1601                                 g_warning("Account not found.\n");
1602
1603                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1604                                 tmp_ac.address = from;
1605                                 tmp_ac.smtp_server = smtpserver;
1606                                 tmp_ac.smtpport = SMTP_PORT;
1607                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1608                                 if (mailval == -1 && errstr) {
1609                                         if (*errstr) g_free(*errstr);
1610                                         *errstr = g_strdup_printf(_("No specific account has been found to "
1611                                                         "send, and an error happened during SMTP session."));
1612                                 }
1613                         }
1614                 }
1615         } else if (!to_list && !newsgroup_list) {
1616                 if (errstr) {
1617                         if (*errstr) g_free(*errstr);
1618                         *errstr = g_strdup(_("Couldn't determine sending informations. "
1619                                 "Maybe the email hasn't been generated by Sylpheed-Claws."));
1620                 }
1621                 mailval = -1;
1622         }
1623
1624         fseek(fp, filepos, SEEK_SET);
1625         if (newsgroup_list && (mailval == 0)) {
1626                 Folder *folder;
1627                 gchar *tmp = NULL;
1628                 FILE *tmpfp;
1629
1630                 /* write to temporary file */
1631                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1632                             G_DIR_SEPARATOR, (gint)file);
1633                 if ((tmpfp = g_fopen(tmp, "wb")) == NULL) {
1634                         FILE_OP_ERROR(tmp, "fopen");
1635                         newsval = -1;
1636                         alertpanel_error(_("Could not create temporary file for news sending."));
1637                 } else {
1638                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1639                                 FILE_OP_ERROR(tmp, "chmod");
1640                                 g_warning("can't change file mode\n");
1641                         }
1642
1643                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1644                                 if (fputs(buf, tmpfp) == EOF) {
1645                                         FILE_OP_ERROR(tmp, "fputs");
1646                                         newsval = -1;
1647                                         if (errstr) {
1648                                                 if (*errstr) g_free(*errstr);
1649                                                 *errstr = g_strdup_printf(_("Error when writing temporary file for news sending."));
1650                                         }
1651                                 }
1652                         }
1653                         fclose(tmpfp);
1654
1655                         if (newsval == 0) {
1656                                 debug_print("Sending message by news\n");
1657
1658                                 folder = FOLDER(newsac->folder);
1659
1660                                 newsval = news_post(folder, tmp);
1661                                 if (newsval < 0 && errstr)  {
1662                                         if (*errstr) g_free(*errstr);
1663                                         *errstr = g_strdup_printf(_("Error occurred while posting the message to %s."),
1664                                          newsac->nntp_server);
1665                                 }
1666                         }
1667                         g_unlink(tmp);
1668                 }
1669                 g_free(tmp);
1670         }
1671
1672         fclose(fp);
1673
1674         /* save message to outbox */
1675         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1676                 FolderItem *outbox;
1677
1678                 debug_print("saving sent message...\n");
1679
1680                 outbox = folder_find_item_from_identifier(savecopyfolder);
1681                 if (!outbox)
1682                         outbox = folder_get_default_outbox();
1683                         
1684                 if (save_clear_text || tmp_enc_file == NULL) {
1685                         procmsg_save_to_outbox(outbox, file, TRUE);
1686                 } else {
1687                         procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1688                 }
1689         }
1690
1691         if (tmp_enc_file != NULL) {
1692                 g_unlink(tmp_enc_file);
1693                 free(tmp_enc_file);
1694                 tmp_enc_file = NULL;
1695         }
1696
1697         if (replymessageid != NULL || fwdmessageid != NULL) {
1698                 gchar **tokens;
1699                 FolderItem *item;
1700                 
1701                 if (replymessageid != NULL)
1702                         tokens = g_strsplit(replymessageid, "\t", 0);
1703                 else
1704                         tokens = g_strsplit(fwdmessageid, "\t", 0);
1705                 item = folder_find_item_from_identifier(tokens[0]);
1706
1707                 /* check if queued message has valid folder and message id */
1708                 if (item != NULL && tokens[2] != NULL) {
1709                         MsgInfo *msginfo;
1710                         
1711                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1712                 
1713                         /* check if referring message exists and has a message id */
1714                         if ((msginfo != NULL) && 
1715                             (msginfo->msgid != NULL) &&
1716                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1717                                 procmsg_msginfo_free(msginfo);
1718                                 msginfo = NULL;
1719                         }
1720                         
1721                         if (msginfo == NULL) {
1722                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1723                         }
1724                         
1725                         if (msginfo != NULL) {
1726                                 if (replymessageid != NULL) {
1727                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1728                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1729                                 }  else {
1730                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1731                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1732                                 }
1733                                 procmsg_msginfo_free(msginfo);
1734                         }
1735                 }
1736                 g_strfreev(tokens);
1737         }
1738
1739         g_free(from);
1740         g_free(smtpserver);
1741         slist_free_strings(to_list);
1742         g_slist_free(to_list);
1743         slist_free_strings(newsgroup_list);
1744         g_slist_free(newsgroup_list);
1745         g_free(savecopyfolder);
1746         g_free(replymessageid);
1747         g_free(fwdmessageid);
1748         g_free(privacy_system);
1749         g_free(encrypt_data);
1750
1751         return (newsval != 0 ? newsval : mailval);
1752 }
1753
1754 gint procmsg_send_message_queue(const gchar *file, gchar **errstr)
1755 {
1756         gint result = procmsg_send_message_queue_full(file, FALSE, errstr);
1757         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1758         return result;
1759 }
1760
1761 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1762 {
1763         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1764
1765         /* NEW flag */
1766         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1767                 item->new_msgs++;
1768         }
1769
1770         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1771                 item->new_msgs--;
1772         }
1773
1774         /* UNREAD flag */
1775         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1776                 item->unread_msgs++;
1777                 if (procmsg_msg_has_marked_parent(msginfo))
1778                         item->unreadmarked_msgs++;
1779         }
1780
1781         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1782                 item->unread_msgs--;
1783                 if (procmsg_msg_has_marked_parent(msginfo))
1784                         item->unreadmarked_msgs--;
1785         }
1786         
1787         /* MARK flag */
1788         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1789                 procmsg_update_unread_children(msginfo, TRUE);
1790                 item->marked_msgs++;
1791         }
1792
1793         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1794                 procmsg_update_unread_children(msginfo, FALSE);
1795                 item->marked_msgs--;
1796         }
1797 }
1798
1799 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1800 {
1801         FolderItem *item;
1802         MsgInfoUpdate msginfo_update;
1803         MsgPermFlags perm_flags_new, perm_flags_old;
1804         MsgTmpFlags tmp_flags_old;
1805
1806         g_return_if_fail(msginfo != NULL);
1807         item = msginfo->folder;
1808         g_return_if_fail(item != NULL);
1809         
1810         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1811
1812         /* Perm Flags handling */
1813         perm_flags_old = msginfo->flags.perm_flags;
1814         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1815         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1816                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1817         }
1818
1819         if (perm_flags_old != perm_flags_new) {
1820                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1821
1822                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1823
1824         }
1825
1826         /* Tmp flags handling */
1827         tmp_flags_old = msginfo->flags.tmp_flags;
1828         msginfo->flags.tmp_flags |= tmp_flags;
1829
1830         /* update notification */
1831         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1832                 msginfo_update.msginfo = msginfo;
1833                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1834                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1835                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1836         }
1837 }
1838
1839 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1840 {
1841         FolderItem *item;
1842         MsgInfoUpdate msginfo_update;
1843         MsgPermFlags perm_flags_new, perm_flags_old;
1844         MsgTmpFlags tmp_flags_old;
1845
1846         g_return_if_fail(msginfo != NULL);
1847         item = msginfo->folder;
1848         g_return_if_fail(item != NULL);
1849         
1850         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1851
1852         /* Perm Flags handling */
1853         perm_flags_old = msginfo->flags.perm_flags;
1854         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1855         
1856         if (perm_flags_old != perm_flags_new) {
1857                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1858
1859                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1860         }
1861
1862         /* Tmp flags hanlding */
1863         tmp_flags_old = msginfo->flags.tmp_flags;
1864         msginfo->flags.tmp_flags &= ~tmp_flags;
1865
1866         /* update notification */
1867         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1868                 msginfo_update.msginfo = msginfo;
1869                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1870                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1871                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1872         }
1873 }
1874
1875 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
1876                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
1877                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
1878 {
1879         FolderItem *item;
1880         MsgInfoUpdate msginfo_update;
1881         MsgPermFlags perm_flags_new, perm_flags_old;
1882         MsgTmpFlags tmp_flags_old;
1883
1884         g_return_if_fail(msginfo != NULL);
1885         item = msginfo->folder;
1886         g_return_if_fail(item != NULL);
1887         
1888         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1889
1890         /* Perm Flags handling */
1891         perm_flags_old = msginfo->flags.perm_flags;
1892         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
1893         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1894                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1895         }
1896
1897         if (perm_flags_old != perm_flags_new) {
1898                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1899
1900                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1901
1902         }
1903
1904         /* Tmp flags handling */
1905         tmp_flags_old = msginfo->flags.tmp_flags;
1906         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
1907         msginfo->flags.tmp_flags |= add_tmp_flags;
1908
1909         /* update notification */
1910         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1911                 msginfo_update.msginfo = msginfo;
1912                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1913                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1914                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1915         }
1916 }
1917
1918 /*!
1919  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1920  *
1921  *\param        info Current message
1922  *\param        perm_flags Flags to be checked
1923  *\param        parentmsgs Hash of prior msgs to avoid loops
1924  *
1925  *\return       gboolean TRUE if perm_flags are found
1926  */
1927 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1928                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1929 {
1930         MsgInfo *tmp;
1931
1932         g_return_val_if_fail(info != NULL, FALSE);
1933
1934         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1935                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1936                                 info->inreplyto);
1937                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1938                         procmsg_msginfo_free(tmp);
1939                         return TRUE;
1940                 } else if (tmp != NULL) {
1941                         gboolean result;
1942
1943                         if (g_hash_table_lookup(parentmsgs, info)) {
1944                                 debug_print("loop detected: %d\n",
1945                                         info->msgnum);
1946                                 result = FALSE;
1947                         } else {
1948                                 g_hash_table_insert(parentmsgs, info, "1");
1949                                 result = procmsg_msg_has_flagged_parent_real(
1950                                     tmp, perm_flags, parentmsgs);
1951                         }
1952                         procmsg_msginfo_free(tmp);
1953                         return result;
1954                 } else {
1955                         return FALSE;
1956                 }
1957         } else
1958                 return FALSE;
1959 }
1960
1961 /*!
1962  *\brief        Callback for cleaning up hash of parentmsgs
1963  */
1964 gboolean parentmsgs_hash_remove(gpointer key,
1965                             gpointer value,
1966                             gpointer user_data)
1967 {
1968         return TRUE;
1969 }
1970
1971 /*!
1972  *\brief        Set up list of parentmsgs
1973  *              See procmsg_msg_has_flagged_parent_real()
1974  */
1975 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1976 {
1977         gboolean result;
1978         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1979
1980         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1981         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1982         g_hash_table_destroy(parentmsgs);
1983         return result;
1984 }
1985
1986 /*!
1987  *\brief        Check if msgs prior in thread are marked
1988  *              See procmsg_msg_has_flagged_parent_real()
1989  */
1990 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1991 {
1992         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1993 }
1994
1995
1996 GSList *procmsg_find_children_func(MsgInfo *info, 
1997                                    GSList *children, GSList *all)
1998 {
1999         GSList *cur;
2000
2001         g_return_val_if_fail(info!=NULL, children);
2002         if (info->msgid == NULL)
2003                 return children;
2004
2005         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2006                 MsgInfo *tmp = (MsgInfo *)cur->data;
2007                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2008                         /* Check if message is already in the list */
2009                         if ((children == NULL) || 
2010                             (g_slist_index(children, tmp) == -1)) {
2011                                 children = g_slist_prepend(children,
2012                                                 procmsg_msginfo_new_ref(tmp));
2013                                 children = procmsg_find_children_func(tmp, 
2014                                                         children, 
2015                                                         all);
2016                         }
2017                 }
2018         }
2019         return children;
2020 }
2021
2022 GSList *procmsg_find_children (MsgInfo *info)
2023 {
2024         GSList *children;
2025         GSList *all, *cur;
2026
2027         g_return_val_if_fail(info!=NULL, NULL);
2028         all = folder_item_get_msg_list(info->folder);
2029         children = procmsg_find_children_func(info, NULL, all);
2030         if (children != NULL) {
2031                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2032                         /* this will not free the used pointers
2033                            created with procmsg_msginfo_new_ref */
2034                         procmsg_msginfo_free((MsgInfo *)cur->data);
2035                 }
2036         }
2037         g_slist_free(all);
2038
2039         return children;
2040 }
2041
2042 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2043 {
2044         GSList *children = procmsg_find_children(info);
2045         GSList *cur;
2046         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2047                 MsgInfo *tmp = (MsgInfo *)cur->data;
2048                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2049                         if(newly_marked) 
2050                                 info->folder->unreadmarked_msgs++;
2051                         else
2052                                 info->folder->unreadmarked_msgs--;
2053                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2054                 }
2055                 procmsg_msginfo_free(tmp);
2056         }
2057         g_slist_free(children);
2058 }
2059
2060 /**
2061  * Set the destination folder for a copy or move operation
2062  *
2063  * \param msginfo The message which's destination folder is changed
2064  * \param to_folder The destination folder for the operation
2065  */
2066 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2067 {
2068         if(msginfo->to_folder != NULL) {
2069                 msginfo->to_folder->op_count--;
2070                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2071         }
2072         msginfo->to_folder = to_folder;
2073         if(to_folder != NULL) {
2074                 to_folder->op_count++;
2075                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2076         }
2077 }
2078
2079 /**
2080  * Apply filtering actions to the msginfo
2081  *
2082  * \param msginfo The MsgInfo describing the message that should be filtered
2083  * \return TRUE if the message was moved and MsgInfo is now invalid,
2084  *         FALSE otherwise
2085  */
2086 gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2087 {
2088         MailFilteringData mail_filtering_data;
2089                         
2090         mail_filtering_data.msginfo = msginfo;                  
2091         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data)) {
2092                 return TRUE;
2093         }
2094
2095         /* filter if enabled in prefs or move to inbox if not */
2096         if((filtering_rules != NULL) &&
2097            filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs)) {
2098                 return TRUE;
2099         }
2100                 
2101         return FALSE;
2102 }
2103
2104 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2105 {
2106         MsgInfo *tmp_msginfo = NULL;
2107         MsgFlags flags = {0, 0};
2108         gchar *tmpfile = get_tmp_file();
2109         FILE *fp = g_fopen(tmpfile, "wb");
2110         
2111         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2112             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2113                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2114                 if (fp) 
2115                         fclose(fp);
2116                 g_free(tmpfile);
2117                 return NULL;
2118         }
2119         
2120         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2121                 fclose(fp);
2122                 fp = NULL;
2123                 tmp_msginfo = procheader_parse_file(
2124                         tmpfile, flags, 
2125                         TRUE, FALSE);
2126         }
2127         if (fp)
2128                 fclose(fp);
2129
2130         if (tmp_msginfo != NULL) {
2131                 if (src_msginfo)
2132                         tmp_msginfo->folder = src_msginfo->folder;
2133                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2134         } else {
2135                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2136         }
2137
2138         g_free(tmpfile);
2139
2140         return tmp_msginfo;
2141 }
2142
2143 static GSList *spam_learners = NULL;
2144
2145 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2146 {
2147         if (!g_slist_find(spam_learners, learn_func))
2148                 spam_learners = g_slist_append(spam_learners, learn_func);
2149         if (mainwindow_get_mainwindow()) {
2150                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2151                 summary_set_menu_sensitive(
2152                         mainwindow_get_mainwindow()->summaryview);
2153                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2154         }
2155 }
2156
2157 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2158 {
2159         spam_learners = g_slist_remove(spam_learners, learn_func);
2160         if (mainwindow_get_mainwindow()) {
2161                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2162                 summary_set_menu_sensitive(
2163                         mainwindow_get_mainwindow()->summaryview);
2164                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2165         }
2166 }
2167
2168 gboolean procmsg_spam_can_learn(void)
2169 {
2170         return g_slist_length(spam_learners) > 0;
2171 }
2172
2173 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2174 {
2175         GSList *cur = spam_learners;
2176         int ret = 0;
2177         for (; cur; cur = cur->next) {
2178                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2179                 ret |= func(info, list, spam);
2180         }
2181         return ret;
2182 }
2183
2184 static gchar *spam_folder_item = NULL;
2185 void procmsg_spam_set_folder (const char *item_identifier)
2186 {
2187         g_free(spam_folder_item);
2188         if (item_identifier)
2189                 spam_folder_item = g_strdup(item_identifier);
2190         else
2191                 spam_folder_item = NULL;
2192 }
2193
2194 FolderItem *procmsg_spam_get_folder (void)
2195 {
2196         FolderItem *item = spam_folder_item ? folder_find_item_from_identifier(spam_folder_item) : NULL;
2197         return item ? item : folder_get_default_trash();
2198 }
2199
2200 static void item_has_queued_mails(FolderItem *item, gpointer data)
2201 {
2202         gboolean *result = (gboolean *)data;
2203         if (*result == TRUE)
2204                 return;
2205         if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0)
2206                 *result = TRUE;
2207 }
2208
2209 gboolean procmsg_have_queued_mails_fast (void)
2210 {
2211         gboolean result = FALSE;
2212         folder_func_to_all_folders(item_has_queued_mails, &result);
2213         return result;
2214 }