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