* src/procmsg.[ch]
[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, *tmp_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);         /* refcnt++ */
766         tmp_msginfo = procmsg_msginfo_get_full_info(msginfo);   /* refcnt++ */ 
767         if (msginfo != NULL) {
768                 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
769                 procmsg_msginfo_free(msginfo);                  /* refcnt-- */
770                 /* tmp_msginfo == msginfo */
771                 if (tmp_msginfo && (msginfo->dispositionnotificationto || 
772                     msginfo->returnreceiptto)) {
773                         procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0); 
774                         procmsg_msginfo_free(msginfo);          /* refcnt-- */
775                 }       
776         }
777         folder_item_update(outbox, TRUE);
778
779         return 0;
780 }
781
782 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
783 {
784         static const gchar *def_cmd = "lpr %s";
785         static guint id = 0;
786         gchar *prtmp;
787         FILE *tmpfp, *prfp;
788         gchar buf[1024];
789         gchar *p;
790
791         g_return_if_fail(msginfo);
792
793         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
794                 g_warning("Can't get text part\n");
795                 return;
796         }
797
798         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
799                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
800
801         if ((prfp = fopen(prtmp, "wb")) == NULL) {
802                 FILE_OP_ERROR(prtmp, "fopen");
803                 g_free(prtmp);
804                 fclose(tmpfp);
805                 return;
806         }
807
808         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
809         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
810         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
811         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
812         if (msginfo->newsgroups)
813                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
814         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
815         fputc('\n', prfp);
816
817         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
818                 fputs(buf, prfp);
819
820         fclose(prfp);
821         fclose(tmpfp);
822
823         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
824             !strchr(p + 2, '%'))
825                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
826         else {
827                 if (cmdline)
828                         g_warning("Print command line is invalid: `%s'\n",
829                                   cmdline);
830                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
831         }
832
833         g_free(prtmp);
834
835         g_strchomp(buf);
836         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
837         system(buf);
838 }
839
840 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
841 {
842         msginfo->refcnt++;
843         
844         return msginfo;
845 }
846
847 MsgInfo *procmsg_msginfo_new(void)
848 {
849         MsgInfo *newmsginfo;
850
851         newmsginfo = g_new0(MsgInfo, 1);
852         newmsginfo->refcnt = 1;
853         
854         return newmsginfo;
855 }
856
857 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
858 {
859         MsgInfo *newmsginfo;
860
861         if (msginfo == NULL) return NULL;
862
863         newmsginfo = g_new0(MsgInfo, 1);
864
865         newmsginfo->refcnt = 1;
866
867 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
868 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
869                         g_strdup(msginfo->mmb) : NULL
870
871         MEMBCOPY(msgnum);
872         MEMBCOPY(size);
873         MEMBCOPY(mtime);
874         MEMBCOPY(date_t);
875         MEMBCOPY(flags);
876
877         MEMBDUP(fromname);
878
879         MEMBDUP(date);
880         MEMBDUP(from);
881         MEMBDUP(to);
882         MEMBDUP(cc);
883         MEMBDUP(newsgroups);
884         MEMBDUP(subject);
885         MEMBDUP(msgid);
886         MEMBDUP(inreplyto);
887         MEMBDUP(xref);
888
889         MEMBCOPY(folder);
890         MEMBCOPY(to_folder);
891
892         MEMBDUP(xface);
893         MEMBDUP(dispositionnotificationto);
894         MEMBDUP(returnreceiptto);
895         MEMBDUP(references);
896
897         MEMBCOPY(score);
898         MEMBCOPY(threadscore);
899
900         return newmsginfo;
901 }
902
903 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
904 {
905         MsgInfo *full_msginfo;
906         gchar *file;
907
908         if (msginfo == NULL) return NULL;
909
910         file = procmsg_get_message_file(msginfo);
911         if (!file) {
912                 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
913                 return NULL;
914         }
915
916         full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
917         g_free(file);
918         if (!full_msginfo) return NULL;
919
920         /* CLAWS: make sure we add the missing members; see: 
921          * procheader.c::procheader_get_headernames() */
922         if (!msginfo->xface)
923                 msginfo->xface = g_strdup(full_msginfo->xface);
924         if (!msginfo->dispositionnotificationto)
925                 msginfo->dispositionnotificationto = 
926                         g_strdup(full_msginfo->dispositionnotificationto);
927         if (!msginfo->returnreceiptto)
928                 msginfo->returnreceiptto = g_strdup
929                         (full_msginfo->returnreceiptto);
930         procmsg_msginfo_free(full_msginfo);
931
932         return procmsg_msginfo_new_ref(msginfo);
933 #if 0
934         full_msginfo->msgnum = msginfo->msgnum;
935         full_msginfo->size = msginfo->size;
936         full_msginfo->mtime = msginfo->mtime;
937         full_msginfo->folder = msginfo->folder;
938 #if USE_GPGME
939         full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
940         full_msginfo->decryption_failed = msginfo->decryption_failed;
941 #endif
942         procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
943
944         return full_msginfo;
945 #endif
946 }
947
948 void procmsg_msginfo_free(MsgInfo *msginfo)
949 {
950         if (msginfo == NULL) return;
951
952         msginfo->refcnt--;
953         if (msginfo->refcnt > 0)
954                 return;
955
956         debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
957
958         if (msginfo->to_folder) {
959                 msginfo->to_folder->op_count--;
960                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
961         }
962
963         g_free(msginfo->fromspace);
964         g_free(msginfo->references);
965         g_free(msginfo->returnreceiptto);
966         g_free(msginfo->dispositionnotificationto);
967         g_free(msginfo->xface);
968
969         g_free(msginfo->fromname);
970
971         g_free(msginfo->date);
972         g_free(msginfo->from);
973         g_free(msginfo->to);
974         g_free(msginfo->cc);
975         g_free(msginfo->newsgroups);
976         g_free(msginfo->subject);
977         g_free(msginfo->msgid);
978         g_free(msginfo->inreplyto);
979         g_free(msginfo->xref);
980
981         g_free(msginfo);
982 }
983
984 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
985 {
986         guint memusage = 0;
987         
988         memusage += sizeof(MsgInfo);
989         if (msginfo->fromname)
990                 memusage += strlen(msginfo->fromname);
991         if (msginfo->date)
992                 memusage += strlen(msginfo->date);
993         if (msginfo->from)
994                 memusage += strlen(msginfo->from);
995         if (msginfo->to)
996                 memusage += strlen(msginfo->to);
997         if (msginfo->cc)
998                 memusage += strlen(msginfo->cc);
999         if (msginfo->newsgroups)
1000                 memusage += strlen(msginfo->newsgroups);
1001         if (msginfo->subject)
1002                 memusage += strlen(msginfo->subject);
1003         if (msginfo->msgid)
1004                 memusage += strlen(msginfo->msgid);
1005         if (msginfo->inreplyto)
1006                 memusage += strlen(msginfo->inreplyto);
1007         if (msginfo->xface)
1008                 memusage += strlen(msginfo->xface);
1009         if (msginfo->dispositionnotificationto)
1010                 memusage += strlen(msginfo->dispositionnotificationto);
1011         if (msginfo->returnreceiptto)
1012                 memusage += strlen(msginfo->returnreceiptto);
1013         if (msginfo->references)
1014                 memusage += strlen(msginfo->references);
1015         if (msginfo->fromspace)
1016                 memusage += strlen(msginfo->fromspace);
1017
1018         return memusage;
1019 }
1020
1021 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1022 {
1023         const MsgInfo *msginfo1 = a;
1024         const MsgInfo *msginfo2 = b;
1025
1026         if (!msginfo1)
1027                 return -1;
1028         if (!msginfo2)
1029                 return -1;
1030
1031         return msginfo1->msgnum - msginfo2->msgnum;
1032 }
1033
1034 enum
1035 {
1036         Q_SENDER           = 0,
1037         Q_SMTPSERVER       = 1,
1038         Q_RECIPIENTS       = 2,
1039         Q_NEWSGROUPS       = 3,
1040         Q_MAIL_ACCOUNT_ID  = 4,
1041         Q_NEWS_ACCOUNT_ID  = 5,
1042         Q_SAVE_COPY_FOLDER = 6,
1043         Q_REPLY_MESSAGE_ID = 7,
1044         Q_FWD_MESSAGE_ID   = 8
1045 };
1046
1047 gint procmsg_send_message_queue(const gchar *file)
1048 {
1049         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1050                                        {"SSV:",  NULL, FALSE},
1051                                        {"R:",    NULL, FALSE},
1052                                        {"NG:",   NULL, FALSE},
1053                                        {"MAID:", NULL, FALSE},
1054                                        {"NAID:", NULL, FALSE},
1055                                        {"SCF:",  NULL, FALSE},
1056                                        {"RMID:", NULL, FALSE},
1057                                        {"FMID:", NULL, FALSE},
1058                                        {NULL,    NULL, FALSE}};
1059         FILE *fp;
1060         gint filepos;
1061         gint mailval = 0, newsval = 0;
1062         gchar *from = NULL;
1063         gchar *smtpserver = NULL;
1064         GSList *to_list = NULL;
1065         GSList *newsgroup_list = NULL;
1066         gchar *savecopyfolder = NULL;
1067         gchar *replymessageid = NULL;
1068         gchar *fwdmessageid = NULL;
1069         gchar buf[BUFFSIZE];
1070         gint hnum;
1071         PrefsAccount *mailac = NULL, *newsac = NULL;
1072         int local = 0;
1073
1074         g_return_val_if_fail(file != NULL, -1);
1075
1076         if ((fp = fopen(file, "rb")) == NULL) {
1077                 FILE_OP_ERROR(file, "fopen");
1078                 return -1;
1079         }
1080
1081         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1082                != -1) {
1083                 gchar *p = buf + strlen(qentry[hnum].name);
1084
1085                 switch (hnum) {
1086                 case Q_SENDER:
1087                         if (!from) from = g_strdup(p);
1088                         break;
1089                 case Q_SMTPSERVER:
1090                         if (!smtpserver) smtpserver = g_strdup(p);
1091                         break;
1092                 case Q_RECIPIENTS:
1093                         to_list = address_list_append(to_list, p);
1094                         break;
1095                 case Q_NEWSGROUPS:
1096                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1097                         break;
1098                 case Q_MAIL_ACCOUNT_ID:
1099                         mailac = account_find_from_id(atoi(p));
1100                         break;
1101                 case Q_NEWS_ACCOUNT_ID:
1102                         newsac = account_find_from_id(atoi(p));
1103                         break;
1104                 case Q_SAVE_COPY_FOLDER:
1105                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1106                         break;
1107                 case Q_REPLY_MESSAGE_ID:
1108                         if (!replymessageid) replymessageid = g_strdup(p);
1109                         break;
1110                 case Q_FWD_MESSAGE_ID:
1111                         if (!fwdmessageid) fwdmessageid = g_strdup(p);
1112                         break;
1113                 }
1114         }
1115         filepos = ftell(fp);
1116
1117         if (to_list) {
1118                 debug_print("Sending message by mail\n");
1119                 if (!from) {
1120                         g_warning("Queued message header is broken.\n");
1121                         mailval = -1;
1122                 } else if (mailac && mailac->use_mail_command &&
1123                            mailac->mail_command && (* mailac->mail_command)) {
1124                         mailval = send_message_local(mailac->mail_command, fp);
1125                         local = 1;
1126                 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1127                         mailval = send_message_local(prefs_common.extsend_cmd, fp);
1128                         local = 1;
1129                 } else {
1130                         if (!mailac) {
1131                                 mailac = account_find_from_smtp_server(from, smtpserver);
1132                                 if (!mailac) {
1133                                         g_warning("Account not found. "
1134                                                     "Using current account...\n");
1135                                         mailac = cur_account;
1136                                 }
1137                         }
1138
1139                         if (mailac)
1140                                 mailval = send_message_smtp(mailac, to_list, fp);
1141                         else {
1142                                 PrefsAccount tmp_ac;
1143
1144                                 g_warning("Account not found.\n");
1145
1146                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1147                                 tmp_ac.address = from;
1148                                 tmp_ac.smtp_server = smtpserver;
1149                                 tmp_ac.smtpport = SMTP_PORT;
1150                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1151                         }
1152                 }
1153         }
1154
1155         fseek(fp, filepos, SEEK_SET);
1156         if (newsgroup_list && (newsval == 0)) {
1157                 Folder *folder;
1158                 gchar *tmp = NULL;
1159                 FILE *tmpfp;
1160
1161                 /* write to temporary file */
1162                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1163                             G_DIR_SEPARATOR, (gint)file);
1164                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1165                         FILE_OP_ERROR(tmp, "fopen");
1166                         newsval = -1;
1167                         alertpanel_error(_("Could not create temporary file for news sending."));
1168                 } else {
1169                         if (change_file_mode_rw(tmpfp, tmp) < 0) {
1170                                 FILE_OP_ERROR(tmp, "chmod");
1171                                 g_warning("can't change file mode\n");
1172                         }
1173
1174                         while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1175                                 if (fputs(buf, tmpfp) == EOF) {
1176                                         FILE_OP_ERROR(tmp, "fputs");
1177                                         newsval = -1;
1178                                         alertpanel_error(_("Error when writing temporary file for news sending."));
1179                                 }
1180                         }
1181                         fclose(tmpfp);
1182
1183                         if (newsval == 0) {
1184                                 debug_print("Sending message by news\n");
1185
1186                                 folder = FOLDER(newsac->folder);
1187
1188                                 newsval = news_post(folder, tmp);
1189                                 if (newsval < 0) {
1190                                         alertpanel_error(_("Error occurred while posting the message to %s ."),
1191                                                  newsac->nntp_server);
1192                                 }
1193                         }
1194                         unlink(tmp);
1195                 }
1196                 g_free(tmp);
1197         }
1198
1199         slist_free_strings(to_list);
1200         g_slist_free(to_list);
1201         slist_free_strings(newsgroup_list);
1202         g_slist_free(newsgroup_list);
1203         g_free(from);
1204         g_free(smtpserver);
1205         fclose(fp);
1206
1207         /* save message to outbox */
1208         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1209                 FolderItem *outbox;
1210
1211                 debug_print("saving sent message...\n");
1212
1213                 outbox = folder_find_item_from_identifier(savecopyfolder);
1214                 if (!outbox)
1215                         outbox = folder_get_default_outbox();
1216
1217                 procmsg_save_to_outbox(outbox, file, TRUE);
1218         }
1219
1220         if (replymessageid != NULL || fwdmessageid != NULL) {
1221                 gchar **tokens;
1222                 FolderItem *item;
1223                 
1224                 if (replymessageid != NULL)
1225                         tokens = g_strsplit(replymessageid, "\x7f", 0);
1226                 else
1227                         tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1228                 item = folder_find_item_from_identifier(tokens[0]);
1229
1230                 /* check if queued message has valid folder and message id */
1231                 if (item != NULL && tokens[2] != NULL) {
1232                         MsgInfo *msginfo;
1233                         
1234                         msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1235                 
1236                         /* check if referring message exists and has a message id */
1237                         if ((msginfo != NULL) && 
1238                             (msginfo->msgid != NULL) &&
1239                             (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1240                                 procmsg_msginfo_free(msginfo);
1241                                 msginfo = NULL;
1242                         }
1243                         
1244                         if (msginfo == NULL) {
1245                                 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1246                         }
1247                         
1248                         if (msginfo != NULL) {
1249                                 if (replymessageid != NULL) {
1250                                         procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1251                                         procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1252                                 } 
1253                                 else {
1254                                         procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1255                                         procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1256                                 }
1257                                 procmsg_msginfo_free(msginfo);
1258                         }
1259                 }
1260                 g_strfreev(tokens);
1261         }
1262
1263         g_free(savecopyfolder);
1264         g_free(replymessageid);
1265         g_free(fwdmessageid);
1266         
1267         return (newsval != 0 ? newsval : mailval);
1268 }
1269
1270 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1271 {
1272         MsgPermFlags new_flags = msginfo->flags.perm_flags;
1273
1274         /* NEW flag */
1275         if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1276                 item->new_msgs++;
1277         }
1278
1279         if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1280                 item->new_msgs--;
1281         }
1282
1283         /* UNREAD flag */
1284         if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1285                 item->unread_msgs++;
1286                 if (procmsg_msg_has_marked_parent(msginfo))
1287                         item->unreadmarked_msgs++;
1288         }
1289
1290         if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1291                 item->unread_msgs--;
1292                 if (procmsg_msg_has_marked_parent(msginfo))
1293                         item->unreadmarked_msgs--;
1294         }
1295         
1296         /* MARK flag */
1297         if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1298                 procmsg_update_unread_children(msginfo, TRUE);
1299         }
1300
1301         if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1302                 procmsg_update_unread_children(msginfo, FALSE);
1303         }
1304 }
1305
1306 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1307 {
1308         FolderItem *item;
1309         MsgInfoUpdate msginfo_update;
1310         MsgPermFlags perm_flags_new, perm_flags_old;
1311
1312         g_return_if_fail(msginfo != NULL);
1313         item = msginfo->folder;
1314         g_return_if_fail(item != NULL);
1315         
1316         debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1317
1318         /* Perm Flags handling */
1319         perm_flags_old = msginfo->flags.perm_flags;
1320         perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1321         if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1322                 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1323         }
1324
1325         if (perm_flags_old != perm_flags_new) {
1326                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1327
1328                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1329
1330                 msginfo_update.msginfo = msginfo;
1331                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1332                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1333         }
1334
1335         /* Tmp flags hanlding */
1336         msginfo->flags.tmp_flags |= tmp_flags;
1337 }
1338
1339 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1340 {
1341         FolderItem *item;
1342         MsgInfoUpdate msginfo_update;
1343         MsgPermFlags perm_flags_new, perm_flags_old;
1344
1345         g_return_if_fail(msginfo != NULL);
1346         item = msginfo->folder;
1347         g_return_if_fail(item != NULL);
1348         
1349         debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1350
1351         /* Perm Flags handling */
1352         perm_flags_old = msginfo->flags.perm_flags;
1353         perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1354         
1355         if (perm_flags_old != perm_flags_new) {
1356                 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1357
1358                 update_folder_msg_counts(item, msginfo, perm_flags_old);
1359
1360                 msginfo_update.msginfo = msginfo;
1361                 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1362                 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1363         }
1364
1365         /* Tmp flags hanlding */
1366         msginfo->flags.tmp_flags &= ~tmp_flags;
1367 }
1368
1369 /*!
1370  *\brief        check for flags (e.g. mark) in prior msgs of current thread
1371  *
1372  *\param        info Current message
1373  *\param        perm_flags Flags to be checked
1374  *\param        parentmsgs Hash of prior msgs to avoid loops
1375  *
1376  *\return       gboolean TRUE if perm_flags are found
1377  */
1378 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1379                 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1380 {
1381         MsgInfo *tmp;
1382
1383         g_return_val_if_fail(info != NULL, FALSE);
1384
1385         if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1386                 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1387                                 info->inreplyto);
1388                 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1389                         procmsg_msginfo_free(tmp);
1390                         return TRUE;
1391                 } else if (tmp != NULL) {
1392                         gboolean result;
1393
1394                         if (g_hash_table_lookup(parentmsgs, info)) {
1395                                 debug_print("loop detected: %s%c%d\n",
1396                                         folder_item_get_path(info->folder),
1397                                         G_DIR_SEPARATOR, info->msgnum);
1398                                 result = FALSE;
1399                         } else {
1400                                 g_hash_table_insert(parentmsgs, info, "1");
1401                                 result = procmsg_msg_has_flagged_parent_real(
1402                                     tmp, perm_flags, parentmsgs);
1403                         }
1404                         procmsg_msginfo_free(tmp);
1405                         return result;
1406                 } else {
1407                         return FALSE;
1408                 }
1409         } else
1410                 return FALSE;
1411 }
1412
1413 /*!
1414  *\brief        Callback for cleaning up hash of parentmsgs
1415  */
1416 gboolean parentmsgs_hash_remove(gpointer key,
1417                             gpointer value,
1418                             gpointer user_data)
1419 {
1420         return TRUE;
1421 }
1422
1423 /*!
1424  *\brief        Set up list of parentmsgs
1425  *              See procmsg_msg_has_flagged_parent_real()
1426  */
1427 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1428 {
1429         gboolean result;
1430         GHashTable *parentmsgs = g_hash_table_new(NULL, NULL); 
1431
1432         result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1433         g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1434         g_hash_table_destroy(parentmsgs);
1435         return result;
1436 }
1437
1438 /*!
1439  *\brief        Check if msgs prior in thread are marked
1440  *              See procmsg_msg_has_flagged_parent_real()
1441  */
1442 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1443 {
1444         return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1445 }
1446
1447
1448 GSList *procmsg_find_children_func(MsgInfo *info, 
1449                                    GSList *children, GSList *all)
1450 {
1451         GSList *cur;
1452
1453         g_return_val_if_fail(info!=NULL, children);
1454         if (info->msgid == NULL)
1455                 return children;
1456
1457         for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1458                 MsgInfo *tmp = (MsgInfo *)cur->data;
1459                 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1460                         /* Check if message is already in the list */
1461                         if ((children == NULL) || 
1462                             (g_slist_index(children, tmp) == -1)) {
1463                                 children = g_slist_prepend(children,
1464                                                 procmsg_msginfo_new_ref(tmp));
1465                                 children = procmsg_find_children_func(tmp, 
1466                                                         children, 
1467                                                         all);
1468                         }
1469                 }
1470         }
1471         return children;
1472 }
1473
1474 GSList *procmsg_find_children (MsgInfo *info)
1475 {
1476         GSList *children;
1477         GSList *all, *cur;
1478
1479         g_return_val_if_fail(info!=NULL, NULL);
1480         all = folder_item_get_msg_list(info->folder);
1481         children = procmsg_find_children_func(info, NULL, all);
1482         if (children != NULL) {
1483                 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1484                         /* this will not free the used pointers
1485                            created with procmsg_msginfo_new_ref */
1486                         procmsg_msginfo_free((MsgInfo *)cur->data);
1487                 }
1488         }
1489         g_slist_free(all);
1490
1491         return children;
1492 }
1493
1494 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1495 {
1496         GSList *children = procmsg_find_children(info);
1497         GSList *cur;
1498         for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1499                 MsgInfo *tmp = (MsgInfo *)cur->data;
1500                 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1501                         if(newly_marked) 
1502                                 info->folder->unreadmarked_msgs++;
1503                         else
1504                                 info->folder->unreadmarked_msgs--;
1505                         folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1506                 }
1507                 procmsg_msginfo_free(tmp);
1508         }
1509         g_slist_free(children);
1510 }
1511
1512 /**
1513  * Set the destination folder for a copy or move operation
1514  *
1515  * \param msginfo The message which's destination folder is changed
1516  * \param to_folder The destination folder for the operation
1517  */
1518 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1519 {
1520         if(msginfo->to_folder != NULL) {
1521                 msginfo->to_folder->op_count--;
1522                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1523         }
1524         msginfo->to_folder = to_folder;
1525         if(to_folder != NULL) {
1526                 to_folder->op_count++;
1527                 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1528         }
1529 }
1530
1531 /**
1532  * Apply filtering actions to the msginfo
1533  *
1534  * \param msginfo The MsgInfo describing the message that should be filtered
1535  * \return TRUE if the message was moved and MsgInfo is now invalid,
1536  *         FALSE otherwise
1537  */
1538 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1539 {
1540         MailFilteringData mail_filtering_data;
1541                         
1542         mail_filtering_data.msginfo = msginfo;                  
1543         if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1544                 return TRUE;
1545
1546         /* filter if enabled in prefs or move to inbox if not */
1547         if((global_processing != NULL) &&
1548            filter_message_by_msginfo(global_processing, msginfo))
1549                 return TRUE;
1550
1551         return FALSE;
1552 }