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