2004-10-08 [christoph] 0.9.12cvs123
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "defs.h"
21
22 #include <glib.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26
27 #include "intl.h"
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
46 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
47 {
48         GHashTable *msg_table;
49
50         if (mlist == NULL) return NULL;
51
52         msg_table = g_hash_table_new(NULL, g_direct_equal);
53         procmsg_msg_hash_table_append(msg_table, mlist);
54
55         return msg_table;
56 }
57
58 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
59 {
60         GSList *cur;
61         MsgInfo *msginfo;
62
63         if (msg_table == NULL || mlist == NULL) return;
64
65         for (cur = mlist; cur != NULL; cur = cur->next) {
66                 msginfo = (MsgInfo *)cur->data;
67
68                 g_hash_table_insert(msg_table,
69                                     GUINT_TO_POINTER(msginfo->msgnum),
70                                     msginfo);
71         }
72 }
73
74 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
75 {
76         GHashTable *msg_table;
77         GSList *cur;
78         MsgInfo *msginfo;
79
80         if (mlist == NULL) return NULL;
81
82         msg_table = g_hash_table_new(NULL, g_direct_equal);
83
84         for (cur = mlist; cur != NULL; cur = cur->next) {
85                 msginfo = (MsgInfo *)cur->data;
86                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
87         }
88
89         return msg_table;
90 }
91
92 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
93 {
94         GSList *cur;
95         MsgInfo *msginfo;
96         gint last = 0;
97
98         for (cur = mlist; cur != NULL; cur = cur->next) {
99                 msginfo = (MsgInfo *)cur->data;
100                 if (msginfo && msginfo->msgnum > last)
101                         last = msginfo->msgnum;
102         }
103
104         return last;
105 }
106
107 void procmsg_msg_list_free(GSList *mlist)
108 {
109         GSList *cur;
110         MsgInfo *msginfo;
111
112         for (cur = mlist; cur != NULL; cur = cur->next) {
113                 msginfo = (MsgInfo *)cur->data;
114                 procmsg_msginfo_free(msginfo);
115         }
116         g_slist_free(mlist);
117 }
118
119 struct MarkSum {
120         gint *new_msgs;
121         gint *unread_msgs;
122         gint *total_msgs;
123         gint *min;
124         gint *max;
125         gint first;
126 };
127
128 /* CLAWS subject threading:
129   
130   in the first round it inserts subject lines in a 
131   relation (subject <-> node)
132
133   the second round finishes the threads by attaching
134   matching subject lines to the one found in the
135   relation. will use the oldest node with the same
136   subject that is not more then thread_by_subject_max_age
137   days old (see subject_relation_lookup)
138 */  
139
140 static void subject_relation_insert(GRelation *relation, GNode *node)
141 {
142         gchar *subject;
143         MsgInfo *msginfo;
144
145         g_return_if_fail(relation != NULL);
146         g_return_if_fail(node != NULL);
147         msginfo = (MsgInfo *) node->data;
148         g_return_if_fail(msginfo != NULL);
149
150         subject = msginfo->subject;
151         if (subject == NULL)
152                 return;
153         subject += subject_get_prefix_length(subject);
154
155         g_relation_insert(relation, subject, node);
156 }
157
158 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
159 {
160         gchar *subject;
161         GTuples *tuples;
162         GNode *node = NULL;
163         gint prefix_length;
164     
165         g_return_val_if_fail(relation != NULL, NULL);
166
167         subject = msginfo->subject;
168         if (subject == NULL)
169                 return NULL;
170         prefix_length = subject_get_prefix_length(subject);
171         if (prefix_length <= 0)
172                 return NULL;
173         subject += prefix_length;
174         
175         tuples = g_relation_select(relation, subject, 0);
176         if (tuples == NULL)
177                 return NULL;
178
179         if (tuples->len > 0) {
180                 int i;
181                 GNode *relation_node;
182                 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
183                 gboolean match;
184
185                 /* check all nodes with the same subject to find the best parent */
186                 for (i = 0; i < tuples->len; i++) {
187                         relation_node = (GNode *) g_tuples_index(tuples, i, 1);
188                         relation_msginfo = (MsgInfo *) relation_node->data;
189                         match = FALSE;
190
191                         /* best node should be the oldest in the found nodes */
192                         /* parent node must not be older then msginfo */
193                         if ((relation_msginfo->date_t < msginfo->date_t) &&
194                             ((best_msginfo == NULL) ||
195                              (best_msginfo->date_t > relation_msginfo->date_t)))
196                                 match = TRUE;
197
198                         /* parent node must not be more then thread_by_subject_max_age
199                            days older then msginfo */
200                         if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
201                             prefs_common.thread_by_subject_max_age * 3600 * 24)
202                                 match = FALSE;
203
204                         /* can add new tests for all matching
205                            nodes found by subject */
206
207                         if (match) {
208                                 node = relation_node;
209                                 best_msginfo = relation_msginfo;
210                         }
211                 }           
212         }
213
214         g_tuples_destroy(tuples);
215         return node;
216 }
217
218 /* return the reversed thread tree */
219 GNode *procmsg_get_thread_tree(GSList *mlist)
220 {
221         GNode *root, *parent, *node, *next;
222         GHashTable *msgid_table;
223         GRelation *subject_relation;
224         MsgInfo *msginfo;
225         const gchar *msgid;
226
227         root = g_node_new(NULL);
228         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
229         subject_relation = g_relation_new(2);
230         g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
231
232         for (; mlist != NULL; mlist = mlist->next) {
233                 msginfo = (MsgInfo *)mlist->data;
234                 parent = root;
235
236                 if (msginfo->inreplyto) {
237                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
238                         if (parent == NULL) {
239                                 parent = root;
240                         }
241                 }
242                 node = g_node_insert_data_before
243                         (parent, parent == root ? parent->children : NULL,
244                          msginfo);
245                 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
246                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
247
248                 /* CLAWS: add subject to relation (without prefix) */
249                 if (prefs_common.thread_by_subject) {
250                         subject_relation_insert(subject_relation, node);
251                 }
252         }
253
254         /* complete the unfinished threads */
255         for (node = root->children; node != NULL; ) {
256                 parent = NULL;
257                 next = node->next;
258                 msginfo = (MsgInfo *)node->data;
259                 if (msginfo->inreplyto) { 
260                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
261                         /* node should not be the parent, and node should not 
262                            be an ancestor of parent (circular reference) */
263                         if (parent && parent != node && 
264                             !g_node_is_ancestor(node, parent)) {
265                                 g_node_unlink(node);
266                                 g_node_insert_before
267                                         (parent, parent->children, node);
268                         }                               
269                 }
270                 node = next;
271         }
272
273         if (prefs_common.thread_by_subject) {
274                 for (node = root->children; node && node != NULL;) {
275                         next = node->next;
276                         msginfo = (MsgInfo *) node->data;
277                         
278                         parent = subject_relation_lookup(subject_relation, msginfo);
279                         
280                         /* the node may already be threaded by IN-REPLY-TO, so go up 
281                          * in the tree to 
282                            find the parent node */
283                         if (parent != NULL) {
284                                 if (g_node_is_ancestor(node, parent))
285                                         parent = NULL;
286                                 if (parent == node)
287                                         parent = NULL;
288                         }
289                         
290                         if (parent) {
291                                 g_node_unlink(node);
292                                 g_node_append(parent, node);
293                         }
294
295                         node = next;
296                 }       
297         }
298         
299         g_relation_destroy(subject_relation);
300         g_hash_table_destroy(msgid_table);
301
302         return root;
303 }
304
305 void procmsg_move_messages(GSList *mlist)
306 {
307         GSList *cur, *movelist = NULL;
308         MsgInfo *msginfo;
309         FolderItem *dest = NULL;
310
311         if (!mlist) return;
312
313         folder_item_update_freeze();
314
315         for (cur = mlist; cur != NULL; cur = cur->next) {
316                 msginfo = (MsgInfo *)cur->data;
317                 if (!dest) {
318                         dest = msginfo->to_folder;
319                         movelist = g_slist_append(movelist, msginfo);
320                 } else if (dest == msginfo->to_folder) {
321                         movelist = g_slist_append(movelist, msginfo);
322                 } else {
323                         folder_item_move_msgs(dest, movelist);
324                         g_slist_free(movelist);
325                         movelist = NULL;
326                         dest = msginfo->to_folder;
327                         movelist = g_slist_append(movelist, msginfo);
328                 }
329                 procmsg_msginfo_set_to_folder(msginfo, NULL);
330         }
331
332         if (movelist) {
333                 folder_item_move_msgs(dest, movelist);
334                 g_slist_free(movelist);
335         }
336
337         folder_item_update_thaw();
338 }
339
340 void procmsg_copy_messages(GSList *mlist)
341 {
342         GSList *cur, *copylist = NULL;
343         MsgInfo *msginfo;
344         FolderItem *dest = NULL;
345
346         if (!mlist) return;
347
348         folder_item_update_freeze();
349
350         for (cur = mlist; cur != NULL; cur = cur->next) {
351                 msginfo = (MsgInfo *)cur->data;
352                 if (!dest) {
353                         dest = msginfo->to_folder;
354                         copylist = g_slist_append(copylist, msginfo);
355                 } else if (dest == msginfo->to_folder) {
356                         copylist = g_slist_append(copylist, msginfo);
357                 } else {
358                         folder_item_copy_msgs(dest, copylist);
359                         g_slist_free(copylist);
360                         copylist = NULL;
361                         dest = msginfo->to_folder;
362                         copylist = g_slist_append(copylist, msginfo);
363                 }
364                 procmsg_msginfo_set_to_folder(msginfo, NULL);
365         }
366
367         if (copylist) {
368                 folder_item_copy_msgs(dest, copylist);
369                 g_slist_free(copylist);
370         }
371
372         folder_item_update_thaw();
373 }
374
375 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
376 {
377         gchar *file;
378
379         g_return_val_if_fail(msginfo != NULL, NULL);
380
381         if (msginfo->plaintext_file)
382                 file = g_strdup(msginfo->plaintext_file);
383         else {
384                 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
385         }
386
387         return file;
388 }
389
390 gchar *procmsg_get_message_file(MsgInfo *msginfo)
391 {
392         gchar *filename = NULL;
393
394         g_return_val_if_fail(msginfo != NULL, NULL);
395
396         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
397         if (!filename)
398                 debug_print("can't fetch message %d\n", msginfo->msgnum);
399
400         return filename;
401 }
402
403 GSList *procmsg_get_message_file_list(GSList *mlist)
404 {
405         GSList *file_list = NULL;
406         MsgInfo *msginfo;
407         MsgFileInfo *fileinfo;
408         gchar *file;
409
410         while (mlist != NULL) {
411                 msginfo = (MsgInfo *)mlist->data;
412                 file = procmsg_get_message_file(msginfo);
413                 if (!file) {
414                         procmsg_message_file_list_free(file_list);
415                         return NULL;
416                 }
417                 fileinfo = g_new(MsgFileInfo, 1);
418                 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
419                 fileinfo->file = file;
420                 fileinfo->flags = g_new(MsgFlags, 1);
421                 *fileinfo->flags = msginfo->flags;
422                 file_list = g_slist_prepend(file_list, fileinfo);
423                 mlist = mlist->next;
424         }
425
426         file_list = g_slist_reverse(file_list);
427
428         return file_list;
429 }
430
431 void procmsg_message_file_list_free(MsgInfoList *file_list)
432 {
433         GSList *cur;
434         MsgFileInfo *fileinfo;
435
436         for (cur = file_list; cur != NULL; cur = cur->next) {
437                 fileinfo = (MsgFileInfo *)cur->data;
438                 procmsg_msginfo_free(fileinfo->msginfo);
439                 g_free(fileinfo->file);
440                 g_free(fileinfo->flags);
441                 g_free(fileinfo);
442         }
443
444         g_slist_free(file_list);
445 }
446
447 FILE *procmsg_open_message(MsgInfo *msginfo)
448 {
449         FILE *fp;
450         gchar *file;
451
452         g_return_val_if_fail(msginfo != NULL, NULL);
453
454         file = procmsg_get_message_file_path(msginfo);
455         g_return_val_if_fail(file != NULL, NULL);
456
457         if (!is_file_exist(file)) {
458                 g_free(file);
459                 file = procmsg_get_message_file(msginfo);
460                 if (!file)
461                         return NULL;
462         }
463
464         if ((fp = fopen(file, "rb")) == NULL) {
465                 FILE_OP_ERROR(file, "fopen");
466                 g_free(file);
467                 return NULL;
468         }
469
470         g_free(file);
471
472         if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
473                 gchar buf[BUFFSIZE];
474
475                 while (fgets(buf, sizeof(buf), fp) != NULL)
476                         if (buf[0] == '\r' || buf[0] == '\n') break;
477         }
478
479         return fp;
480 }
481
482 gboolean procmsg_msg_exist(MsgInfo *msginfo)
483 {
484         gchar *path;
485         gboolean ret;
486
487         if (!msginfo) return FALSE;
488
489         path = folder_item_get_path(msginfo->folder);
490         change_dir(path);
491         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
492         g_free(path);
493
494         return ret;
495 }
496
497 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
498                                 PrefsFilterType type)
499 {
500         static HeaderEntry hentry[] = {{"X-BeenThere:",    NULL, TRUE},
501                                        {"X-ML-Name:",      NULL, TRUE},
502                                        {"X-List:",         NULL, TRUE},
503                                        {"X-Mailing-list:", NULL, TRUE},
504                                        {"List-Id:",        NULL, TRUE},
505                                        {"X-Sequence:",     NULL, TRUE},
506                                        {NULL,              NULL, FALSE}};
507         enum
508         {
509                 H_X_BEENTHERE    = 0,
510                 H_X_ML_NAME      = 1,
511                 H_X_LIST         = 2,
512                 H_X_MAILING_LIST = 3,
513                 H_LIST_ID        = 4,
514                 H_X_SEQUENCE     = 5
515         };
516
517         FILE *fp;
518
519         g_return_if_fail(msginfo != NULL);
520         g_return_if_fail(header != NULL);
521         g_return_if_fail(key != NULL);
522
523         *header = NULL;
524         *key = NULL;
525
526         switch (type) {
527         case FILTER_BY_NONE:
528                 return;
529         case FILTER_BY_AUTO:
530                 if ((fp = procmsg_open_message(msginfo)) == NULL)
531                         return;
532                 procheader_get_header_fields(fp, hentry);
533                 fclose(fp);
534
535 #define SET_FILTER_KEY(hstr, idx)       \
536 {                                       \
537         *header = g_strdup(hstr);       \
538         *key = hentry[idx].body;        \
539         hentry[idx].body = NULL;        \
540 }
541
542                 if (hentry[H_X_BEENTHERE].body != NULL) {
543                         SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
544                 } else if (hentry[H_X_ML_NAME].body != NULL) {
545                         SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
546                 } else if (hentry[H_X_LIST].body != NULL) {
547                         SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
548                 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
549                         SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
550                 } else if (hentry[H_LIST_ID].body != NULL) {
551                         SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
552                         extract_list_id_str(*key);
553                 } else if (hentry[H_X_SEQUENCE].body != NULL) {
554                         guchar *p;
555
556                         SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
557                         p = *key;
558                         while (*p != '\0') {
559                                 while (*p != '\0' && !isspace(*p)) p++;
560                                 while (isspace(*p)) p++;
561                                 if (isdigit(*p)) {
562                                         *p = '\0';
563                                         break;
564                                 }
565                         }
566                         g_strstrip(*key);
567                 } else if (msginfo->subject) {
568                         *header = g_strdup("subject");
569                         *key = g_strdup(msginfo->subject);
570                 }
571
572 #undef SET_FILTER_KEY
573
574                 g_free(hentry[H_X_BEENTHERE].body);
575                 hentry[H_X_BEENTHERE].body = NULL;
576                 g_free(hentry[H_X_ML_NAME].body);
577                 hentry[H_X_ML_NAME].body = NULL;
578                 g_free(hentry[H_X_LIST].body);
579                 hentry[H_X_LIST].body = NULL;
580                 g_free(hentry[H_X_MAILING_LIST].body);
581                 hentry[H_X_MAILING_LIST].body = NULL;
582                 g_free(hentry[H_LIST_ID].body);
583                 hentry[H_LIST_ID].body = NULL;
584
585                 break;
586         case FILTER_BY_FROM:
587                 *header = g_strdup("from");
588                 *key = g_strdup(msginfo->from);
589                 break;
590         case FILTER_BY_TO:
591                 *header = g_strdup("to");
592                 *key = g_strdup(msginfo->to);
593                 break;
594         case FILTER_BY_SUBJECT:
595                 *header = g_strdup("subject");
596                 *key = g_strdup(msginfo->subject);
597                 break;
598         default:
599                 break;
600         }
601 }
602
603 void procmsg_empty_trash(void)
604 {
605         FolderItem *trash;
606         GList *cur;
607
608         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
609                 trash = FOLDER(cur->data)->trash;
610                 if (trash && trash->total_msgs > 0) {
611                         GSList *mlist = folder_item_get_msg_list(trash);
612                         GSList *cur;
613                         for (cur = mlist ; cur != NULL ; cur = cur->next) {
614                                 MsgInfo * msginfo = (MsgInfo *) cur->data;
615                                 partial_mark_for_delete(msginfo);
616                                 procmsg_msginfo_free(msginfo);
617                         }
618
619                         folder_item_remove_all_msg(trash);
620                 }
621         }
622 }
623
624 /*!
625  *\brief        Send messages in queue
626  *
627  *\param        queue Queue folder to process
628  *\param        save_msgs Unused
629  *
630  *\return       Number of messages sent, negative if an error occurred
631  *              positive if no error occurred
632  */
633 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
634 {
635         gint ret = 1, count = 0;
636         GSList *list, *elem;
637
638         if (!queue)
639                 queue = folder_get_default_queue();
640         g_return_val_if_fail(queue != NULL, -1);
641
642         folder_item_scan(queue);
643         list = folder_item_get_msg_list(queue);
644
645         for (elem = list; elem != NULL; elem = elem->next) {
646                 gchar *file;
647                 MsgInfo *msginfo;
648                 
649                 msginfo = (MsgInfo *)(elem->data);
650                 if (!MSG_IS_LOCKED(msginfo->flags)) {
651                         file = folder_item_fetch_msg(queue, msginfo->msgnum);
652                         if (file) {
653                                 if (procmsg_send_message_queue(file) < 0) {
654                                         g_warning("Sending queued message %d failed.\n", 
655                                                   msginfo->msgnum);
656                                         ret = -1;
657                                 } else {
658                                         /* CLAWS: 
659                                          * We save in procmsg_send_message_queue because
660                                          * we need the destination folder from the queue
661                                          * header
662                                                         
663                                         if (save_msgs)
664                                                 procmsg_save_to_outbox
665                                                         (queue->folder->outbox,
666                                                          file, TRUE);
667                                          */
668                                         count++; 
669                                         folder_item_remove_msg(queue, msginfo->msgnum);
670                                 }
671                                 g_free(file);
672                         }
673                 }
674                 /* FIXME: supposedly if only one message is locked, and queue
675                  * is being flushed, the following free says something like 
676                  * "freeing msg ## in folder (nil)". */
677                 procmsg_msginfo_free(msginfo);
678         }
679
680         return ret * count;
681 }
682
683 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
684 {
685         FILE *fp, *outfp;
686         gchar buf[BUFFSIZE];
687         
688         if ((fp = fopen(in, "rb")) == NULL) {
689                 FILE_OP_ERROR(in, "fopen");
690                 return -1;
691         }
692         if ((outfp = fopen(out, "wb")) == NULL) {
693                 FILE_OP_ERROR(out, "fopen");
694                 fclose(fp);
695                 return -1;
696         }
697         while (fgets(buf, sizeof(buf), fp) != NULL)
698                 if (buf[0] == '\r' || buf[0] == '\n') break;
699         while (fgets(buf, sizeof(buf), fp) != NULL)
700                 fputs(buf, outfp);
701         fclose(outfp);
702         fclose(fp);
703         return 0;
704
705 }
706 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
707                             gboolean is_queued)
708 {
709         gint num;
710         MsgInfo *msginfo, *tmp_msginfo;
711         MsgFlags flag = {0, 0};
712
713         debug_print("saving sent message...\n");
714
715         if (!outbox)
716                 outbox = folder_get_default_outbox();
717         g_return_val_if_fail(outbox != NULL, -1);
718
719         /* remove queueing headers */
720         if (is_queued) {
721                 gchar tmp[MAXPATHLEN + 1];
722
723                 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
724                            get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
725                 
726                 if (procmsg_remove_special_headers(file, tmp) !=0)
727                         return -1;
728
729                 folder_item_scan(outbox);
730                 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
731                         g_warning("can't save message\n");
732                         unlink(tmp);
733                         return -1;
734                 }
735         } else {
736                 folder_item_scan(outbox);
737                 if ((num = folder_item_add_msg
738                         (outbox, file, &flag, FALSE)) < 0) {
739                         g_warning("can't save message\n");
740                         return -1;
741                 }
742                 return -1;
743         }
744         msginfo = folder_item_get_msginfo(outbox, num);         /* refcnt++ */
745         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
746         if (msginfo != NULL) {
747                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
748                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
749                 /* tmp_msginfo == msginfo */
750                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
751                     msginfo->returnreceiptto)) {
752                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
753                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
754                 }       
755         }
756
757         return 0;
758 }
759
760 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
761 {
762         static const gchar *def_cmd = "lpr %s";
763         static guint id = 0;
764         gchar *prtmp;
765         FILE *tmpfp, *prfp;
766         gchar buf[1024];
767         gchar *p;
768
769         g_return_if_fail(msginfo);
770
771         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
772                 g_warning("Can't get text part\n");
773                 return;
774         }
775
776         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
777                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
778
779         if ((prfp = fopen(prtmp, "wb")) == NULL) {
780                 FILE_OP_ERROR(prtmp, "fopen");
781                 g_free(prtmp);
782                 fclose(tmpfp);
783                 return;
784         }
785
786         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
787         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
788         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
789         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
790         if (msginfo->newsgroups)
791                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
792         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
793         fputc('\n', prfp);
794
795         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
796                 fputs(buf, prfp);
797
798         fclose(prfp);
799         fclose(tmpfp);
800
801         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
802             !strchr(p + 2, '%'))
803                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
804         else {
805                 if (cmdline)
806                         g_warning("Print command line is invalid: `%s'\n",
807                                   cmdline);
808                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
809         }
810
811         g_free(prtmp);
812
813         g_strchomp(buf);
814         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
815         system(buf);
816 }
817
818 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
819 {
820         msginfo->refcnt++;
821         
822         return msginfo;
823 }
824
825 MsgInfo *procmsg_msginfo_new(void)
826 {
827         MsgInfo *newmsginfo;
828
829         newmsginfo = g_new0(MsgInfo, 1);
830         newmsginfo->refcnt = 1;
831         
832         return newmsginfo;
833 }
834
835 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
836 {
837         MsgInfo *newmsginfo;
838
839         if (msginfo == NULL) return NULL;
840
841         newmsginfo = g_new0(MsgInfo, 1);
842
843         newmsginfo->refcnt = 1;
844
845 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
846 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
847                         g_strdup(msginfo->mmb) : NULL
848
849         MEMBCOPY(msgnum);
850         MEMBCOPY(size);
851         MEMBCOPY(mtime);
852         MEMBCOPY(date_t);
853
854         MEMBCOPY(flags);
855
856         MEMBDUP(fromname);
857
858         MEMBDUP(date);
859         MEMBDUP(from);
860         MEMBDUP(to);
861         MEMBDUP(cc);
862         MEMBDUP(newsgroups);
863         MEMBDUP(subject);
864         MEMBDUP(msgid);
865         MEMBDUP(inreplyto);
866         MEMBDUP(xref);
867
868         MEMBCOPY(folder);
869         MEMBCOPY(to_folder);
870
871         MEMBDUP(xface);
872         MEMBDUP(dispositionnotificationto);
873         MEMBDUP(returnreceiptto);
874         MEMBDUP(references);
875
876         MEMBCOPY(score);
877         MEMBCOPY(threadscore);
878         MEMBDUP(plaintext_file);
879
880         return newmsginfo;
881 }
882
883 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
884 {
885         MsgInfo *full_msginfo;
886         gchar *file;
887
888         if (msginfo == NULL) return NULL;
889
890         file = procmsg_get_message_file(msginfo);
891         if (!file) {
892                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
893                 return NULL;
894         }
895
896         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
897         g_free(file);
898         if (!full_msginfo) return NULL;
899
900         /* CLAWS: make sure we add the missing members; see: 
901          * procheader.c::procheader_get_headernames() */
902         if (!msginfo->xface)
903                 msginfo->xface = g_strdup(full_msginfo->xface);
904         if (!msginfo->dispositionnotificationto)
905                 msginfo->dispositionnotificationto = 
906                         g_strdup(full_msginfo->dispositionnotificationto);
907         if (!msginfo->returnreceiptto)
908                 msginfo->returnreceiptto = g_strdup
909                         (full_msginfo->returnreceiptto);
910         if (!msginfo->partial_recv && full_msginfo->partial_recv)
911                 msginfo->partial_recv = g_strdup
912                         (full_msginfo->partial_recv);
913         msginfo->total_size = full_msginfo->total_size;
914         if (!msginfo->account_server && full_msginfo->account_server)
915                 msginfo->account_server = g_strdup
916                         (full_msginfo->account_server);
917         if (!msginfo->account_login && full_msginfo->account_login)
918                 msginfo->account_login = g_strdup
919                         (full_msginfo->account_login);
920         msginfo->planned_download = full_msginfo->planned_download;
921         procmsg_msginfo_free(full_msginfo);
922
923         return procmsg_msginfo_new_ref(msginfo);
924 }
925
926 void procmsg_msginfo_free(MsgInfo *msginfo)
927 {
928         if (msginfo == NULL) return;
929
930         msginfo->refcnt--;
931         if (msginfo->refcnt > 0)
932                 return;
933
934         if (msginfo->to_folder) {
935                 msginfo->to_folder->op_count--;
936                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
937         }
938
939         g_free(msginfo->fromspace);
940         g_free(msginfo->references);
941         g_free(msginfo->returnreceiptto);
942         g_free(msginfo->dispositionnotificationto);
943         g_free(msginfo->xface);
944
945         g_free(msginfo->fromname);
946
947         g_free(msginfo->date);
948         g_free(msginfo->from);
949         g_free(msginfo->to);
950         g_free(msginfo->cc);
951         g_free(msginfo->newsgroups);
952         g_free(msginfo->subject);
953         g_free(msginfo->msgid);
954         g_free(msginfo->inreplyto);
955         g_free(msginfo->xref);
956
957         g_free(msginfo->partial_recv);
958         g_free(msginfo->account_server);
959         g_free(msginfo->account_login);
960         
961         g_free(msginfo->plaintext_file);
962
963         g_free(msginfo);
964 }
965
966 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
967 {
968         guint memusage = 0;
969         
970         memusage += sizeof(MsgInfo);
971         if (msginfo->fromname)
972                 memusage += strlen(msginfo->fromname);
973         if (msginfo->date)
974                 memusage += strlen(msginfo->date);
975         if (msginfo->from)
976                 memusage += strlen(msginfo->from);
977         if (msginfo->to)
978                 memusage += strlen(msginfo->to);
979         if (msginfo->cc)
980                 memusage += strlen(msginfo->cc);
981         if (msginfo->newsgroups)
982                 memusage += strlen(msginfo->newsgroups);
983         if (msginfo->subject)
984                 memusage += strlen(msginfo->subject);
985         if (msginfo->msgid)
986                 memusage += strlen(msginfo->msgid);
987         if (msginfo->inreplyto)
988                 memusage += strlen(msginfo->inreplyto);
989         if (msginfo->xface)
990                 memusage += strlen(msginfo->xface);
991         if (msginfo->dispositionnotificationto)
992                 memusage += strlen(msginfo->dispositionnotificationto);
993         if (msginfo->returnreceiptto)
994                 memusage += strlen(msginfo->returnreceiptto);
995         if (msginfo->references)
996                 memusage += strlen(msginfo->references);
997         if (msginfo->fromspace)
998                 memusage += strlen(msginfo->fromspace);
999
1000         return memusage;
1001 }
1002
1003 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1004 {
1005         const MsgInfo *msginfo1 = a;
1006         const MsgInfo *msginfo2 = b;
1007
1008         if (!msginfo1)
1009                 return -1;
1010         if (!msginfo2)
1011                 return -1;
1012
1013         return msginfo1->msgnum - msginfo2->msgnum;
1014 }
1015
1016 enum
1017 {
1018         Q_SENDER           = 0,
1019         Q_SMTPSERVER       = 1,
1020         Q_RECIPIENTS       = 2,
1021         Q_NEWSGROUPS       = 3,
1022         Q_MAIL_ACCOUNT_ID  = 4,
1023         Q_NEWS_ACCOUNT_ID  = 5,
1024         Q_SAVE_COPY_FOLDER = 6,
1025         Q_REPLY_MESSAGE_ID = 7,
1026         Q_FWD_MESSAGE_ID   = 8,
1027         Q_PRIVACY_SYSTEM   = 9,
1028         Q_ENCRYPT          = 10,
1029         Q_ENCRYPT_DATA     = 11,
1030 };
1031
1032 gint procmsg_send_message_queue(const gchar *file)
1033 {
1034         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1035                                        {"SSV:",  NULL, FALSE},
1036                                        {"R:",    NULL, FALSE},
1037                                        {"NG:",   NULL, FALSE},
1038                                        {"MAID:", NULL, FALSE},
1039                                        {"NAID:", NULL, FALSE},
1040                                        {"SCF:",  NULL, FALSE},
1041                                        {"RMID:", NULL, FALSE},
1042                                        {"FMID:", NULL, FALSE},
1043                                        {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1044                                        {"X-Sylpheed-Encrypt:", NULL, FALSE},
1045                                        {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1046                                        {NULL,    NULL, FALSE}};
1047         FILE *fp;
1048         gint filepos;
1049         gint mailval = 0, newsval = 0;
1050         gchar *from = NULL;
1051         gchar *smtpserver = NULL;
1052         GSList *to_list = NULL;
1053         GSList *newsgroup_list = NULL;
1054         gchar *savecopyfolder = NULL;
1055         gchar *replymessageid = NULL;
1056         gchar *fwdmessageid = NULL;
1057         gchar *privacy_system = NULL;
1058         gboolean encrypt = FALSE;
1059         gchar *encrypt_data = NULL;
1060         gchar buf[BUFFSIZE];
1061         gint hnum;
1062         PrefsAccount *mailac = NULL, *newsac = NULL;
1063         int local = 0;
1064
1065         g_return_val_if_fail(file != NULL, -1);
1066
1067         if ((fp = fopen(file, "rb")) == NULL) {
1068                 FILE_OP_ERROR(file, "fopen");
1069                 return -1;
1070         }
1071
1072         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1073                != -1) {
1074                 gchar *p = buf + strlen(qentry[hnum].name);
1075
1076                 switch (hnum) {
1077                 case Q_SENDER:
1078                         if (!from) from = g_strdup(p);
1079                         break;
1080                 case Q_SMTPSERVER:
1081                         if (!smtpserver) smtpserver = g_strdup(p);
1082                         break;
1083                 case Q_RECIPIENTS:
1084                         to_list = address_list_append(to_list, p);
1085                         break;
1086                 case Q_NEWSGROUPS:
1087                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1088                         break;
1089                 case Q_MAIL_ACCOUNT_ID:
1090                         mailac = account_find_from_id(atoi(p));
1091                         break;
1092                 case Q_NEWS_ACCOUNT_ID:
1093                         newsac = account_find_from_id(atoi(p));
1094                         break;
1095                 case Q_SAVE_COPY_FOLDER:
1096                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1097                         break;
1098                 case Q_REPLY_MESSAGE_ID:
1099                         if (!replymessageid) replymessageid = g_strdup(p);
1100                         break;
1101                 case Q_FWD_MESSAGE_ID:
1102                         if (!fwdmessageid) fwdmessageid = g_strdup(p);
1103                         break;
1104                 case Q_PRIVACY_SYSTEM:
1105                         if (privacy_system == NULL) privacy_system = g_strdup(p);
1106                         break;
1107                 case Q_ENCRYPT:
1108                         if (p[0] == '1') encrypt = TRUE;
1109                         break;
1110                 case Q_ENCRYPT_DATA:
1111                         if (encrypt_data == NULL) encrypt_data = g_strdup(p);
1112                         break;
1113                 }
1114         }
1115         filepos = ftell(fp);
1116
1117         if (encrypt) {
1118                 /* FIXME: memory leaks, in case of errors */
1119                 MimeInfo *mimeinfo;
1120
1121                 fclose(fp);
1122
1123                 mimeinfo = procmime_scan_queue_file(file);
1124                 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data))
1125                         return -1;
1126
1127                 fp = my_tmpfile();
1128                 if (procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1129                         fclose(fp);
1130                         return -1;
1131                 }
1132                 procmime_mimeinfo_free_all(mimeinfo);
1133                         
1134                 rewind(fp);
1135                 filepos = 0;
1136         }
1137
1138         if (to_list) {
1139                 debug_print("Sending message by mail\n");
1140                 if (!from) {
1141                         g_warning("Queued message header is broken.\n");
1142                         mailval = -1;
1143                 } else if (mailac && mailac->use_mail_command &&
1144                            mailac->mail_command && (* mailac->mail_command)) {
1145                         mailval = send_message_local(mailac->mail_command, fp);
1146                         local = 1;
1147                 } else {
1148                         if (!mailac) {
1149                                 mailac = account_find_from_smtp_server(from, smtpserver);
1150                                 if (!mailac) {
1151                                         g_warning("Account not found. "
1152                                                     "Using current account...\n");
1153                                         mailac = cur_account;
1154                                 }
1155                         }
1156
1157                         if (mailac)
1158                                 mailval = send_message_smtp(mailac, to_list, fp);
1159                         else {
1160                                 PrefsAccount tmp_ac;
1161
1162                                 g_warning("Account not found.\n");
1163
1164                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1165                                 tmp_ac.address = from;
1166                                 tmp_ac.smtp_server = smtpserver;
1167                                 tmp_ac.smtpport = SMTP_PORT;
1168                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1169                         }
1170                 }
1171         }
1172
1173         fseek(fp, filepos, SEEK_SET);
1174         if (newsgroup_list && (mailval == 0)) {
1175                 Folder *folder;
1176                 gchar *tmp = NULL;
1177                 FILE *tmpfp;
1178
1179                 /* write to temporary file */
1180                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1181                             G_DIR_SEPARATOR, (gint)file);
1182                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1183                         FILE_OP_ERROR(tmp, "fopen");
1184                         newsval = -1;
1185                         alertpanel_error(_("Could not create temporary file for news sending."));
1186                 } else {
1187                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1188                                 FILE_OP_ERROR(tmp, "chmod");
1189                                 g_warning("can't change file mode\n");
1190                         }
1191
1192                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1193                                 if (fputs(buf, tmpfp) == EOF) {
1194                                         FILE_OP_ERROR(tmp, "fputs");
1195                                         newsval = -1;
1196                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1197                                 }
1198                         }
1199                         fclose(tmpfp);
1200
1201                         if (newsval == 0) {
1202                                 debug_print("Sending message by news\n");
1203
1204                                 folder = FOLDER(newsac->folder);
1205
1206                                 newsval = news_post(folder, tmp);
1207                                 if (newsval < 0) {
1208                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1209                                                  newsac->nntp_server);
1210                                 }
1211                         }
1212                         unlink(tmp);
1213                 }
1214                 g_free(tmp);
1215         }
1216
1217         slist_free_strings(to_list);
1218         g_slist_free(to_list);
1219         slist_free_strings(newsgroup_list);
1220         g_slist_free(newsgroup_list);
1221         g_free(from);
1222         g_free(smtpserver);
1223         fclose(fp);
1224
1225         /* save message to outbox */
1226         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1227                 FolderItem *outbox;
1228
1229                 debug_print("saving sent message...\n");
1230
1231                 outbox = folder_find_item_from_identifier(savecopyfolder);
1232                 if (!outbox)
1233                         outbox = folder_get_default_outbox();
1234
1235                 procmsg_save_to_outbox(outbox, file, TRUE);
1236         }
1237
1238         if (replymessageid != NULL || fwdmessageid != NULL) {
1239                 gchar **tokens;
1240                 FolderItem *item;
1241                 
1242                 if (replymessageid != NULL)
1243                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1244                 else
1245                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1246                 item = folder_find_item_from_identifier(tokens[0]);
1247
1248                 /* check if queued message has valid folder and message id */
1249                 if (item != NULL && tokens[2] != NULL) {
1250                         MsgInfo *msginfo;
1251                         
1252                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1253                 
1254                         /* check if referring message exists and has a message id */
1255                         if ((msginfo != NULL) && 
1256                             (msginfo->msgid != NULL) &&
1257                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1258                                 procmsg_msginfo_free(msginfo);
1259                                 msginfo = NULL;
1260                         }
1261                         
1262                         if (msginfo == NULL) {
1263                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1264                         }
1265                         
1266                         if (msginfo != NULL) {
1267                                 if (replymessageid != NULL) {
1268                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1269                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1270                                 }  else {
1271                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1272                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1273                                 }
1274                                 procmsg_msginfo_free(msginfo);
1275                         }
1276                 }
1277                 g_strfreev(tokens);
1278         }
1279
1280         g_free(savecopyfolder);
1281         g_free(replymessageid);
1282         g_free(fwdmessageid);
1283         
1284         return (newsval != 0 ? newsval : mailval);
1285 }
1286
1287 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1288 {
1289         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1290
1291         /* NEW flag */
1292         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1293                 item->new_msgs++;
1294         }
1295
1296         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1297                 item->new_msgs--;
1298         }
1299
1300         /* UNREAD flag */
1301         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1302                 item->unread_msgs++;
1303                 if (procmsg_msg_has_marked_parent(msginfo))
1304                         item->unreadmarked_msgs++;
1305         }
1306
1307         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1308                 item->unread_msgs--;
1309                 if (procmsg_msg_has_marked_parent(msginfo))
1310                         item->unreadmarked_msgs--;
1311         }
1312         
1313         /* MARK flag */
1314         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1315                 procmsg_update_unread_children(msginfo, TRUE);
1316         }
1317
1318         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1319                 procmsg_update_unread_children(msginfo, FALSE);
1320         }
1321 }
1322
1323 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1324 {
1325         FolderItem *item;
1326         MsgInfoUpdate msginfo_update;
1327         MsgPermFlags perm_flags_new, perm_flags_old;
1328         MsgTmpFlags tmp_flags_old;
1329
1330         g_return_if_fail(msginfo != NULL);
1331         item = msginfo->folder;
1332         g_return_if_fail(item != NULL);
1333         
1334         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1335
1336         /* Perm Flags handling */
1337         perm_flags_old = msginfo->flags.perm_flags;
1338         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1339         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1340                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1341         }
1342
1343         if (perm_flags_old != perm_flags_new) {
1344                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1345
1346                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1347
1348         }
1349
1350         /* Tmp flags handling */
1351         tmp_flags_old = msginfo->flags.tmp_flags;
1352         msginfo->flags.tmp_flags |= tmp_flags;
1353
1354         /* update notification */
1355         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1356                 msginfo_update.msginfo = msginfo;
1357                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1358                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1359                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1360         }
1361 }
1362
1363 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1364 {
1365         FolderItem *item;
1366         MsgInfoUpdate msginfo_update;
1367         MsgPermFlags perm_flags_new, perm_flags_old;
1368         MsgTmpFlags tmp_flags_old;
1369
1370         g_return_if_fail(msginfo != NULL);
1371         item = msginfo->folder;
1372         g_return_if_fail(item != NULL);
1373         
1374         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1375
1376         /* Perm Flags handling */
1377         perm_flags_old = msginfo->flags.perm_flags;
1378         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1379         
1380         if (perm_flags_old != perm_flags_new) {
1381                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1382
1383                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1384
1385                 msginfo_update.msginfo = msginfo;
1386                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1387                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1388                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1389         }
1390
1391         /* Tmp flags hanlding */
1392         tmp_flags_old = msginfo->flags.tmp_flags;
1393         msginfo->flags.tmp_flags &= ~tmp_flags;
1394
1395         /* update notification */
1396         if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1397                 msginfo_update.msginfo = msginfo;
1398                 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1399                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1400                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1401         }
1402 }
1403
1404 /*!
1405  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1406  *
1407  *\param        info Current message
1408  *\param        perm_flags Flags to be checked
1409  *\param        parentmsgs Hash of prior msgs to avoid loops
1410  *
1411  *\return       gboolean TRUE if perm_flags are found
1412  */
1413 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1414                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1415 {
1416         MsgInfo *tmp;
1417
1418         g_return_val_if_fail(info != NULL, FALSE);
1419
1420         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1421                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1422                                 info->inreplyto);
1423                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1424                         procmsg_msginfo_free(tmp);
1425                         return TRUE;
1426                 } else if (tmp != NULL) {
1427                         gboolean result;
1428
1429                         if (g_hash_table_lookup(parentmsgs, info)) {
1430                                 debug_print("loop detected: %s%c%d\n",
1431                                         folder_item_get_path(info->folder),
1432                                         G_DIR_SEPARATOR, info->msgnum);
1433                                 result = FALSE;
1434                         } else {
1435                                 g_hash_table_insert(parentmsgs, info, "1");
1436                                 result = procmsg_msg_has_flagged_parent_real(
1437                                     tmp, perm_flags, parentmsgs);
1438                         }
1439                         procmsg_msginfo_free(tmp);
1440                         return result;
1441                 } else {
1442                         return FALSE;
1443                 }
1444         } else
1445                 return FALSE;
1446 }
1447
1448 /*!
1449  *\brief        Callback for cleaning up hash of parentmsgs
1450  */
1451 gboolean parentmsgs_hash_remove(gpointer key,
1452                             gpointer value,
1453                             gpointer user_data)
1454 {
1455         return TRUE;
1456 }
1457
1458 /*!
1459  *\brief        Set up list of parentmsgs
1460  *              See procmsg_msg_has_flagged_parent_real()
1461  */
1462 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1463 {
1464         gboolean result;
1465         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1466
1467         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1468         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1469         g_hash_table_destroy(parentmsgs);
1470         return result;
1471 }
1472
1473 /*!
1474  *\brief        Check if msgs prior in thread are marked
1475  *              See procmsg_msg_has_flagged_parent_real()
1476  */
1477 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1478 {
1479         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1480 }
1481
1482
1483 GSList *procmsg_find_children_func(MsgInfo *info, 
1484                                    GSList *children, GSList *all)
1485 {
1486         GSList *cur;
1487
1488         g_return_val_if_fail(info!=NULL, children);
1489         if (info->msgid == NULL)
1490                 return children;
1491
1492         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1493                 MsgInfo *tmp = (MsgInfo *)cur->data;
1494                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1495                         /* Check if message is already in the list */
1496                         if ((children == NULL) || 
1497                             (g_slist_index(children, tmp) == -1)) {
1498                                 children = g_slist_prepend(children,
1499                                                 procmsg_msginfo_new_ref(tmp));
1500                                 children = procmsg_find_children_func(tmp, 
1501                                                         children, 
1502                                                         all);
1503                         }
1504                 }
1505         }
1506         return children;
1507 }
1508
1509 GSList *procmsg_find_children (MsgInfo *info)
1510 {
1511         GSList *children;
1512         GSList *all, *cur;
1513
1514         g_return_val_if_fail(info!=NULL, NULL);
1515         all = folder_item_get_msg_list(info->folder);
1516         children = procmsg_find_children_func(info, NULL, all);
1517         if (children != NULL) {
1518                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1519                         /* this will not free the used pointers
1520                            created with procmsg_msginfo_new_ref */
1521                         procmsg_msginfo_free((MsgInfo *)cur->data);
1522                 }
1523         }
1524         g_slist_free(all);
1525
1526         return children;
1527 }
1528
1529 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1530 {
1531         GSList *children = procmsg_find_children(info);
1532         GSList *cur;
1533         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1534                 MsgInfo *tmp = (MsgInfo *)cur->data;
1535                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1536                         if(newly_marked) 
1537                                 info->folder->unreadmarked_msgs++;
1538                         else
1539                                 info->folder->unreadmarked_msgs--;
1540                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1541                 }
1542                 procmsg_msginfo_free(tmp);
1543         }
1544         g_slist_free(children);
1545 }
1546
1547 /**
1548  * Set the destination folder for a copy or move operation
1549  *
1550  * \param msginfo The message which's destination folder is changed
1551  * \param to_folder The destination folder for the operation
1552  */
1553 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1554 {
1555         if(msginfo->to_folder != NULL) {
1556                 msginfo->to_folder->op_count--;
1557                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1558         }
1559         msginfo->to_folder = to_folder;
1560         if(to_folder != NULL) {
1561                 to_folder->op_count++;
1562                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1563         }
1564 }
1565
1566 /**
1567  * Apply filtering actions to the msginfo
1568  *
1569  * \param msginfo The MsgInfo describing the message that should be filtered
1570  * \return TRUE if the message was moved and MsgInfo is now invalid,
1571  *         FALSE otherwise
1572  */
1573 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1574 {
1575         MailFilteringData mail_filtering_data;
1576                         
1577         mail_filtering_data.msginfo = msginfo;                  
1578         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1579                 return TRUE;
1580
1581         /* filter if enabled in prefs or move to inbox if not */
1582         if((filtering_rules != NULL) &&
1583            filter_message_by_msginfo(filtering_rules, msginfo))
1584                 return TRUE;
1585
1586         return FALSE;
1587 }