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