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