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