Clear statusbar's progressbar in case of NNTP xhdr/xover failure.
[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
407         return folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
408 }
409
410 gchar *procmsg_get_message_file(MsgInfo *msginfo)
411 {
412         gchar *filename = NULL;
413
414         cm_return_val_if_fail(msginfo != NULL, NULL);
415
416         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
417         if (!filename)
418                 debug_print("can't fetch message %d\n", msginfo->msgnum);
419
420         return filename;
421 }
422
423 gchar *procmsg_get_message_file_full(MsgInfo *msginfo, gboolean headers, gboolean body)
424 {
425         gchar *filename = NULL;
426
427         cm_return_val_if_fail(msginfo != NULL, NULL);
428
429         filename = folder_item_fetch_msg_full(msginfo->folder, msginfo->msgnum,
430                                                 headers, body);
431         if (!filename)
432                 debug_print("can't fetch message %d\n", msginfo->msgnum);
433
434         return filename;
435 }
436
437 GSList *procmsg_get_message_file_list(GSList *mlist)
438 {
439         GSList *file_list = NULL;
440         MsgInfo *msginfo;
441         MsgFileInfo *fileinfo;
442         gchar *file;
443
444         while (mlist != NULL) {
445                 msginfo = (MsgInfo *)mlist->data;
446                 file = procmsg_get_message_file(msginfo);
447                 if (!file) {
448                         procmsg_message_file_list_free(file_list);
449                         return NULL;
450                 }
451                 fileinfo = g_new(MsgFileInfo, 1);
452                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
453                 fileinfo->file = file;
454                 fileinfo->flags = g_new(MsgFlags, 1);
455                 *fileinfo->flags = msginfo->flags;
456                 file_list = g_slist_prepend(file_list, fileinfo);
457                 mlist = mlist->next;
458         }
459
460         file_list = g_slist_reverse(file_list);
461
462         return file_list;
463 }
464
465 void procmsg_message_file_list_free(MsgInfoList *file_list)
466 {
467         GSList *cur;
468         MsgFileInfo *fileinfo;
469
470         for (cur = file_list; cur != NULL; cur = cur->next) {
471                 fileinfo = (MsgFileInfo *)cur->data;
472                 procmsg_msginfo_free(&(fileinfo->msginfo));
473                 g_free(fileinfo->file);
474                 g_free(fileinfo->flags);
475                 g_free(fileinfo);
476         }
477
478         g_slist_free(file_list);
479 }
480
481 FILE *procmsg_open_message(MsgInfo *msginfo)
482 {
483         FILE *fp;
484         gchar *file;
485
486         cm_return_val_if_fail(msginfo != NULL, NULL);
487         
488         file = procmsg_get_message_file_path(msginfo);
489         cm_return_val_if_fail(file != NULL, NULL);
490
491         if (!is_file_exist(file)) {
492                 g_free(file);
493                 file = procmsg_get_message_file(msginfo);
494                 if (!file)
495                         return NULL;
496         }
497
498         if ((fp = g_fopen(file, "rb")) == NULL) {
499                 FILE_OP_ERROR(file, "fopen");
500                 g_free(file);
501                 return NULL;
502         }
503
504         g_free(file);
505
506         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
507                 gchar buf[BUFFSIZE];
508
509                 while (fgets(buf, sizeof(buf), fp) != NULL) {
510                         /* new way */
511                         if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
512                                 strlen("X-Claws-End-Special-Headers:"))) ||
513                             (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
514                                 strlen("X-Sylpheed-End-Special-Headers:"))))
515                                 break;
516                         /* old way */
517                         if (buf[0] == '\r' || buf[0] == '\n') break;
518                         /* from other mailers */
519                         if (!strncmp(buf, "Date: ", 6)
520                         ||  !strncmp(buf, "To: ", 4)
521                         ||  !strncmp(buf, "From: ", 6)
522                         ||  !strncmp(buf, "Subject: ", 9)) {
523                                 rewind(fp);
524                                 break;
525                         }
526                 }
527         }
528
529         return fp;
530 }
531
532 gboolean procmsg_msg_exist(MsgInfo *msginfo)
533 {
534         gchar *path;
535         gboolean ret;
536
537         if (!msginfo) return FALSE;
538
539         path = folder_item_get_path(msginfo->folder);
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 = NULL;
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, fp, qentry)) != -1 && buf != NULL) {
754                 gchar *p = buf + strlen(qentry[hnum].name);
755
756                 if (hnum == Q_MAIL_ACCOUNT_ID) {
757                         mailac = account_find_from_id(atoi(p));
758                         break;
759                 }
760                 g_free(buf);
761                 buf = NULL;
762         }
763         fclose(fp);
764         return mailac;
765 }
766
767 gchar *procmsg_msginfo_get_avatar(MsgInfo *msginfo, gint type)
768 {
769         GSList *mia;
770
771         if (!msginfo || !msginfo->extradata || !msginfo->extradata->avatars)
772                 return NULL;
773
774         for (mia = msginfo->extradata->avatars; mia; mia = mia->next) {
775                 MsgInfoAvatar *avatar = (MsgInfoAvatar *)mia->data;
776                 if (avatar->avatar_id == type)
777                         return avatar->avatar_src;
778         }
779
780         return NULL;
781 }
782
783 void procmsg_msginfo_add_avatar(MsgInfo *msginfo, gint type, const gchar *data)
784 {
785         MsgInfoAvatar *av;
786
787         if (!msginfo->extradata)
788                 msginfo->extradata = g_new0(MsgInfoExtraData, 1);
789
790         av = g_new0(MsgInfoAvatar, 1);
791         av->avatar_id = type;
792         av->avatar_src = g_strdup(data);
793
794         msginfo->extradata->avatars = g_slist_append(msginfo->extradata->avatars, av);
795 }
796
797 gchar *procmsg_msginfo_get_identifier(MsgInfo *msginfo)
798 {
799         gchar *folder_id;
800         const gchar *msgid;
801         gchar *id;
802
803         cm_return_val_if_fail(msginfo != NULL, NULL);
804         folder_id = folder_item_get_identifier(msginfo->folder);
805         msgid = msginfo->msgid;
806
807         id = g_strconcat(folder_id, G_DIR_SEPARATOR_S, msgid, NULL);
808
809         g_free(folder_id);
810
811         return id;
812 }
813
814 MsgInfo *procmsg_get_msginfo_from_identifier(const gchar *id)
815 {
816         gchar *folder_id = g_strdup(id);
817         gchar *separator = strrchr(folder_id, G_DIR_SEPARATOR);
818         const gchar *msgid;
819         FolderItem *item;
820         MsgInfo *msginfo;
821
822         if (separator == NULL) {
823                 g_free(folder_id);
824                 return NULL;
825         }
826
827         *separator = '\0';
828         msgid = separator + 1;
829
830         item = folder_find_item_from_identifier(folder_id);
831
832         if (item == NULL) {
833                 g_free(folder_id);
834                 return NULL;
835         }
836
837         msginfo = folder_item_get_msginfo_by_msgid(item, msgid);
838         g_free(folder_id);
839
840         return msginfo;
841 }
842
843 static GSList *procmsg_list_sort_by_account(FolderItem *queue, GSList *list)
844 {
845         GSList *result = NULL;
846         GSList *orig = NULL;
847         PrefsAccount *last_account = NULL;
848         MsgInfo *msg = NULL;
849         GSList *cur = NULL;
850         gboolean nothing_to_sort = TRUE;
851
852         if (!list)
853                 return NULL;
854
855         orig = g_slist_copy(list);
856         
857         msg = (MsgInfo *)orig->data;
858         
859         for (cur = orig; cur; cur = cur->next)
860                 debug_print("sort before %s\n", ((MsgInfo *)cur->data)->from);
861         
862         debug_print("\n");
863
864 parse_again:    
865         nothing_to_sort = TRUE;
866         cur = orig;
867         while (cur) {
868                 gchar *file = NULL;
869                 PrefsAccount *ac = NULL;
870                 msg = (MsgInfo *)cur->data;
871                 file = folder_item_fetch_msg(queue, msg->msgnum);
872                 ac = procmsg_get_account_from_file(file);
873                 g_free(file);
874
875                 if (last_account == NULL || (ac != NULL && ac == last_account)) {
876                         result = g_slist_append(result, msg);
877                         orig = g_slist_remove(orig, msg);
878                         last_account = ac;
879                         nothing_to_sort = FALSE;
880                         goto parse_again;
881                 }
882                 cur = cur->next;
883         }
884         
885         if (orig && g_slist_length(orig)) {
886                 if (!last_account && nothing_to_sort) {
887                         /* can't find an account for the rest of the list */
888                         cur = orig;
889                         while (cur) {
890                                 result = g_slist_append(result, cur->data);
891                                 cur = cur->next;
892                         }
893                 } else {
894                         last_account = NULL;
895                         goto parse_again;
896                 }
897         }
898         
899         g_slist_free(orig);
900         
901         for (cur = result; cur; cur = cur->next)
902                 debug_print("sort after %s\n", ((MsgInfo *)cur->data)->from);
903
904         debug_print("\n");
905
906         return result;
907 }
908
909 static gboolean procmsg_is_last_for_account(FolderItem *queue, MsgInfo *msginfo, GSList *elem)
910 {
911         gchar *file = folder_item_fetch_msg(queue, msginfo->msgnum);
912         PrefsAccount *ac = procmsg_get_account_from_file(file);
913         GSList *cur;
914         g_free(file);
915         for (cur = elem; cur; cur = cur->next) {
916                 MsgInfo *cur_msginfo = (MsgInfo *)cur->data;
917                 file = folder_item_fetch_msg(queue, cur_msginfo->msgnum);
918                 
919                 if (cur_msginfo != msginfo && !MSG_IS_LOCKED(cur_msginfo->flags)) {
920                         if (procmsg_get_account_from_file(file) == ac) {
921                                 g_free(file);
922                                 return FALSE;
923                         }
924                 }
925                 
926                 g_free(file);
927         }
928         return TRUE;
929 }
930
931 static gboolean send_queue_lock = FALSE;
932
933 gboolean procmsg_queue_lock(char **errstr)
934 {
935         if (send_queue_lock) {
936                 /* Avoid having to translate two similar strings */
937                 log_warning(LOG_PROTOCOL, "%s\n", _("Already trying to send."));
938                 if (errstr) {
939                         if (*errstr) g_free(*errstr);
940                         *errstr = g_strdup_printf(_("Already trying to send."));
941                 }
942                 return FALSE;
943         }
944         send_queue_lock = TRUE;
945         return TRUE;
946 }
947 void procmsg_queue_unlock(void)
948 {
949         send_queue_lock = FALSE;
950 }
951 /*!
952  *\brief        Send messages in queue
953  *
954  *\param        queue Queue folder to process
955  *\param        save_msgs Unused
956  *
957  *\return       Number of messages sent, negative if an error occurred
958  *              positive if no error occurred
959  */
960 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs, gchar **errstr)
961 {
962         gint sent = 0, err = 0;
963         GSList *list, *elem;
964         GSList *sorted_list = NULL;
965         GNode *node, *next;
966         
967         if (!procmsg_queue_lock(errstr)) {
968                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
969                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
970                 return -1;
971         }
972         inc_lock();
973         if (!queue)
974                 queue = folder_get_default_queue();
975         
976         if (queue == NULL) {
977                 procmsg_queue_unlock();
978                 inc_unlock();
979                 return -1;
980         }
981
982         main_window_set_menu_sensitive(mainwindow_get_mainwindow());
983         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
984
985         folder_item_scan(queue);
986         list = folder_item_get_msg_list(queue);
987
988         /* sort the list per sender account; this helps reusing the same SMTP server */
989         sorted_list = procmsg_list_sort_by_account(queue, list);
990         
991         for (elem = sorted_list; elem != NULL; elem = elem->next) {
992                 gchar *file;
993                 MsgInfo *msginfo;
994                         
995                 msginfo = (MsgInfo *)(elem->data);
996                 if (!MSG_IS_LOCKED(msginfo->flags) && !MSG_IS_DELETED(msginfo->flags)) {
997                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
998                         if (file) {
999                                 gboolean queued_removed = FALSE;
1000                                 if (procmsg_send_message_queue_full(file, 
1001                                                 !procmsg_is_last_for_account(queue, msginfo, elem),
1002                                                 errstr, queue, msginfo->msgnum, &queued_removed) < 0) {
1003                                         g_warning("Sending queued message %d failed.",
1004                                                   msginfo->msgnum);
1005                                         err++;
1006                                 } else {
1007                                         sent++; 
1008                                         if (!queued_removed)
1009                                                 folder_item_remove_msg(queue, msginfo->msgnum);
1010                                 }
1011                                 g_free(file);
1012                         }
1013                 }
1014                 /* FIXME: supposedly if only one message is locked, and queue
1015                  * is being flushed, the following free says something like 
1016                  * "freeing msg ## in folder (nil)". */
1017                 procmsg_msginfo_free(&msginfo);
1018         }
1019
1020         g_slist_free(sorted_list);
1021         folder_item_scan(queue);
1022
1023         if (queue->node && queue->node->children) {
1024                 node = queue->node->children;
1025                 while (node != NULL) {
1026                         int res = 0;
1027                         next = node->next;
1028                         send_queue_lock = FALSE;
1029                         res = procmsg_send_queue(FOLDER_ITEM(node->data), save_msgs, errstr);
1030                         send_queue_lock = TRUE;
1031                         if (res < 0) 
1032                                 err = -res;
1033                         else
1034                                 sent += res;
1035                         node = next;
1036                 }
1037         }
1038         procmsg_queue_unlock();
1039         inc_unlock();
1040         main_window_set_menu_sensitive(mainwindow_get_mainwindow());
1041         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1042
1043         return (err != 0 ? -err : sent);
1044 }
1045
1046 gboolean procmsg_is_sending(void)
1047 {
1048         return send_queue_lock;
1049 }
1050
1051 /*!
1052  *\brief        Determine if a queue folder is empty
1053  *
1054  *\param        queue Queue folder to process
1055  *
1056  *\return       TRUE if the queue folder is empty, otherwise return FALSE
1057  */
1058 gboolean procmsg_queue_is_empty(FolderItem *queue)
1059 {
1060         GSList *list;
1061         gboolean res = FALSE;
1062         if (!queue)
1063                 queue = folder_get_default_queue();
1064         cm_return_val_if_fail(queue != NULL, TRUE);
1065
1066         folder_item_scan(queue);
1067         list = folder_item_get_msg_list(queue);
1068         res = (list == NULL);
1069         procmsg_msg_list_free(list);
1070
1071         if (res == TRUE) {
1072                 GNode *node, *next;
1073                 if (queue->node && queue->node->children) {
1074                         node = queue->node->children;
1075                         while (node != NULL) {
1076                                 next = node->next;
1077                                 if (!procmsg_queue_is_empty(FOLDER_ITEM(node->data)))
1078                                         return FALSE;
1079                                 node = next;
1080                         }
1081                 }
1082         }
1083         return res;
1084 }
1085
1086 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
1087 {
1088         FILE *fp, *outfp;
1089         gchar buf[BUFFSIZE];
1090         
1091         if ((fp = g_fopen(in, "rb")) == NULL) {
1092                 FILE_OP_ERROR(in, "fopen");
1093                 return -1;
1094         }
1095         if ((outfp = g_fopen(out, "wb")) == NULL) {
1096                 FILE_OP_ERROR(out, "fopen");
1097                 fclose(fp);
1098                 return -1;
1099         }
1100         while (fgets(buf, sizeof(buf), fp) != NULL) {
1101                 /* new way */
1102                 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
1103                         strlen("X-Claws-End-Special-Headers:"))) ||
1104                     (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
1105                         strlen("X-Sylpheed-End-Special-Headers:"))))
1106                         break;
1107                 /* old way */
1108                 if (buf[0] == '\r' || buf[0] == '\n') break;
1109                 /* from other mailers */
1110                 if (!strncmp(buf, "Date: ", 6)
1111                 ||  !strncmp(buf, "To: ", 4)
1112                 ||  !strncmp(buf, "From: ", 6)
1113                 ||  !strncmp(buf, "Subject: ", 9)) {
1114                         rewind(fp);
1115                         break;
1116                 }
1117         }
1118         while (fgets(buf, sizeof(buf), fp) != NULL) {
1119                 if (fputs(buf, outfp) == EOF) {
1120                         FILE_OP_ERROR(out, "fputs");
1121                         fclose(outfp);
1122                         fclose(fp);
1123                         return -1;
1124                 }
1125         }
1126         fclose(outfp);
1127         fclose(fp);
1128         return 0;
1129 }
1130
1131 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
1132                             gboolean is_queued)
1133 {
1134         gint num;
1135         MsgInfo *msginfo, *tmp_msginfo;
1136         MsgFlags flag = {0, 0};
1137
1138         debug_print("saving sent message...\n");
1139
1140         if (!outbox)
1141                 outbox = folder_get_default_outbox();
1142         cm_return_val_if_fail(outbox != NULL, -1);
1143
1144         /* remove queueing headers */
1145         if (is_queued) {
1146                 gchar tmp[MAXPATHLEN + 1];
1147
1148                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
1149                            get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
1150                 
1151                 if (procmsg_remove_special_headers(file, tmp) !=0)
1152                         return -1;
1153
1154                 folder_item_scan(outbox);
1155                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
1156                         g_warning("can't save message");
1157                         claws_unlink(tmp);
1158                         return -1;
1159                 }
1160         } else {
1161                 folder_item_scan(outbox);
1162                 if ((num = folder_item_add_msg
1163                         (outbox, file, &flag, FALSE)) < 0) {
1164                         g_warning("can't save message");
1165                         return -1;
1166                 }
1167         }
1168         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
1169         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
1170         if (msginfo != NULL) {
1171                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1172                 procmsg_msginfo_free(&msginfo);                 /* refcnt-- */
1173                 /* tmp_msginfo == msginfo */
1174                 if (tmp_msginfo && msginfo->extradata && 
1175                     (msginfo->extradata->dispositionnotificationto || 
1176                      msginfo->extradata->returnreceiptto)) {
1177                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
1178                 }       
1179                 procmsg_msginfo_free(&tmp_msginfo);             /* refcnt-- */
1180         }
1181
1182         return 0;
1183 }
1184
1185
1186 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1187 {
1188         msginfo->refcnt++;
1189         
1190         return msginfo;
1191 }
1192
1193 MsgInfo *procmsg_msginfo_new(void)
1194 {
1195         MsgInfo *newmsginfo;
1196
1197         newmsginfo = g_new0(MsgInfo, 1);
1198         newmsginfo->refcnt = 1;
1199         
1200         return newmsginfo;
1201 }
1202
1203 static MsgInfoAvatar *procmsg_msginfoavatar_copy(MsgInfoAvatar *avatar)
1204 {
1205         MsgInfoAvatar *newavatar;
1206
1207         if (avatar == NULL) return NULL;
1208
1209         newavatar = g_new0(MsgInfoAvatar, 1);
1210         newavatar->avatar_id = avatar->avatar_id;
1211         newavatar->avatar_src = g_strdup(avatar->avatar_src);
1212
1213         return newavatar;
1214 }
1215
1216 static void procmsg_msginfoavatar_free(MsgInfoAvatar *avatar)
1217 {
1218         if (avatar != NULL) {
1219                 if (avatar->avatar_src != NULL)
1220                         g_free(avatar->avatar_src);
1221                 g_free(avatar);
1222         }
1223 }
1224
1225 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1226 {
1227         MsgInfo *newmsginfo;
1228         GSList *refs;
1229
1230         if (msginfo == NULL) return NULL;
1231
1232         newmsginfo = g_new0(MsgInfo, 1);
1233
1234         newmsginfo->refcnt = 1;
1235
1236 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
1237 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
1238                         g_strdup(msginfo->mmb) : NULL
1239
1240         MEMBCOPY(msgnum);
1241         MEMBCOPY(size);
1242         MEMBCOPY(mtime);
1243         MEMBCOPY(date_t);
1244
1245         MEMBCOPY(flags);
1246
1247         MEMBDUP(fromname);
1248
1249         MEMBDUP(date);
1250         MEMBDUP(from);
1251         MEMBDUP(to);
1252         MEMBDUP(cc);
1253         MEMBDUP(newsgroups);
1254         MEMBDUP(subject);
1255         MEMBDUP(msgid);
1256         MEMBDUP(inreplyto);
1257         MEMBDUP(xref);
1258
1259         MEMBCOPY(folder);
1260         MEMBCOPY(to_folder);
1261
1262         if (msginfo->extradata) {
1263                 newmsginfo->extradata = g_new0(MsgInfoExtraData, 1);
1264                 if (msginfo->extradata->avatars) {
1265                         newmsginfo->extradata->avatars = slist_copy_deep(msginfo->extradata->avatars,
1266                                                                 (GCopyFunc) procmsg_msginfoavatar_copy);
1267                 }
1268                 MEMBDUP(extradata->dispositionnotificationto);
1269                 MEMBDUP(extradata->returnreceiptto);
1270                 MEMBDUP(extradata->partial_recv);
1271                 MEMBDUP(extradata->account_server);
1272                 MEMBDUP(extradata->account_login);
1273                 MEMBDUP(extradata->list_post);
1274                 MEMBDUP(extradata->list_subscribe);
1275                 MEMBDUP(extradata->list_unsubscribe);
1276                 MEMBDUP(extradata->list_help);
1277                 MEMBDUP(extradata->list_archive);
1278                 MEMBDUP(extradata->list_owner);
1279                 MEMBDUP(extradata->resent_from);
1280         }
1281
1282         refs = msginfo->references;
1283         for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1284                 newmsginfo->references = g_slist_prepend
1285                         (newmsginfo->references, g_strdup(refs->data)); 
1286         }
1287         newmsginfo->references = g_slist_reverse(newmsginfo->references);
1288
1289         MEMBCOPY(score);
1290         MEMBDUP(plaintext_file);
1291
1292         return newmsginfo;
1293 }
1294
1295 MsgInfo *procmsg_msginfo_get_full_info_from_file(MsgInfo *msginfo, const gchar *file)
1296 {
1297         MsgInfo *full_msginfo;
1298
1299         if (msginfo == NULL) return NULL;
1300
1301         if (!file || !is_file_exist(file)) {
1302                 g_warning("procmsg_msginfo_get_full_info_from_file(): can't get message file.");
1303                 return NULL;
1304         }
1305
1306         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1307         if (!full_msginfo) return NULL;
1308
1309         msginfo->total_size = full_msginfo->total_size;
1310         msginfo->planned_download = full_msginfo->planned_download;
1311
1312         if (full_msginfo->extradata) {
1313                 if (!msginfo->extradata)
1314                         msginfo->extradata = g_new0(MsgInfoExtraData, 1);
1315                 if (!msginfo->extradata->list_post)
1316                         msginfo->extradata->list_post = g_strdup(full_msginfo->extradata->list_post);
1317                 if (!msginfo->extradata->list_subscribe)
1318                         msginfo->extradata->list_subscribe = g_strdup(full_msginfo->extradata->list_subscribe);
1319                 if (!msginfo->extradata->list_unsubscribe)
1320                         msginfo->extradata->list_unsubscribe = g_strdup(full_msginfo->extradata->list_unsubscribe);
1321                 if (!msginfo->extradata->list_help)
1322                         msginfo->extradata->list_help = g_strdup(full_msginfo->extradata->list_help);
1323                 if (!msginfo->extradata->list_archive)
1324                         msginfo->extradata->list_archive= g_strdup(full_msginfo->extradata->list_archive);
1325                 if (!msginfo->extradata->list_owner)
1326                         msginfo->extradata->list_owner = g_strdup(full_msginfo->extradata->list_owner);
1327                 if (!msginfo->extradata->avatars)
1328                         msginfo->extradata->avatars = slist_copy_deep(full_msginfo->extradata->avatars,
1329                                                                         (GCopyFunc) procmsg_msginfoavatar_copy);
1330                 if (!msginfo->extradata->dispositionnotificationto)
1331                         msginfo->extradata->dispositionnotificationto = 
1332                                 g_strdup(full_msginfo->extradata->dispositionnotificationto);
1333                 if (!msginfo->extradata->returnreceiptto)
1334                         msginfo->extradata->returnreceiptto = g_strdup
1335                                 (full_msginfo->extradata->returnreceiptto);
1336                 if (!msginfo->extradata->partial_recv && full_msginfo->extradata->partial_recv)
1337                         msginfo->extradata->partial_recv = g_strdup
1338                                 (full_msginfo->extradata->partial_recv);
1339                 if (!msginfo->extradata->account_server && full_msginfo->extradata->account_server)
1340                         msginfo->extradata->account_server = g_strdup
1341                                 (full_msginfo->extradata->account_server);
1342                 if (!msginfo->extradata->account_login && full_msginfo->extradata->account_login)
1343                         msginfo->extradata->account_login = g_strdup
1344                                 (full_msginfo->extradata->account_login);
1345                 if (!msginfo->extradata->resent_from && full_msginfo->extradata->resent_from)
1346                         msginfo->extradata->resent_from = g_strdup
1347                                 (full_msginfo->extradata->resent_from);
1348         }
1349         procmsg_msginfo_free(&full_msginfo);
1350
1351         return procmsg_msginfo_new_ref(msginfo);
1352 }
1353
1354 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1355 {
1356         MsgInfo *full_msginfo;
1357         gchar *file;
1358
1359         if (msginfo == NULL) return NULL;
1360
1361         file = procmsg_get_message_file_path(msginfo);
1362         if (!file || !is_file_exist(file)) {
1363                 g_free(file);
1364                 file = procmsg_get_message_file(msginfo);
1365         }
1366         if (!file || !is_file_exist(file)) {
1367                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.");
1368                 return NULL;
1369         }
1370
1371         full_msginfo = procmsg_msginfo_get_full_info_from_file(msginfo, file);
1372         g_free(file);
1373         return full_msginfo;
1374 }
1375
1376 #define FREENULL(n) { g_free(n); n = NULL; }
1377 void procmsg_msginfo_free(MsgInfo **msginfo_ptr)
1378 {
1379         MsgInfo *msginfo = *msginfo_ptr;
1380
1381         if (msginfo == NULL) return;
1382
1383         msginfo->refcnt--;
1384         if (msginfo->refcnt > 0)
1385                 return;
1386
1387         if (msginfo->to_folder) {
1388                 msginfo->to_folder->op_count--;
1389                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1390         }
1391
1392         FREENULL(msginfo->fromspace);
1393
1394         FREENULL(msginfo->fromname);
1395
1396         FREENULL(msginfo->date);
1397         FREENULL(msginfo->from);
1398         FREENULL(msginfo->to);
1399         FREENULL(msginfo->cc);
1400         FREENULL(msginfo->newsgroups);
1401         FREENULL(msginfo->subject);
1402         FREENULL(msginfo->msgid);
1403         FREENULL(msginfo->inreplyto);
1404         FREENULL(msginfo->xref);
1405
1406         if (msginfo->extradata) {
1407                 if (msginfo->extradata->avatars) {
1408                         g_slist_foreach(msginfo->extradata->avatars,
1409                                         (GFunc)procmsg_msginfoavatar_free,
1410                                         NULL);
1411                         g_slist_free(msginfo->extradata->avatars);
1412                         msginfo->extradata->avatars = NULL;
1413                 }
1414                 FREENULL(msginfo->extradata->returnreceiptto);
1415                 FREENULL(msginfo->extradata->dispositionnotificationto);
1416                 FREENULL(msginfo->extradata->list_post);
1417                 FREENULL(msginfo->extradata->list_subscribe);
1418                 FREENULL(msginfo->extradata->list_unsubscribe);
1419                 FREENULL(msginfo->extradata->list_help);
1420                 FREENULL(msginfo->extradata->list_archive);
1421                 FREENULL(msginfo->extradata->list_owner);
1422                 FREENULL(msginfo->extradata->partial_recv);
1423                 FREENULL(msginfo->extradata->account_server);
1424                 FREENULL(msginfo->extradata->account_login);
1425                 FREENULL(msginfo->extradata->resent_from);
1426                 FREENULL(msginfo->extradata);
1427         }
1428         slist_free_strings_full(msginfo->references);
1429         msginfo->references = NULL;
1430         g_slist_free(msginfo->tags);
1431         msginfo->tags = NULL;
1432
1433         FREENULL(msginfo->plaintext_file);
1434
1435         g_free(msginfo);
1436         *msginfo_ptr = NULL;
1437 }
1438 #undef FREENULL
1439
1440 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1441 {
1442         guint memusage = 0;
1443         GSList *tmp;
1444         
1445         memusage += sizeof(MsgInfo);
1446         if (msginfo->fromname)
1447                 memusage += strlen(msginfo->fromname);
1448         if (msginfo->date)
1449                 memusage += strlen(msginfo->date);
1450         if (msginfo->from)
1451                 memusage += strlen(msginfo->from);
1452         if (msginfo->to)
1453                 memusage += strlen(msginfo->to);
1454         if (msginfo->cc)
1455                 memusage += strlen(msginfo->cc);
1456         if (msginfo->newsgroups)
1457                 memusage += strlen(msginfo->newsgroups);
1458         if (msginfo->subject)
1459                 memusage += strlen(msginfo->subject);
1460         if (msginfo->msgid)
1461                 memusage += strlen(msginfo->msgid);
1462         if (msginfo->inreplyto)
1463                 memusage += strlen(msginfo->inreplyto);
1464
1465         for (tmp = msginfo->references; tmp; tmp=tmp->next) {
1466                 gchar *r = (gchar *)tmp->data;
1467                 memusage += r?strlen(r):0 + sizeof(GSList);
1468         }
1469         if (msginfo->fromspace)
1470                 memusage += strlen(msginfo->fromspace);
1471
1472         for (tmp = msginfo->tags; tmp; tmp=tmp->next) {
1473                 memusage += sizeof(GSList);
1474         }
1475         if (msginfo->extradata) {
1476                 memusage += sizeof(MsgInfoExtraData);
1477                 if (msginfo->extradata->avatars) {
1478                         for (tmp = msginfo->extradata->avatars; tmp; tmp = tmp->next) {
1479                                 MsgInfoAvatar *avt = (MsgInfoAvatar *)tmp->data;
1480                                 memusage += (avt->avatar_src)? strlen(avt->avatar_src): 0;
1481                                 memusage += sizeof(MsgInfoAvatar) + sizeof(GSList);
1482                         }
1483                 }
1484                 if (msginfo->extradata->dispositionnotificationto)
1485                         memusage += strlen(msginfo->extradata->dispositionnotificationto);
1486                 if (msginfo->extradata->returnreceiptto)
1487                         memusage += strlen(msginfo->extradata->returnreceiptto);
1488
1489                 if (msginfo->extradata->partial_recv)
1490                         memusage += strlen(msginfo->extradata->partial_recv);
1491                 if (msginfo->extradata->account_server)
1492                         memusage += strlen(msginfo->extradata->account_server);
1493                 if (msginfo->extradata->account_login)
1494                         memusage += strlen(msginfo->extradata->account_login);
1495                 if (msginfo->extradata->resent_from)
1496                         memusage += strlen(msginfo->extradata->resent_from);
1497
1498                 if (msginfo->extradata->list_post)
1499                         memusage += strlen(msginfo->extradata->list_post);
1500                 if (msginfo->extradata->list_subscribe)
1501                         memusage += strlen(msginfo->extradata->list_subscribe);
1502                 if (msginfo->extradata->list_unsubscribe)
1503                         memusage += strlen(msginfo->extradata->list_unsubscribe);
1504                 if (msginfo->extradata->list_help)
1505                         memusage += strlen(msginfo->extradata->list_help);
1506                 if (msginfo->extradata->list_archive)
1507                         memusage += strlen(msginfo->extradata->list_archive);
1508                 if (msginfo->extradata->list_owner)
1509                         memusage += strlen(msginfo->extradata->list_owner);
1510         }
1511         return memusage;
1512 }
1513
1514 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr,
1515                                             FolderItem *queue, gint msgnum, gboolean *queued_removed)
1516 {
1517         static HeaderEntry qentry[] = {
1518                                        {"S:",    NULL, FALSE}, /* 0 */
1519                                        {"SSV:",  NULL, FALSE},
1520                                        {"R:",    NULL, FALSE},
1521                                        {"NG:",   NULL, FALSE},
1522                                        {"MAID:", NULL, FALSE},
1523                                        {"NAID:", NULL, FALSE}, /* 5 */
1524                                        {"SCF:",  NULL, FALSE},
1525                                        {"RMID:", NULL, FALSE},
1526                                        {"FMID:", NULL, FALSE},
1527                                        {"X-Claws-Privacy-System:", NULL, FALSE},
1528                                        {"X-Claws-Encrypt:", NULL, FALSE}, /* 10 */
1529                                        {"X-Claws-Encrypt-Data:", NULL, FALSE},
1530                                        {"X-Claws-End-Special-Headers:", NULL, FALSE},
1531                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1532                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1533                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE}, /* 15 */
1534                                        {"X-Sylpheed-End-Special-Headers:", NULL, FALSE},
1535                                        {NULL,    NULL, FALSE}};
1536         FILE *fp;
1537         gint filepos;
1538         gint mailval = 0, newsval = 0;
1539         gchar *from = NULL;
1540         gchar *smtpserver = NULL;
1541         GSList *to_list = NULL;
1542         GSList *newsgroup_list = NULL;
1543         gchar *savecopyfolder = NULL;
1544         gchar *replymessageid = NULL;
1545         gchar *fwdmessageid = NULL;
1546         gchar *buf;
1547         gint hnum;
1548         PrefsAccount *mailac = NULL, *newsac = NULL;
1549         gboolean encrypt = FALSE;
1550         FolderItem *outbox;
1551
1552         cm_return_val_if_fail(file != NULL, -1);
1553
1554         if ((fp = g_fopen(file, "rb")) == NULL) {
1555                 FILE_OP_ERROR(file, "fopen");
1556                 if (errstr) {
1557                         if (*errstr) g_free(*errstr);
1558                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1559                 }
1560                 return -1;
1561         }
1562
1563         while ((hnum = procheader_get_one_field(&buf, fp, qentry)) != -1 && buf != NULL) {
1564                 gchar *p = buf + strlen(qentry[hnum].name);
1565
1566                 switch (hnum) {
1567                 case Q_SENDER:
1568                         if (from == NULL) 
1569                                 from = g_strdup(p);
1570                         break;
1571                 case Q_SMTPSERVER:
1572                         if (smtpserver == NULL) 
1573                                 smtpserver = g_strdup(p);
1574                         break;
1575                 case Q_RECIPIENTS:
1576                         to_list = address_list_append(to_list, p);
1577                         break;
1578                 case Q_NEWSGROUPS:
1579                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1580                         break;
1581                 case Q_MAIL_ACCOUNT_ID:
1582                         mailac = account_find_from_id(atoi(p));
1583                         break;
1584                 case Q_NEWS_ACCOUNT_ID:
1585                         newsac = account_find_from_id(atoi(p));
1586                         break;
1587                 case Q_SAVE_COPY_FOLDER:
1588                         if (savecopyfolder == NULL) 
1589                                 savecopyfolder = g_strdup(p);
1590                         break;
1591                 case Q_REPLY_MESSAGE_ID:
1592                         if (replymessageid == NULL) 
1593                                 replymessageid = g_strdup(p);
1594                         break;
1595                 case Q_FWD_MESSAGE_ID:
1596                         if (fwdmessageid == NULL) 
1597                                 fwdmessageid = g_strdup(p);
1598                         break;
1599                 case Q_ENCRYPT:
1600                 case Q_ENCRYPT_OLD:
1601                         if (p[0] == '1') 
1602                                 encrypt = TRUE;
1603                         break;
1604                 case Q_CLAWS_HDRS:
1605                 case Q_CLAWS_HDRS_OLD:
1606                         /* end of special headers reached */
1607                         g_free(buf);
1608                         goto send_mail; /* can't "break;break;" */
1609                 }
1610                 g_free(buf);
1611         }
1612
1613 send_mail:
1614         filepos = ftell(fp);
1615         if (filepos < 0) {
1616                 FILE_OP_ERROR(file, "ftell");
1617                 if (errstr) {
1618                         if (*errstr) g_free(*errstr);
1619                         *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1620                 }
1621                 return -1;
1622         }
1623
1624         if (to_list) {
1625                 debug_print("Sending message by mail\n");
1626                 if (!from) {
1627                         if (errstr) {
1628                                 if (*errstr) g_free(*errstr);
1629                                 *errstr = g_strdup_printf(_("Queued message header is broken."));
1630                         }
1631                         mailval = -1;
1632                 } else if (mailac && mailac->use_mail_command &&
1633                            mailac->mail_command && (* mailac->mail_command)) {
1634                         mailval = send_message_local(mailac->mail_command, fp);
1635                 } else {
1636                         if (!mailac) {
1637                                 mailac = account_find_from_smtp_server(from, smtpserver);
1638                                 if (!mailac) {
1639                                         g_warning("Account not found. "
1640                                                     "Using current account...");
1641                                         mailac = cur_account;
1642                                 }
1643                         }
1644
1645                         if (mailac) {
1646                                 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1647                                 if (mailval == -1 && errstr) {
1648                                         if (*errstr) g_free(*errstr);
1649                                         *errstr = g_strdup_printf(_("An error happened during SMTP session."));
1650                                 }
1651                         } else {
1652                                 PrefsAccount tmp_ac;
1653
1654                                 g_warning("Account not found.");
1655
1656                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1657                                 tmp_ac.address = from;
1658                                 tmp_ac.smtp_server = smtpserver;
1659                                 tmp_ac.smtpport = SMTP_PORT;
1660                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1661                                 if (mailval == -1 && errstr) {
1662                                         if (*errstr) g_free(*errstr);
1663                                         *errstr = g_strdup_printf(_("No specific account has been found to "
1664                                                         "send, and an error happened during SMTP session."));
1665                                 }
1666                         }
1667                 }
1668         } else if (!to_list && !newsgroup_list) {
1669                 if (errstr) {
1670                         if (*errstr) g_free(*errstr);
1671                         *errstr = g_strdup(_("Couldn't determine sending information. "
1672                                 "Maybe the email hasn't been generated by Claws Mail."));
1673                 }
1674                 mailval = -1;
1675         }
1676
1677         if (fseek(fp, filepos, SEEK_SET) < 0) {
1678                 FILE_OP_ERROR(file, "fseek");
1679                 mailval = -1;
1680         }
1681
1682         if (newsgroup_list && newsac && (mailval == 0)) {
1683                 Folder *folder;
1684                 gchar *tmp = NULL;
1685                 gchar buf[BUFFSIZE];
1686                 FILE *tmpfp;
1687
1688                 /* write to temporary file */
1689                 tmp = g_strdup_printf("%s%cnntp%p", get_tmp_dir(),
1690                             G_DIR_SEPARATOR, file);
1691                 if ((tmpfp = g_fopen(tmp, "wb")) == NULL) {
1692                         FILE_OP_ERROR(tmp, "fopen");
1693                         newsval = -1;
1694                         alertpanel_error(_("Couldn't create temporary file for news sending."));
1695                 } else {
1696                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1697                                 FILE_OP_ERROR(tmp, "chmod");
1698                                 g_warning("can't change file mode");
1699                         }
1700
1701                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1702                                 if (fputs(buf, tmpfp) == EOF) {
1703                                         FILE_OP_ERROR(tmp, "fputs");
1704                                         newsval = -1;
1705                                         if (errstr) {
1706                                                 if (*errstr) g_free(*errstr);
1707                                                 *errstr = g_strdup_printf(_("Error when writing temporary file for news sending."));
1708                                         }
1709                                 }
1710                         }
1711                         fclose(tmpfp);
1712
1713                         if (newsval == 0) {
1714                                 debug_print("Sending message by news\n");
1715
1716                                 folder = FOLDER(newsac->folder);
1717
1718                                 newsval = news_post(folder, tmp);
1719                                 if (newsval < 0 && errstr)  {
1720                                         if (*errstr) g_free(*errstr);
1721                                         *errstr = g_strdup_printf(_("Error occurred while posting the message to %s."),
1722                                          newsac->nntp_server);
1723                                 }
1724                         }
1725                         claws_unlink(tmp);
1726                 }
1727                 g_free(tmp);
1728         }
1729
1730         fclose(fp);
1731
1732         /* update session statistics */
1733         if (mailval == 0 && newsval == 0) {
1734                 /* update session stats */
1735                 if (replymessageid)
1736                         session_stats.replied++;
1737                 else if (fwdmessageid)
1738                         session_stats.forwarded++;
1739                 else
1740                         session_stats.sent++;
1741         }
1742
1743         /* save message to outbox */
1744         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1745                 debug_print("saving sent message...\n");
1746
1747                 if (!encrypt || !mailac->save_encrypted_as_clear_text) {
1748                         outbox = folder_find_item_from_identifier(savecopyfolder);
1749                         if (!outbox)
1750                                 outbox = folder_get_default_outbox();
1751
1752                         /* Mail was not saved to outbox before encrypting, save it now. */
1753                         gboolean saved = FALSE;
1754                         *queued_removed = FALSE;
1755                         if (queue && msgnum > 0) {
1756                                 MsgInfo *queued_mail = folder_item_get_msginfo(queue, msgnum);
1757                                 if (folder_item_move_msg(outbox, queued_mail) >= 0) {
1758                                         debug_print("moved queued mail %d to sent folder\n", msgnum);
1759                                         saved = TRUE;
1760                                         *queued_removed = TRUE;
1761                                 } else if (folder_item_copy_msg(outbox, queued_mail) >= 0) {
1762                                         debug_print("copied queued mail %d to sent folder\n", msgnum);
1763                                         saved = TRUE;
1764                                 }
1765                                 procmsg_msginfo_free(&queued_mail);
1766                         }
1767                         if (!saved) {
1768                                 debug_print("resaving queued mail to sent folder\n");
1769                                 procmsg_save_to_outbox(outbox, file, TRUE);
1770                         }
1771                 }
1772         }
1773
1774         if (replymessageid != NULL || fwdmessageid != NULL) {
1775                 gchar **tokens;
1776                 FolderItem *item;
1777                 
1778                 if (replymessageid != NULL)
1779                         tokens = g_strsplit(replymessageid, "\t", 0);
1780                 else
1781                         tokens = g_strsplit(fwdmessageid, "\t", 0);
1782                 item = folder_find_item_from_identifier(tokens[0]);
1783
1784                 /* check if queued message has valid folder and message id */
1785                 if (item != NULL && tokens[2] != NULL) {
1786                         MsgInfo *msginfo;
1787                         
1788                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1789                 
1790                         /* check if referring message exists and has a message id */
1791                         if ((msginfo != NULL) && 
1792                             (msginfo->msgid != NULL) &&
1793                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1794                                 procmsg_msginfo_free(&msginfo);
1795                                 msginfo = NULL;
1796                         }
1797                         
1798                         if (msginfo == NULL) {
1799                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1800                         }
1801                         
1802                         if (msginfo != NULL) {
1803                                 if (replymessageid != NULL) {
1804                                         MsgPermFlags to_unset = 0;
1805
1806                                         if (prefs_common.mark_as_read_on_new_window)
1807                                                 to_unset = (MSG_NEW|MSG_UNREAD);
1808
1809                                         procmsg_msginfo_unset_flags(msginfo, to_unset, 0);
1810                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1811                                 }  else {
1812                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1813                                 }
1814                                 procmsg_msginfo_free(&msginfo);
1815                         }
1816                 }
1817                 g_strfreev(tokens);
1818         }
1819
1820         g_free(from);
1821         g_free(smtpserver);
1822         slist_free_strings_full(to_list);
1823         slist_free_strings_full(newsgroup_list);
1824         g_free(savecopyfolder);
1825         g_free(replymessageid);
1826         g_free(fwdmessageid);
1827
1828         return (newsval != 0 ? newsval : mailval);
1829 }
1830
1831 gint procmsg_send_message_queue(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1832 {
1833         gint result = procmsg_send_message_queue_full(file, FALSE, errstr, queue, msgnum, queued_removed);
1834         main_window_set_menu_sensitive(mainwindow_get_mainwindow());
1835         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1836         return result;
1837 }
1838
1839 gint procmsg_send_message_queue_with_lock(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1840 {
1841         gint val;
1842         if (procmsg_queue_lock(errstr)) {
1843                 val = procmsg_send_message_queue(file, errstr, queue, msgnum, queued_removed);
1844                 procmsg_queue_unlock();
1845                 return val;
1846         }
1847         return -1;
1848 }
1849
1850 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1851 {
1852         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1853
1854         /* NEW flag */
1855         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1856                 item->new_msgs++;
1857         }
1858
1859         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1860                 item->new_msgs--;
1861         }
1862
1863         /* UNREAD flag */
1864         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1865                 item->unread_msgs++;
1866                 if (procmsg_msg_has_marked_parent(msginfo))
1867                         item->unreadmarked_msgs++;
1868         }
1869
1870         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1871                 item->unread_msgs--;
1872                 if (procmsg_msg_has_marked_parent(msginfo))
1873                         item->unreadmarked_msgs--;
1874         }
1875         
1876         /* MARK flag */
1877         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1878                 procmsg_update_unread_children(msginfo, TRUE);
1879                 item->marked_msgs++;
1880         }
1881
1882         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1883                 procmsg_update_unread_children(msginfo, FALSE);
1884                 item->marked_msgs--;
1885         }
1886
1887         if (!(old_flags & MSG_REPLIED) && (new_flags & MSG_REPLIED)) {
1888                 item->replied_msgs++;
1889         }
1890
1891         if ((old_flags & MSG_REPLIED) && !(new_flags & MSG_REPLIED)) {
1892                 item->replied_msgs--;
1893         }
1894
1895         if (!(old_flags & MSG_FORWARDED) && (new_flags & MSG_FORWARDED)) {
1896                 item->forwarded_msgs++;
1897         }
1898
1899         if ((old_flags & MSG_FORWARDED) && !(new_flags & MSG_FORWARDED)) {
1900                 item->forwarded_msgs--;
1901         }
1902
1903         if (!(old_flags & MSG_LOCKED) && (new_flags & MSG_LOCKED)) {
1904                 item->locked_msgs++;
1905         }
1906
1907         if ((old_flags & MSG_LOCKED) && !(new_flags & MSG_LOCKED)) {
1908                 item->locked_msgs--;
1909         }
1910
1911         if ((old_flags & MSG_IGNORE_THREAD) && !(new_flags & MSG_IGNORE_THREAD)) {
1912                 item->ignored_msgs--;
1913         }
1914
1915         if (!(old_flags & MSG_IGNORE_THREAD) && (new_flags & MSG_IGNORE_THREAD)) {
1916                 item->ignored_msgs++;
1917         }
1918
1919         if ((old_flags & MSG_WATCH_THREAD) && !(new_flags & MSG_WATCH_THREAD)) {
1920                 item->watched_msgs--;
1921         }
1922
1923         if (!(old_flags & MSG_WATCH_THREAD) && (new_flags & MSG_WATCH_THREAD)) {
1924                 item->watched_msgs++;
1925         }
1926 }
1927
1928 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1929 {
1930         FolderItem *item;
1931         MsgInfoUpdate msginfo_update;
1932         MsgPermFlags perm_flags_new, perm_flags_old;
1933         MsgTmpFlags tmp_flags_old;
1934
1935         cm_return_if_fail(msginfo != NULL);
1936         item = msginfo->folder;
1937         cm_return_if_fail(item != NULL);
1938         
1939         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1940
1941         /* Perm Flags handling */
1942         perm_flags_old = msginfo->flags.perm_flags;
1943         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1944         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1945                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1946         }
1947         if ((perm_flags & MSG_WATCH_THREAD) || (perm_flags_old & MSG_WATCH_THREAD)) {
1948                 perm_flags_new &= ~(MSG_IGNORE_THREAD);
1949         }
1950
1951         if (perm_flags_old != perm_flags_new) {
1952                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1953
1954                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1955                 summary_update_unread(mainwindow_get_mainwindow()->summaryview, NULL);
1956         }
1957
1958         /* Tmp flags handling */
1959         tmp_flags_old = msginfo->flags.tmp_flags;
1960         msginfo->flags.tmp_flags |= tmp_flags;
1961
1962         /* update notification */
1963         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1964                 msginfo_update.msginfo = msginfo;
1965                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1966                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1967                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1968         }
1969 }
1970
1971 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1972 {
1973         FolderItem *item;
1974         MsgInfoUpdate msginfo_update;
1975         MsgPermFlags perm_flags_new, perm_flags_old;
1976         MsgTmpFlags tmp_flags_old;
1977
1978         cm_return_if_fail(msginfo != NULL);
1979         item = msginfo->folder;
1980         cm_return_if_fail(item != NULL);
1981         
1982         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1983
1984         /* Perm Flags handling */
1985         perm_flags_old = msginfo->flags.perm_flags;
1986         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1987         
1988         if (perm_flags_old != perm_flags_new) {
1989                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1990
1991                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1992         }
1993
1994         /* Tmp flags hanlding */
1995         tmp_flags_old = msginfo->flags.tmp_flags;
1996         msginfo->flags.tmp_flags &= ~tmp_flags;
1997
1998         /* update notification */
1999         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
2000                 msginfo_update.msginfo = msginfo;
2001                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
2002                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2003                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
2004         }
2005 }
2006
2007 void procmsg_msginfo_change_flags(MsgInfo *msginfo, 
2008                                 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
2009                                 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
2010 {
2011         FolderItem *item;
2012         MsgInfoUpdate msginfo_update;
2013         MsgPermFlags perm_flags_new, perm_flags_old;
2014         MsgTmpFlags tmp_flags_old;
2015
2016         cm_return_if_fail(msginfo != NULL);
2017         item = msginfo->folder;
2018         cm_return_if_fail(item != NULL);
2019         
2020         debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
2021
2022         /* Perm Flags handling */
2023         perm_flags_old = msginfo->flags.perm_flags;
2024         perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
2025         if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
2026                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
2027         }
2028         if ((add_perm_flags & MSG_WATCH_THREAD) || (perm_flags_old & MSG_WATCH_THREAD)) {
2029                 perm_flags_new &= ~(MSG_IGNORE_THREAD);
2030         }
2031
2032         if (perm_flags_old != perm_flags_new) {
2033                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
2034
2035                 update_folder_msg_counts(item, msginfo, perm_flags_old);
2036
2037         }
2038
2039         /* Tmp flags handling */
2040         tmp_flags_old = msginfo->flags.tmp_flags;
2041         msginfo->flags.tmp_flags &= ~rem_tmp_flags;
2042         msginfo->flags.tmp_flags |= add_tmp_flags;
2043
2044         /* update notification */
2045         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
2046                 msginfo_update.msginfo = msginfo;
2047                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
2048                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2049                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
2050         }
2051 }
2052
2053 /*!
2054  *\brief        check for flags (e.g. mark) in prior msgs of current thread
2055  *
2056  *\param        info Current message
2057  *\param        perm_flags Flags to be checked
2058  *\param        parentmsgs Hash of prior msgs to avoid loops
2059  *
2060  *\return       gboolean TRUE if perm_flags are found
2061  */
2062 static gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
2063                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
2064 {
2065         MsgInfo *tmp;
2066
2067         cm_return_val_if_fail(info != NULL, FALSE);
2068
2069         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
2070                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
2071                                 info->inreplyto);
2072                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
2073                         procmsg_msginfo_free(&tmp);
2074                         return TRUE;
2075                 } else if (tmp != NULL) {
2076                         gboolean result;
2077
2078                         if (g_hash_table_lookup(parentmsgs, info)) {
2079                                 debug_print("loop detected: %d\n",
2080                                         info->msgnum);
2081                                 result = FALSE;
2082                         } else {
2083                                 g_hash_table_insert(parentmsgs, info, "1");
2084                                 result = procmsg_msg_has_flagged_parent_real(
2085                                     tmp, perm_flags, parentmsgs);
2086                         }
2087                         procmsg_msginfo_free(&tmp);
2088                         return result;
2089                 } else {
2090                         return FALSE;
2091                 }
2092         } else
2093                 return FALSE;
2094 }
2095
2096 /*!
2097  *\brief        Callback for cleaning up hash of parentmsgs
2098  */
2099 static gboolean parentmsgs_hash_remove(gpointer key,
2100                             gpointer value,
2101                             gpointer user_data)
2102 {
2103         return TRUE;
2104 }
2105
2106 /*!
2107  *\brief        Set up list of parentmsgs
2108  *              See procmsg_msg_has_flagged_parent_real()
2109  */
2110 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
2111 {
2112         gboolean result;
2113         static GHashTable *parentmsgs = NULL;
2114         
2115         if (parentmsgs == NULL)
2116                 parentmsgs = g_hash_table_new(NULL, NULL); 
2117
2118         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2119         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2120
2121         return result;
2122 }
2123
2124 /*!
2125  *\brief        Check if msgs prior in thread are marked
2126  *              See procmsg_msg_has_flagged_parent_real()
2127  */
2128 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2129 {
2130         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2131 }
2132
2133
2134 static GSList *procmsg_find_children_func(MsgInfo *info, 
2135                                    GSList *children, GSList *all)
2136 {
2137         GSList *cur;
2138
2139         cm_return_val_if_fail(info!=NULL, children);
2140         if (info->msgid == NULL)
2141                 return children;
2142
2143         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2144                 MsgInfo *tmp = (MsgInfo *)cur->data;
2145                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2146                         /* Check if message is already in the list */
2147                         if ((children == NULL) || 
2148                             (g_slist_index(children, tmp) == -1)) {
2149                                 children = g_slist_prepend(children,
2150                                                 procmsg_msginfo_new_ref(tmp));
2151                                 children = procmsg_find_children_func(tmp, 
2152                                                         children, 
2153                                                         all);
2154                         }
2155                 }
2156         }
2157         return children;
2158 }
2159
2160 static GSList *procmsg_find_children (MsgInfo *info)
2161 {
2162         GSList *children;
2163         GSList *all, *cur;
2164
2165         cm_return_val_if_fail(info!=NULL, NULL);
2166         all = folder_item_get_msg_list(info->folder);
2167         children = procmsg_find_children_func(info, NULL, all);
2168         if (children != NULL) {
2169                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2170                         /* this will not free the used pointers
2171                            created with procmsg_msginfo_new_ref */
2172                         procmsg_msginfo_free((MsgInfo **)&(cur->data));
2173                 }
2174         }
2175         g_slist_free(all);
2176
2177         return children;
2178 }
2179
2180 static void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2181 {
2182         GSList *children = procmsg_find_children(info);
2183         GSList *cur;
2184         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2185                 MsgInfo *tmp = (MsgInfo *)cur->data;
2186                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2187                         if(newly_marked) 
2188                                 info->folder->unreadmarked_msgs++;
2189                         else
2190                                 info->folder->unreadmarked_msgs--;
2191                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2192                 }
2193                 procmsg_msginfo_free(&tmp);
2194         }
2195         g_slist_free(children);
2196 }
2197
2198 /**
2199  * Set the destination folder for a copy or move operation
2200  *
2201  * \param msginfo The message which's destination folder is changed
2202  * \param to_folder The destination folder for the operation
2203  */
2204 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2205 {
2206         if(msginfo->to_folder != NULL) {
2207                 msginfo->to_folder->op_count--;
2208                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2209         }
2210         msginfo->to_folder = to_folder;
2211         if(to_folder != NULL) {
2212                 to_folder->op_count++;
2213                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2214         }
2215 }
2216
2217 /**
2218  * Apply filtering actions to the msginfo
2219  *
2220  * \param msginfo The MsgInfo describing the message that should be filtered
2221  * \return TRUE if the message was moved and MsgInfo is now invalid,
2222  *         FALSE otherwise
2223  */
2224 static gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2225 {
2226         MailFilteringData mail_filtering_data;
2227                         
2228         mail_filtering_data.msginfo = msginfo;                  
2229         mail_filtering_data.msglist = NULL;                     
2230         mail_filtering_data.filtered = NULL;                    
2231         mail_filtering_data.unfiltered = NULL;
2232         mail_filtering_data.account = ac_prefs; 
2233
2234         if (!ac_prefs || ac_prefs->filterhook_on_recv)
2235                 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
2236                 return TRUE;
2237
2238         /* filter if enabled in prefs or move to inbox if not */
2239         if((filtering_rules != NULL) &&
2240                 filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs,
2241                                 FILTERING_INCORPORATION, NULL)) {
2242                 return TRUE;
2243         }
2244                 
2245         return FALSE;
2246 }
2247
2248 void procmsg_msglist_filter(GSList *list, PrefsAccount *ac, 
2249                             GSList **filtered, GSList **unfiltered,
2250                             gboolean do_filter)
2251 {
2252         GSList *cur, *to_do = NULL;
2253         gint total = 0, curnum = 0;
2254         MailFilteringData mail_filtering_data;
2255                         
2256         cm_return_if_fail(filtered != NULL);
2257         cm_return_if_fail(unfiltered != NULL);
2258
2259         *filtered = NULL;
2260         *unfiltered = NULL;
2261         
2262         if (list == NULL)
2263                 return;
2264
2265         total = g_slist_length(list);
2266
2267         if (!do_filter) {
2268                 *filtered = NULL;
2269                 *unfiltered = g_slist_copy(list);
2270                 return;
2271         }
2272
2273         statusbar_print_all(_("Filtering messages...\n"));
2274
2275         mail_filtering_data.msginfo = NULL;                     
2276         mail_filtering_data.msglist = list;                     
2277         mail_filtering_data.filtered = NULL;                    
2278         mail_filtering_data.unfiltered = NULL;  
2279         mail_filtering_data.account = ac;       
2280                         
2281         if (!ac || ac->filterhook_on_recv)
2282         hooks_invoke(MAIL_LISTFILTERING_HOOKLIST, &mail_filtering_data);
2283         
2284         if (mail_filtering_data.filtered == NULL &&
2285             mail_filtering_data.unfiltered == NULL) {
2286                 /* nothing happened */
2287                 debug_print(MAIL_LISTFILTERING_HOOKLIST " did nothing. filtering whole list normally.\n");
2288                 to_do = list;
2289         } 
2290         if (mail_filtering_data.filtered != NULL) {
2291                 /* keep track of what's been filtered by the hooks */
2292                 debug_print(MAIL_LISTFILTERING_HOOKLIST " filtered some stuff. total %d filtered %d unfilt %d.\n",
2293                         g_slist_length(list),
2294                         g_slist_length(mail_filtering_data.filtered),
2295                         g_slist_length(mail_filtering_data.unfiltered));
2296
2297                 *filtered = g_slist_copy(mail_filtering_data.filtered);
2298         }
2299         if (mail_filtering_data.unfiltered != NULL) {
2300                 /* what the hooks didn't handle will go in filtered or 
2301                  * unfiltered in the next loop */
2302                 debug_print(MAIL_LISTFILTERING_HOOKLIST " left unfiltered stuff. total %d filtered %d unfilt %d.\n",
2303                         g_slist_length(list),
2304                         g_slist_length(mail_filtering_data.filtered),
2305                         g_slist_length(mail_filtering_data.unfiltered));
2306                 to_do = mail_filtering_data.unfiltered;
2307         } 
2308
2309         for (cur = to_do; cur; cur = cur->next) {
2310                 MsgInfo *info = (MsgInfo *)cur->data;
2311                 if (procmsg_msginfo_filter(info, ac))
2312                         *filtered = g_slist_prepend(*filtered, info);
2313                 else
2314                         *unfiltered = g_slist_prepend(*unfiltered, info);
2315                 statusbar_progress_all(curnum++, total, prefs_common.statusbar_update_step);
2316         }
2317
2318         g_slist_free(mail_filtering_data.filtered);
2319         g_slist_free(mail_filtering_data.unfiltered);
2320         
2321         *filtered = g_slist_reverse(*filtered);
2322         *unfiltered = g_slist_reverse(*unfiltered);
2323
2324         statusbar_progress_all(0,0,0);
2325         statusbar_pop_all();
2326 }
2327
2328 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2329 {
2330         MsgInfo *tmp_msginfo = NULL;
2331         MsgFlags flags = {0, 0};
2332         gchar *tmpfile = get_tmp_file();
2333         FILE *fp = g_fopen(tmpfile, "wb");
2334         
2335         if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2336             g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2337                 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2338                 if (fp) 
2339                         fclose(fp);
2340                 g_free(tmpfile);
2341                 return NULL;
2342         }
2343         
2344         if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2345                 fclose(fp);
2346                 fp = NULL;
2347                 tmp_msginfo = procheader_parse_file(
2348                         tmpfile, flags, 
2349                         TRUE, FALSE);
2350         }
2351         if (fp)
2352                 fclose(fp);
2353
2354         if (tmp_msginfo != NULL) {
2355                 if (src_msginfo)
2356                         tmp_msginfo->folder = src_msginfo->folder;
2357                 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2358         } else {
2359                 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");
2360         }
2361
2362         g_free(tmpfile);
2363
2364         return tmp_msginfo;
2365 }
2366
2367 static GSList *spam_learners = NULL;
2368
2369 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2370 {
2371         if (!g_slist_find(spam_learners, learn_func))
2372                 spam_learners = g_slist_append(spam_learners, learn_func);
2373         if (mainwindow_get_mainwindow()) {
2374                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2375                 summary_set_menu_sensitive(
2376                         mainwindow_get_mainwindow()->summaryview);
2377                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2378         }
2379 }
2380
2381 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2382 {
2383         spam_learners = g_slist_remove(spam_learners, learn_func);
2384         if (mainwindow_get_mainwindow()) {
2385                 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2386                 summary_set_menu_sensitive(
2387                         mainwindow_get_mainwindow()->summaryview);
2388                 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2389         }
2390 }
2391
2392 gboolean procmsg_spam_can_learn(void)
2393 {
2394         return g_slist_length(spam_learners) > 0;
2395 }
2396
2397 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2398 {
2399         GSList *cur = spam_learners;
2400         int ret = 0;
2401         for (; cur; cur = cur->next) {
2402                 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2403                 ret |= func(info, list, spam);
2404         }
2405         return ret;
2406 }
2407
2408 static gchar *spam_folder_item = NULL;
2409 static FolderItem * (*procmsg_spam_get_folder_func)(MsgInfo *msginfo) = NULL;
2410 void procmsg_spam_set_folder (const char *item_identifier, FolderItem *(*spam_get_folder_func)(MsgInfo *info))
2411 {
2412         g_free(spam_folder_item);
2413         if (item_identifier)
2414                 spam_folder_item = g_strdup(item_identifier);
2415         else
2416                 spam_folder_item = NULL;
2417         if (spam_get_folder_func != NULL)
2418                 procmsg_spam_get_folder_func = spam_get_folder_func;
2419         else
2420                 procmsg_spam_get_folder_func = NULL;
2421 }
2422
2423 FolderItem *procmsg_spam_get_folder (MsgInfo *msginfo)
2424 {
2425         FolderItem *item = NULL;
2426         
2427         if (procmsg_spam_get_folder_func) 
2428                 item = procmsg_spam_get_folder_func(msginfo);
2429         if (item == NULL && spam_folder_item)
2430                 item = folder_find_item_from_identifier(spam_folder_item);
2431         if (item == NULL)
2432                 item = folder_get_default_trash();
2433         return item;
2434 }
2435
2436 static void item_has_queued_mails(FolderItem *item, gpointer data)
2437 {
2438         gboolean *result = (gboolean *)data;
2439         if (*result == TRUE)
2440                 return;
2441         if (folder_has_parent_of_type(item, F_QUEUE)) {
2442                 if (item->total_msgs == 0)
2443                         return;
2444                 else {
2445                         GSList *msglist = folder_item_get_msg_list(item);
2446                         GSList *cur;
2447                         for (cur = msglist; cur; cur = cur->next) {
2448                                 MsgInfo *msginfo = (MsgInfo *)cur->data;
2449                                 if (!MSG_IS_DELETED(msginfo->flags) &&
2450                                     !MSG_IS_LOCKED(msginfo->flags)) {
2451                                         *result = TRUE;
2452                                         break;
2453                                 }
2454                         }
2455                         procmsg_msg_list_free(msglist);
2456                 }
2457         }
2458 }
2459
2460 gboolean procmsg_have_queued_mails_fast (void)
2461 {
2462         gboolean result = FALSE;
2463         folder_func_to_all_folders(item_has_queued_mails, &result);
2464         return result;
2465 }
2466
2467 static void item_has_trashed_mails(FolderItem *item, gpointer data)
2468 {
2469         gboolean *result = (gboolean *)data;
2470         if (*result == TRUE)
2471                 return;
2472         if (folder_has_parent_of_type(item, F_TRASH) && item->total_msgs > 0)
2473                 *result = TRUE;
2474 }
2475
2476 gboolean procmsg_have_trashed_mails_fast (void)
2477 {
2478         gboolean result = FALSE;
2479         folder_func_to_all_folders(item_has_trashed_mails, &result);
2480         return result;
2481 }
2482
2483 gchar *procmsg_msginfo_get_tags_str(MsgInfo *msginfo)
2484 {
2485         GSList *cur = NULL;
2486         gchar *tags = NULL;
2487         
2488         if (!msginfo)
2489                 return NULL;
2490
2491         if (msginfo->tags == NULL)
2492                 return NULL;
2493         for (cur = msginfo->tags; cur; cur = cur->next) {
2494                 const gchar *tag = tags_get_tag(GPOINTER_TO_INT(cur->data));
2495                 if (!tag)
2496                         continue;
2497                 if (!tags)
2498                         tags = g_strdup(tag);
2499                 else {
2500                         int olen = strlen(tags);
2501                         int nlen = olen + strlen(tag) + 2 /* strlen(", ") */;
2502                         tags = g_realloc(tags, nlen+1);
2503                         if (!tags)
2504                                 return NULL;
2505                         strcpy(tags+olen, ", ");
2506                         strcpy(tags+olen+2, tag);
2507                         tags[nlen]='\0';
2508                 }
2509         }
2510         return tags;
2511 }
2512
2513 void procmsg_msginfo_update_tags(MsgInfo *msginfo, gboolean set, gint id)
2514 {
2515         GSList changed;
2516
2517         if (id == 0)
2518                 return;
2519
2520         if (!set) {
2521                 msginfo->tags = g_slist_remove(
2522                                         msginfo->tags,
2523                                         GINT_TO_POINTER(id));
2524                 changed.data = GINT_TO_POINTER(id);
2525                 changed.next = NULL;
2526                 folder_item_commit_tags(msginfo->folder, msginfo, NULL, &changed);
2527         } else {
2528                 if (!g_slist_find(msginfo->tags, GINT_TO_POINTER(id))) {
2529                         msginfo->tags = g_slist_append(
2530                                         msginfo->tags,
2531                                         GINT_TO_POINTER(id));
2532                 }
2533                 changed.data = GINT_TO_POINTER(id);
2534                 changed.next = NULL;
2535                 folder_item_commit_tags(msginfo->folder, msginfo, &changed, NULL);
2536         }
2537         
2538 }
2539
2540 void procmsg_msginfo_clear_tags(MsgInfo *msginfo)
2541 {
2542         GSList *unset = msginfo->tags;
2543         msginfo->tags = NULL;
2544         folder_item_commit_tags(msginfo->folder, msginfo, NULL, unset);
2545         g_slist_free(unset);
2546 }