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