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