mark crossposted messages as read
[claws.git] / src / procmsg.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2002 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
26 #include "intl.h"
27 #include "main.h"
28 #include "utils.h"
29 #include "procmsg.h"
30 #include "procheader.h"
31 #include "send.h"
32 #include "procmime.h"
33 #include "statusbar.h"
34 #include "folder.h"
35 #include "prefs_common.h"
36 #include "account.h"
37 #if USE_GPGME
38 #  include "rfc2015.h"
39 #endif
40
41 typedef struct _FlagInfo        FlagInfo;
42
43 struct _FlagInfo
44 {
45         guint    msgnum;
46         MsgFlags flags;
47 };
48
49 static void mark_sum_func                       (gpointer        key,
50                                                  gpointer        value,
51                                                  gpointer        data);
52
53 static GHashTable *procmsg_read_mark_file       (const gchar    *folder);
54 static gint procmsg_cmp_msgnum                  (gconstpointer   a,
55                                                  gconstpointer   b);
56 static gint procmsg_cmp_flag_msgnum             (gconstpointer   a,
57                                                  gconstpointer   b);
58
59
60 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
61 {
62         GHashTable *msg_table;
63
64         if (mlist == NULL) return NULL;
65
66         msg_table = g_hash_table_new(NULL, g_direct_equal);
67         procmsg_msg_hash_table_append(msg_table, mlist);
68
69         return msg_table;
70 }
71
72 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
73 {
74         GSList *cur;
75         MsgInfo *msginfo;
76
77         if (msg_table == NULL || mlist == NULL) return;
78
79         for (cur = mlist; cur != NULL; cur = cur->next) {
80                 msginfo = (MsgInfo *)cur->data;
81
82                 g_hash_table_insert(msg_table,
83                                     GUINT_TO_POINTER(msginfo->msgnum),
84                                     msginfo);
85         }
86 }
87
88 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
89 {
90         GHashTable *msg_table;
91         GSList *cur;
92         MsgInfo *msginfo;
93
94         if (mlist == NULL) return NULL;
95
96         msg_table = g_hash_table_new(NULL, g_direct_equal);
97
98         for (cur = mlist; cur != NULL; cur = cur->next) {
99                 msginfo = (MsgInfo *)cur->data;
100                 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
101         }
102
103         return msg_table;
104 }
105
106 static gint procmsg_read_cache_data_str(FILE *fp, gchar **str)
107 {
108         gchar buf[BUFFSIZE];
109         gint ret = 0;
110         size_t len;
111
112         if (fread(&len, sizeof(len), 1, fp) == 1) {
113                 if (len < 0)
114                         ret = -1;
115                 else {
116                         gchar *tmp = NULL;
117
118                         while (len > 0) {
119                                 size_t size = MIN(len, BUFFSIZE - 1);
120
121                                 if (fread(buf, size, 1, fp) != 1) {
122                                         ret = -1;
123                                         if (tmp) g_free(tmp);
124                                         *str = NULL;
125                                         break;
126                                 }
127
128                                 buf[size] = '\0';
129                                 if (tmp) {
130                                         *str = g_strconcat(tmp, buf, NULL);
131                                         g_free(tmp);
132                                         tmp = *str;
133                                 } else
134                                         tmp = *str = g_strdup(buf);
135
136                                 len -= size;
137                         }
138                 }
139         } else
140                 ret = -1;
141
142         if (ret < 0)
143                 g_warning(_("Cache data is corrupted\n"));
144
145         return ret;
146 }
147
148 #define READ_CACHE_DATA(data, fp) \
149 { \
150         if (procmsg_read_cache_data_str(fp, &data) < 0) { \
151                 procmsg_msginfo_free(msginfo); \
152                 break; \
153         } \
154 }
155
156 #define READ_CACHE_DATA_INT(n, fp) \
157 { \
158         if (fread(&n, sizeof(n), 1, fp) != 1) { \
159                 g_warning(_("Cache data is corrupted\n")); \
160                 procmsg_msginfo_free(msginfo); \
161                 break; \
162         } \
163 }
164
165 GSList *procmsg_read_cache(FolderItem *item, gboolean scan_file)
166 {
167         GSList *mlist = NULL;
168         GSList *last = NULL;
169         gchar *cache_file;
170         FILE *fp;
171         MsgInfo *msginfo;
172         MsgFlags default_flags;
173         gchar file_buf[BUFFSIZE];
174         gint ver;
175         guint num;
176         FolderType type;
177
178         g_return_val_if_fail(item != NULL, NULL);
179         g_return_val_if_fail(item->folder != NULL, NULL);
180         type = item->folder->type;
181
182         default_flags.perm_flags = MSG_NEW|MSG_UNREAD;
183         default_flags.tmp_flags = MSG_CACHED;
184         if (type == F_MH) {
185                 if (item->stype == F_QUEUE) {
186                         MSG_SET_TMP_FLAGS(default_flags, MSG_QUEUED);
187                 } else if (item->stype == F_DRAFT) {
188                         MSG_SET_TMP_FLAGS(default_flags, MSG_DRAFT);
189                 }
190         } else if (type == F_IMAP) {
191                 MSG_SET_TMP_FLAGS(default_flags, MSG_IMAP);
192         } else if (type == F_NEWS) {
193                 MSG_SET_TMP_FLAGS(default_flags, MSG_NEWS);
194         }
195
196         if (type == F_MH) {
197                 gchar *path;
198
199                 path = folder_item_get_path(item);
200                 if (change_dir(path) < 0) {
201                         g_free(path);
202                         return NULL;
203                 }
204                 g_free(path);
205         }
206         cache_file = folder_item_get_cache_file(item);
207         if ((fp = fopen(cache_file, "rb")) == NULL) {
208                 debug_print(_("\tNo cache file\n"));
209                 g_free(cache_file);
210                 return NULL;
211         }
212         setvbuf(fp, file_buf, _IOFBF, sizeof(file_buf));
213         g_free(cache_file);
214
215         debug_print(_("\tReading summary cache...\n"));
216
217         /* compare cache version */
218         if (fread(&ver, sizeof(ver), 1, fp) != 1 ||
219             CACHE_VERSION != ver) {
220                 debug_print(_("Cache version is different. Discarding it.\n"));
221                 fclose(fp);
222                 return NULL;
223         }
224
225         while (fread(&num, sizeof(num), 1, fp) == 1) {
226                 msginfo = g_new0(MsgInfo, 1);
227                 msginfo->msgnum = num;
228                 READ_CACHE_DATA_INT(msginfo->size, fp);
229                 READ_CACHE_DATA_INT(msginfo->mtime, fp);
230                 READ_CACHE_DATA_INT(msginfo->date_t, fp);
231                 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
232
233                 READ_CACHE_DATA(msginfo->fromname, fp);
234
235                 READ_CACHE_DATA(msginfo->date, fp);
236                 READ_CACHE_DATA(msginfo->from, fp);
237                 READ_CACHE_DATA(msginfo->to, fp);
238                 READ_CACHE_DATA(msginfo->cc, fp);
239                 READ_CACHE_DATA(msginfo->newsgroups, fp);
240                 READ_CACHE_DATA(msginfo->subject, fp);
241                 READ_CACHE_DATA(msginfo->msgid, fp);
242                 READ_CACHE_DATA(msginfo->inreplyto, fp);
243                 READ_CACHE_DATA(msginfo->references, fp);
244                 READ_CACHE_DATA(msginfo->xref, fp);
245
246
247                 MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags);
248                 MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags);
249
250                 /* if the message file doesn't exist or is changed,
251                    don't add the data */
252                 if (type == F_MH && scan_file &&
253                     folder_item_is_msg_changed(item, msginfo))
254                         procmsg_msginfo_free(msginfo);
255                 else {
256                         msginfo->folder = item;
257
258                         if (!mlist)
259                                 last = mlist = g_slist_append(NULL, msginfo);
260                         else {
261                                 last = g_slist_append(last, msginfo);
262                                 last = last->next;
263                         }
264                 }
265         }
266
267         fclose(fp);
268         debug_print(_("done.\n"));
269
270         return mlist;
271 }
272
273 #undef READ_CACHE_DATA
274 #undef READ_CACHE_DATA_INT
275
276 void procmsg_set_flags(GSList *mlist, FolderItem *item)
277 {
278         GSList *cur, *tmp;
279         gint newmsg = 0;
280         gint lastnum = 0;
281         gchar *markdir;
282         MsgInfo *msginfo;
283         GHashTable *mark_table;
284         MsgFlags *flags;
285
286         if (!mlist) return;
287         g_return_if_fail(item != NULL);
288         g_return_if_fail(item->folder != NULL);
289
290         debug_print(_("\tMarking the messages...\n"));
291
292         markdir = folder_item_get_path(item);
293         if (!is_dir_exist(markdir))
294                 make_dir_hier(markdir);
295
296         mark_table = procmsg_read_mark_file(markdir);
297         g_free(markdir);
298
299         if (!mark_table) return;
300
301         for (cur = mlist; cur != NULL; cur = cur->next) {
302                 msginfo = (MsgInfo *)cur->data;
303
304                 if (lastnum < msginfo->msgnum)
305                         lastnum = msginfo->msgnum;
306
307                 flags = g_hash_table_lookup
308                         (mark_table, GUINT_TO_POINTER(msginfo->msgnum));
309
310                 if (flags != NULL) {
311                         /* add the permanent flags only */
312                         msginfo->flags.perm_flags = flags->perm_flags;
313                         if (item->folder->type == F_IMAP) {
314                                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP);
315                         } else if (item->folder->type == F_NEWS) {
316                                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS);
317                         }
318                 } else {
319                         /* not found (new message) */
320                         if (newmsg == 0) {
321                                 for (tmp = mlist; tmp != cur; tmp = tmp->next)
322                                         MSG_UNSET_PERM_FLAGS
323                                                 (((MsgInfo *)tmp->data)->flags,
324                                                  MSG_NEW);
325                         }
326                         newmsg++;
327                 }
328         }
329
330         item->last_num = lastnum;
331
332         debug_print(_("done.\n"));
333         if (newmsg)
334                 debug_print(_("\t%d new message(s)\n"), newmsg);
335
336         hash_free_value_mem(mark_table);
337         g_hash_table_destroy(mark_table);
338 }
339
340 gint procmsg_get_last_num_in_cache(GSList *mlist)
341 {
342         GSList *cur;
343         MsgInfo *msginfo;
344         gint last = 0;
345
346         if (mlist == NULL) return 0;
347
348         for (cur = mlist; cur != NULL; cur = cur->next) {
349                 msginfo = (MsgInfo *)cur->data;
350                 if (msginfo && msginfo->msgnum > last)
351                         last = msginfo->msgnum;
352         }
353
354         return last;
355 }
356
357 void procmsg_msg_list_free(GSList *mlist)
358 {
359         GSList *cur;
360         MsgInfo *msginfo;
361
362         for (cur = mlist; cur != NULL; cur = cur->next) {
363                 msginfo = (MsgInfo *)cur->data;
364                 procmsg_msginfo_free(msginfo);
365         }
366         g_slist_free(mlist);
367 }
368
369 void procmsg_write_cache(MsgInfo *msginfo, FILE *fp)
370 {
371         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
372
373         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
374         WRITE_CACHE_DATA_INT(msginfo->size, fp);
375         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
376         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
377         WRITE_CACHE_DATA_INT(flags, fp);
378
379         WRITE_CACHE_DATA(msginfo->fromname, fp);
380
381         WRITE_CACHE_DATA(msginfo->date, fp);
382         WRITE_CACHE_DATA(msginfo->from, fp);
383         WRITE_CACHE_DATA(msginfo->to, fp);
384         WRITE_CACHE_DATA(msginfo->cc, fp);
385         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
386         WRITE_CACHE_DATA(msginfo->subject, fp);
387         WRITE_CACHE_DATA(msginfo->msgid, fp);
388         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
389         WRITE_CACHE_DATA(msginfo->references, fp);
390         WRITE_CACHE_DATA(msginfo->xref, fp);
391
392 }
393
394 void procmsg_write_flags(MsgInfo *msginfo, FILE *fp)
395 {
396         MsgPermFlags flags = msginfo->flags.perm_flags;
397
398         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
399         WRITE_CACHE_DATA_INT(flags, fp);
400 }
401
402 struct MarkSum {
403         gint *new;
404         gint *unread;
405         gint *total;
406         gint *min;
407         gint *max;
408         gint first;
409 };
410
411 static void mark_sum_func(gpointer key, gpointer value, gpointer data)
412 {
413         MsgFlags *flags = value;
414         gint num = GPOINTER_TO_INT(key);
415         struct MarkSum *marksum = data;
416
417         if (marksum->first <= num) {
418                 if (MSG_IS_NEW(*flags) && !MSG_IS_IGNORE_THREAD(*flags)) (*marksum->new)++;
419                 if (MSG_IS_UNREAD(*flags) && !MSG_IS_IGNORE_THREAD(*flags)) (*marksum->unread)++;
420                 if (num > *marksum->max) *marksum->max = num;
421                 if (num < *marksum->min || *marksum->min == 0) *marksum->min = num;
422                 (*marksum->total)++;
423         }
424
425         g_free(flags);
426 }
427
428 void procmsg_get_mark_sum(const gchar *folder,
429                           gint *new, gint *unread, gint *total,
430                           gint *min, gint *max,
431                           gint first)
432 {
433         GHashTable *mark_table;
434         struct MarkSum marksum;
435
436         *new = *unread = *total = *min = *max = 0;
437         marksum.new    = new;
438         marksum.unread = unread;
439         marksum.total  = total;
440         marksum.min    = min;
441         marksum.max    = max;
442         marksum.first  = first;
443
444         mark_table = procmsg_read_mark_file(folder);
445
446         if (mark_table) {
447                 g_hash_table_foreach(mark_table, mark_sum_func, &marksum);
448                 g_hash_table_destroy(mark_table);
449         }
450         debug_print("mark->new = %d, mark->unread = %d, mark->total = %d\n",
451                     *(marksum.new), *(marksum.unread), *(marksum.total));
452 }
453
454 static GHashTable *procmsg_read_mark_file(const gchar *folder)
455 {
456         FILE *fp;
457         GHashTable *mark_table = NULL;
458         gint num;
459         MsgFlags *flags;
460         MsgPermFlags perm_flags;
461
462         if ((fp = procmsg_open_mark_file(folder, FALSE)) == NULL)
463                 return NULL;
464
465         mark_table = g_hash_table_new(NULL, g_direct_equal);
466
467         while (fread(&num, sizeof(num), 1, fp) == 1) {
468                 if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
469
470                 flags = g_new0(MsgFlags, 1);
471                 flags->perm_flags = perm_flags;
472     
473                 if(!MSG_IS_REALLY_DELETED(*flags)) {
474                         g_hash_table_insert(mark_table, GUINT_TO_POINTER(num), flags);
475                 } else {
476                         g_hash_table_remove(mark_table, GUINT_TO_POINTER(num));
477                 }
478         }
479
480         fclose(fp);
481         return mark_table;
482 }
483
484 FILE *procmsg_open_mark_file(const gchar *folder, gboolean append)
485 {
486         gchar *markfile;
487         FILE *fp;
488         gint ver;
489
490         markfile = g_strconcat(folder, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
491
492         if ((fp = fopen(markfile, "rb")) == NULL)
493                 debug_print(_("Mark file not found.\n"));
494         else if (fread(&ver, sizeof(ver), 1, fp) != 1 || MARK_VERSION != ver) {
495                 debug_print(_("Mark version is different (%d != %d). "
496                               "Discarding it.\n"), ver, MARK_VERSION);
497                 fclose(fp);
498                 fp = NULL;
499         }
500
501         /* read mode */
502         if (append == FALSE) {
503                 g_free(markfile);
504                 return fp;
505         }
506
507         if (fp) {
508                 /* reopen with append mode */
509                 fclose(fp);
510                 if ((fp = fopen(markfile, "ab")) == NULL)
511                         g_warning(_("Can't open mark file with append mode.\n"));
512         } else {
513                 /* open with overwrite mode if mark file doesn't exist or
514                    version is different */
515                 if ((fp = fopen(markfile, "wb")) == NULL)
516                         g_warning(_("Can't open mark file with write mode.\n"));
517                 else {
518                         ver = MARK_VERSION;
519                         WRITE_CACHE_DATA_INT(ver, fp);
520                 }
521         }
522
523         g_free(markfile);
524         return fp;
525 }
526
527 /* return the reversed thread tree */
528 GNode *procmsg_get_thread_tree(GSList *mlist)
529 {
530         GNode *root, *parent, *node, *next;
531         GHashTable *msgid_table;
532         GHashTable *subject_table;
533         MsgInfo *msginfo;
534         const gchar *msgid;
535         const gchar *subject;
536
537         root = g_node_new(NULL);
538         msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
539         subject_table = g_hash_table_new(g_str_hash, g_str_equal);
540
541         for (; mlist != NULL; mlist = mlist->next) {
542                 msginfo = (MsgInfo *)mlist->data;
543                 parent = root;
544
545                 if (msginfo->inreplyto) {
546                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
547                         if (parent == NULL) {
548                                 parent = root;
549                         } else {
550                                 if(MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags)) {
551                                         procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
552                                 }
553                         }
554                 }
555                 node = g_node_insert_data_before
556                         (parent, parent == root ? parent->children : NULL,
557                          msginfo);
558                 if ((msgid = msginfo->msgid) &&
559                     g_hash_table_lookup(msgid_table, msgid) == NULL)
560                         g_hash_table_insert(msgid_table, (gchar *)msgid, node);
561
562                 subject = msginfo->subject;
563                 if (subject_table_lookup(subject_table,
564                                          (gchar *) subject) == NULL)
565                         subject_table_insert(subject_table, (gchar *)subject,
566                                              node);
567         }
568
569         /* complete the unfinished threads */
570         for (node = root->children; node != NULL; ) {
571                 next = node->next;
572                 msginfo = (MsgInfo *)node->data;
573                 parent = NULL;
574                 if (msginfo->inreplyto) 
575                         parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
576                 if (parent && parent != node) {
577                         g_node_unlink(node);
578                         g_node_insert_before
579                                 (parent, parent->children, node);
580                         /* CLAWS: ignore thread */
581                         if(MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags)) {
582                                 procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
583                         }
584                 }
585                 node = next;
586         }
587
588         /* CLAWS: now see if the first level (below root) still has some nodes that can be
589          * threaded by subject line. we need to handle this in a special way to prevent
590          * circular reference from a node that has already been threaded by IN-REPLY-TO
591          * but is also in the subject line hash table */
592         for (node = root->children; node != NULL; ) {
593                 next = node->next;
594                 msginfo = (MsgInfo *) node->data;
595                 parent = NULL;
596                 if (subject_is_reply(msginfo->subject)) {
597                         parent = subject_table_lookup(subject_table,
598                                                       msginfo->subject);
599                         /* the node may already be threaded by IN-REPLY-TO,
600                            so go up in the tree to find the parent node */
601                         if (parent != NULL) {
602                                 if (g_node_is_ancestor(node, parent))
603                                         parent = NULL;
604                                 if (parent == node)
605                                         parent = NULL;
606                         }
607
608                         if (parent) {
609                                 g_node_unlink(node);
610                                 g_node_append(parent, node);
611                                 /* CLAWS: ignore thread */
612                                 if(MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags)) {
613                                         procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
614                                 }
615                         }
616                 }                                       
617                 node = next;
618         }               
619                 
620         g_hash_table_destroy(subject_table);
621         g_hash_table_destroy(msgid_table);
622
623         return root;
624 }
625
626 void procmsg_move_messages(GSList *mlist)
627 {
628         GSList *cur, *movelist = NULL;
629         MsgInfo *msginfo;
630         FolderItem *dest = NULL;
631         GHashTable *hash;
632
633         if (!mlist) return;
634
635         hash = procmsg_to_folder_hash_table_create(mlist);
636         folder_item_scan_foreach(hash);
637         g_hash_table_destroy(hash);
638
639         for (cur = mlist; cur != NULL; cur = cur->next) {
640                 msginfo = (MsgInfo *)cur->data;
641                 if (!dest) {
642                         dest = msginfo->to_folder;
643                         movelist = g_slist_append(movelist, msginfo);
644                 } else if (dest == msginfo->to_folder) {
645                         movelist = g_slist_append(movelist, msginfo);
646                 } else {
647                         folder_item_move_msgs_with_dest(dest, movelist);
648                         g_slist_free(movelist);
649                         movelist = NULL;
650                         dest = msginfo->to_folder;
651                         movelist = g_slist_append(movelist, msginfo);
652                 }
653         }
654
655         if (movelist) {
656                 folder_item_move_msgs_with_dest(dest, movelist);
657                 g_slist_free(movelist);
658         }
659 }
660
661 void procmsg_copy_messages(GSList *mlist)
662 {
663         GSList *cur, *copylist = NULL;
664         MsgInfo *msginfo;
665         FolderItem *dest = NULL;
666         GHashTable *hash;
667
668         if (!mlist) return;
669
670         hash = procmsg_to_folder_hash_table_create(mlist);
671         folder_item_scan_foreach(hash);
672         g_hash_table_destroy(hash);
673
674         for (cur = mlist; cur != NULL; cur = cur->next) {
675                 msginfo = (MsgInfo *)cur->data;
676                 if (!dest) {
677                         dest = msginfo->to_folder;
678                         copylist = g_slist_append(copylist, msginfo);
679                 } else if (dest == msginfo->to_folder) {
680                         copylist = g_slist_append(copylist, msginfo);
681                 } else {
682                         folder_item_copy_msgs_with_dest(dest, copylist);
683                         g_slist_free(copylist);
684                         copylist = NULL;
685                         dest = msginfo->to_folder;
686                         copylist = g_slist_append(copylist, msginfo);
687                 }
688         }
689
690         if (copylist) {
691                 folder_item_copy_msgs_with_dest(dest, copylist);
692                 g_slist_free(copylist);
693         }
694 }
695
696 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
697 {
698         gchar *path, *file;
699
700         g_return_val_if_fail(msginfo != NULL, NULL);
701
702         if (msginfo->plaintext_file)
703                 file = g_strdup(msginfo->plaintext_file);
704         else {
705                 path = folder_item_get_path(msginfo->folder);
706                 file = g_strconcat(path, G_DIR_SEPARATOR_S,
707                                    itos(msginfo->msgnum), NULL);
708                 g_free(path);
709         }
710
711         return file;
712 }
713
714 gchar *procmsg_get_message_file(MsgInfo *msginfo)
715 {
716         gchar *filename = NULL;
717
718         g_return_val_if_fail(msginfo != NULL, NULL);
719
720         filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
721         if (!filename)
722                 g_warning(_("can't fetch message %d\n"), msginfo->msgnum);
723
724         return filename;
725 }
726
727 FILE *procmsg_open_message(MsgInfo *msginfo)
728 {
729         FILE *fp;
730         gchar *file;
731
732         g_return_val_if_fail(msginfo != NULL, NULL);
733
734         file = procmsg_get_message_file_path(msginfo);
735         g_return_val_if_fail(file != NULL, NULL);
736
737         if (!is_file_exist(file)) {
738                 g_free(file);
739                 file = procmsg_get_message_file(msginfo);
740                 g_return_val_if_fail(file != NULL, NULL);
741         }
742
743         if ((fp = fopen(file, "rb")) == NULL) {
744                 FILE_OP_ERROR(file, "fopen");
745                 g_free(file);
746                 return NULL;
747         }
748
749         g_free(file);
750
751         if (MSG_IS_QUEUED(msginfo->flags)) {
752                 gchar buf[BUFFSIZE];
753
754                 while (fgets(buf, sizeof(buf), fp) != NULL)
755                         if (buf[0] == '\r' || buf[0] == '\n') break;
756         }
757
758         return fp;
759 }
760
761 #if USE_GPGME
762 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
763 {
764         FILE *fp;
765         MimeInfo *mimeinfo_;
766
767         g_return_val_if_fail(msginfo != NULL, NULL);
768
769         if (mimeinfo) *mimeinfo = NULL;
770
771         if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
772
773         mimeinfo_ = procmime_scan_mime_header(fp);
774         if (!mimeinfo_) {
775                 fclose(fp);
776                 return NULL;
777         }
778
779         if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
780             rfc2015_is_encrypted(mimeinfo_)) {
781                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
782         }
783
784         if (MSG_IS_ENCRYPTED(msginfo->flags) &&
785             !msginfo->plaintext_file &&
786             !msginfo->decryption_failed) {
787                 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
788                 if (msginfo->plaintext_file &&
789                     !msginfo->decryption_failed) {
790                         fclose(fp);
791                         procmime_mimeinfo_free_all(mimeinfo_);
792                         if ((fp = procmsg_open_message(msginfo)) == NULL)
793                                 return NULL;
794                         mimeinfo_ = procmime_scan_mime_header(fp);
795                         if (!mimeinfo_) {
796                                 fclose(fp);
797                                 return NULL;
798                         }
799                 }
800         }
801
802         if (mimeinfo) *mimeinfo = mimeinfo_;
803         return fp;
804 }
805 #endif
806
807 gboolean procmsg_msg_exist(MsgInfo *msginfo)
808 {
809         gchar *path;
810         gboolean ret;
811
812         if (!msginfo) return FALSE;
813
814         path = folder_item_get_path(msginfo->folder);
815         change_dir(path);
816         ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
817         g_free(path);
818
819         return ret;
820 }
821
822 void procmsg_empty_trash(void)
823 {
824         FolderItem *trash;
825         GList *cur;
826
827         for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
828                 trash = FOLDER(cur->data)->trash;
829                 if (trash && trash->total > 0)
830                         folder_item_remove_all_msg(trash);
831         }
832 }
833
834 gint procmsg_send_queue(void)
835 {
836         FolderItem *queue;
837         gint i;
838         gint ret = 0;
839
840         queue = folder_get_default_queue();
841         g_return_val_if_fail(queue != NULL, -1);
842         folder_item_scan(queue);
843         if (queue->last_num < 0) return -1;
844         else if (queue->last_num == 0) return 0;
845
846         for (i = 1; i <= queue->last_num; i++) {
847                 gchar *file;
848
849                 file = folder_item_fetch_msg(queue, i);
850                 if (file) {
851                         if (procmsg_send_message_queue(file) < 0) {
852                                 g_warning(_("Sending queued message %d failed.\n"), i);
853                                 ret = -1;
854                         } else
855                                 folder_item_remove_msg(queue, i);
856                         g_free(file);
857                 }
858         }
859
860         return ret;
861 }
862
863 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
864 {
865         static const gchar *def_cmd = "lpr %s";
866         static guint id = 0;
867         gchar *prtmp;
868         FILE *tmpfp, *prfp;
869         gchar buf[1024];
870         gchar *p;
871
872         g_return_if_fail(msginfo);
873
874         if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
875                 g_warning(_("Can't get text part\n"));
876                 return;
877         }
878
879         prtmp = g_strdup_printf("%s%cprinttmp.%08x",
880                                 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
881
882         if ((prfp = fopen(prtmp, "wb")) == NULL) {
883                 FILE_OP_ERROR(prtmp, "fopen");
884                 g_free(prtmp);
885                 fclose(tmpfp);
886                 return;
887         }
888
889         if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
890         if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
891         if (msginfo->to)   fprintf(prfp, "To: %s\n", msginfo->to);
892         if (msginfo->cc)   fprintf(prfp, "Cc: %s\n", msginfo->cc);
893         if (msginfo->newsgroups)
894                 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
895         if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
896         fputc('\n', prfp);
897
898         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
899                 fputs(buf, prfp);
900
901         fclose(prfp);
902         fclose(tmpfp);
903
904         if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
905             !strchr(p + 2, '%'))
906                 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
907         else {
908                 if (cmdline)
909                         g_warning(_("Print command line is invalid: `%s'\n"),
910                                   cmdline);
911                 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
912         }
913
914         g_free(prtmp);
915
916         g_strchomp(buf);
917         if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
918         system(buf);
919 }
920
921 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
922 {
923         MsgInfo *newmsginfo;
924
925         if (msginfo == NULL) return NULL;
926
927         newmsginfo = g_new0(MsgInfo, 1);
928
929 #define MEMBCOPY(mmb)   newmsginfo->mmb = msginfo->mmb
930 #define MEMBDUP(mmb)    newmsginfo->mmb = msginfo->mmb ? \
931                         g_strdup(msginfo->mmb) : NULL
932
933         MEMBCOPY(msgnum);
934         MEMBCOPY(size);
935         MEMBCOPY(mtime);
936         MEMBCOPY(date_t);
937         MEMBCOPY(flags);
938
939         MEMBDUP(fromname);
940
941         MEMBDUP(date);
942         MEMBDUP(from);
943         MEMBDUP(to);
944         MEMBDUP(cc);
945         MEMBDUP(newsgroups);
946         MEMBDUP(subject);
947         MEMBDUP(msgid);
948         MEMBDUP(inreplyto);
949         MEMBDUP(xref);
950
951         MEMBCOPY(folder);
952         MEMBCOPY(to_folder);
953
954         MEMBDUP(xface);
955         MEMBDUP(dispositionnotificationto);
956         MEMBDUP(returnreceiptto);
957         MEMBDUP(references);
958
959         MEMBCOPY(score);
960         MEMBCOPY(threadscore);
961
962         return newmsginfo;
963 }
964
965 void procmsg_msginfo_free(MsgInfo *msginfo)
966 {
967         if (msginfo == NULL) return;
968
969         g_free(msginfo->fromspace);
970         g_free(msginfo->references);
971         g_free(msginfo->returnreceiptto);
972         g_free(msginfo->dispositionnotificationto);
973         g_free(msginfo->xface);
974
975         g_free(msginfo->fromname);
976
977         g_free(msginfo->date);
978         g_free(msginfo->from);
979         g_free(msginfo->to);
980         g_free(msginfo->cc);
981         g_free(msginfo->newsgroups);
982         g_free(msginfo->subject);
983         g_free(msginfo->msgid);
984         g_free(msginfo->inreplyto);
985         g_free(msginfo->xref);
986
987         g_free(msginfo);
988 }
989
990 static gint procmsg_cmp_msgnum(gconstpointer a, gconstpointer b)
991 {
992         const MsgInfo *msginfo = a;
993         const guint msgnum = GPOINTER_TO_UINT(b);
994
995         if (!msginfo)
996                 return -1;
997
998         return msginfo->msgnum - msgnum;
999 }
1000
1001 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1002 {
1003         const MsgInfo *msginfo1 = a;
1004         const MsgInfo *msginfo2 = b;
1005
1006         if (!msginfo1)
1007                 return -1;
1008         if (!msginfo2)
1009                 return -1;
1010
1011         return msginfo1->msgnum - msginfo2->msgnum;
1012 }
1013
1014 static gint procmsg_cmp_flag_msgnum(gconstpointer a, gconstpointer b)
1015 {
1016         const FlagInfo *finfo = a;
1017         const guint msgnum = GPOINTER_TO_UINT(b);
1018
1019         if (!finfo)
1020                 return -1;
1021
1022         return finfo->msgnum - 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 };
1035
1036 gint procmsg_send_message_queue(const gchar *file)
1037 {
1038         static HeaderEntry qentry[] = {{"S:",    NULL, FALSE},
1039                                        {"SSV:",  NULL, FALSE},
1040                                        {"R:",    NULL, FALSE},
1041                                        {"NG:",   NULL, FALSE},
1042                                        {"MAID:", NULL, FALSE},
1043                                        {"NAID:", NULL, FALSE},
1044                                        {"SCF:",  NULL, FALSE},
1045                                        {NULL,    NULL, FALSE}};
1046         FILE *fp;
1047         gint filepos;
1048         gint mailval = 0, newsval = 0;
1049         gchar *from = NULL;
1050         gchar *smtpserver = NULL;
1051         GSList *to_list = NULL;
1052         GSList *newsgroup_list = NULL;
1053         gchar *savecopyfolder = NULL;
1054         gchar buf[BUFFSIZE];
1055         gint hnum;
1056         PrefsAccount *mailac = NULL, *newsac = NULL;
1057         gchar *tmp = NULL;
1058         int local = 0;
1059
1060         g_return_val_if_fail(file != NULL, -1);
1061
1062         if ((fp = fopen(file, "rb")) == NULL) {
1063                 FILE_OP_ERROR(file, "fopen");
1064                 return -1;
1065         }
1066
1067         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1068                != -1) {
1069                 gchar *p = buf + strlen(qentry[hnum].name);
1070
1071                 switch (hnum) {
1072                 case Q_SENDER:
1073                         if (!from) from = g_strdup(p);
1074                         break;
1075                 case Q_SMTPSERVER:
1076                         if (!smtpserver) smtpserver = g_strdup(p);
1077                         break;
1078                 case Q_RECIPIENTS:
1079                         to_list = address_list_append(to_list, p);
1080                         break;
1081                 case Q_NEWSGROUPS:
1082                         newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1083                         break;
1084                 case Q_MAIL_ACCOUNT_ID:
1085                         mailac = account_find_from_id(atoi(p));
1086                         break;
1087                 case Q_NEWS_ACCOUNT_ID:
1088                         newsac = account_find_from_id(atoi(p));
1089                         break;
1090                 case Q_SAVE_COPY_FOLDER:
1091                         if (!savecopyfolder) savecopyfolder = g_strdup(p);
1092                         break;
1093                 }
1094         }
1095         filepos = ftell(fp);
1096
1097         if(newsgroup_list || prefs_common.savemsg) {
1098                 FILE *tmpfp;
1099
1100                 /* write to temporary file */
1101                 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1102                             G_DIR_SEPARATOR, (gint)file);
1103                 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1104                         FILE_OP_ERROR(tmp, "fopen");
1105                         newsval = -1;
1106                 }
1107                 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1108                         FILE_OP_ERROR(tmp, "chmod");
1109                         g_warning(_("can't change file mode\n"));
1110                 }
1111
1112                 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1113                         if (fputs(buf, tmpfp) == EOF) {
1114                                 FILE_OP_ERROR(tmp, "fputs");
1115                                 newsval = -1;
1116                         }
1117                 }
1118                 fclose(tmpfp);
1119         }
1120
1121         fseek(fp, filepos, SEEK_SET);
1122         if (to_list) {
1123                 debug_print(_("Sending message by mail\n"));
1124                 if(!from) {
1125                         g_warning(_("Queued message header is broken.\n"));
1126                         mailval = -1;
1127                 } else if (mailac && mailac->use_mail_command &&
1128                            mailac->mail_command && (* mailac->mail_command)) {
1129                         mailval = send_message_local(mailac->mail_command, fp);
1130                         local = 1;
1131                 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1132                         mailval = send_message_local(prefs_common.extsend_cmd, fp);
1133                         local = 1;
1134                 } else {
1135                         if (!mailac) {
1136                                 mailac = account_find_from_smtp_server(from, smtpserver);
1137                                 if (!mailac) {
1138                                         g_warning(_("Account not found. "
1139                                                     "Using current account...\n"));
1140                                         mailac = cur_account;
1141                                 }
1142                         }
1143
1144                         if (mailac)
1145                                 mailval = send_message_smtp(mailac, to_list, fp);
1146                         else {
1147                                 PrefsAccount tmp_ac;
1148
1149                                 g_warning(_("Account not found.\n"));
1150
1151                                 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1152                                 tmp_ac.address = from;
1153                                 tmp_ac.smtp_server = smtpserver;
1154                                 tmp_ac.smtpport = SMTP_PORT;
1155                                 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1156                         }
1157                 }
1158                 if (mailval < 0) {
1159                         if (!local)
1160                                 alertpanel_error(
1161                                         _("Error occurred while sending the message to `%s'."),
1162                                         mailac ? mailac->smtp_server : smtpserver);
1163                         else
1164                                 alertpanel_error(
1165                                         _("Error occurred while sending the message with command `%s'."),
1166                                         (mailac && mailac->use_mail_command && 
1167                                          mailac->mail_command && (*mailac->mail_command)) ? 
1168                                                 mailac->mail_command : prefs_common.extsend_cmd);
1169                 }
1170         }
1171
1172         if(newsgroup_list && (newsval == 0)) {
1173                 Folder *folder;
1174
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
1186         /* save message to outbox */
1187         if (mailval == 0 && newsval == 0 && savecopyfolder) {
1188                 FolderItem *folder;
1189                 gchar *path;
1190                 gint num;
1191                 FILE *fp;
1192
1193                 debug_print(_("saving sent message...\n"));
1194
1195                 folder = folder_find_item_from_identifier(savecopyfolder);
1196                 if(!folder)
1197                         folder = folder_get_default_outbox();
1198                 path = folder_item_get_path(folder);
1199                 if (!is_dir_exist(path))
1200                         make_dir_hier(path);
1201
1202                 folder_item_scan(folder);
1203                 if ((num = folder_item_add_msg(folder, tmp, FALSE)) < 0) {
1204                         g_warning(_("can't save message\n"));
1205                 }
1206
1207                 if(num) {
1208                         if ((fp = procmsg_open_mark_file(path, TRUE)) == NULL)
1209                                 g_warning(_("can't open mark file\n"));
1210                         else {
1211                                 MsgInfo newmsginfo;
1212
1213                                 newmsginfo.msgnum = num;
1214                                 newmsginfo.flags.perm_flags = 0;
1215                                 newmsginfo.flags.tmp_flags = 0;
1216                                 procmsg_write_flags(&newmsginfo, fp);
1217                                 fclose(fp);
1218                         }
1219                 }
1220                 g_free(path);
1221         }
1222
1223         slist_free_strings(to_list);
1224         g_slist_free(to_list);
1225         slist_free_strings(newsgroup_list);
1226         g_slist_free(newsgroup_list);
1227         g_free(from);
1228         g_free(smtpserver);
1229         fclose(fp);
1230         if(tmp) {
1231                 unlink(tmp);
1232                 g_free(tmp);
1233         }
1234
1235         return (newsval != 0 ? newsval : mailval);
1236 }
1237
1238 #define CHANGE_FLAGS(msginfo) \
1239 { \
1240 if (msginfo->folder->folder->change_flags != NULL) \
1241 msginfo->folder->folder->change_flags(msginfo->folder->folder, \
1242                                       msginfo->folder, \
1243                                       msginfo); \
1244 }
1245
1246 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1247 {
1248         gboolean changed = FALSE;
1249         FolderItem *item = msginfo->folder;
1250
1251         debug_print(_("Setting flags for message %d in folder %s\n"), msginfo->msgnum, item->path);
1252
1253         /* if new flag is set */
1254         if((perm_flags & MSG_NEW) && !MSG_IS_NEW(msginfo->flags) &&
1255            !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
1256                 item->new++;
1257                 changed = TRUE;
1258         }
1259
1260         /* if unread flag is set */
1261         if((perm_flags & MSG_UNREAD) && !MSG_IS_UNREAD(msginfo->flags) &&
1262            !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
1263                 item->unread++;
1264                 changed = TRUE;
1265         }
1266
1267         /* if ignore thread flag is set */
1268         if((perm_flags & MSG_IGNORE_THREAD) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
1269                 if(MSG_IS_NEW(msginfo->flags) || (perm_flags & MSG_NEW)) {
1270                         item->new--;
1271                         changed = TRUE;
1272                 }
1273                 if(MSG_IS_UNREAD(msginfo->flags) || (perm_flags & MSG_UNREAD)) {
1274                         item->unread--;
1275                         changed = TRUE;
1276                 }
1277         }
1278
1279         if (MSG_IS_IMAP(msginfo->flags))
1280                 imap_msg_set_perm_flags(msginfo, perm_flags);
1281
1282         msginfo->flags.perm_flags |= perm_flags;
1283         msginfo->flags.tmp_flags |= tmp_flags;
1284
1285         if(changed) {
1286                 folderview_update_item(item, FALSE);
1287         }
1288         CHANGE_FLAGS(msginfo);
1289         procmsg_msginfo_write_flags(msginfo);
1290 }
1291
1292 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1293 {
1294         gboolean changed = FALSE;
1295         FolderItem *item = msginfo->folder;
1296         
1297         debug_print(_("Unsetting flags for message %d in folder %s\n"), msginfo->msgnum, item->path);
1298
1299         /* if new flag is unset */
1300         if((perm_flags & MSG_NEW) && MSG_IS_NEW(msginfo->flags) &&
1301            !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
1302                 item->new--;
1303                 changed = TRUE;
1304         }
1305
1306         /* if unread flag is unset */
1307         if((perm_flags & MSG_UNREAD) && MSG_IS_UNREAD(msginfo->flags) &&
1308            !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
1309                 item->unread--;
1310                 changed = TRUE;
1311         }
1312
1313         /* if ignore thread flag is unset */
1314         if((perm_flags & MSG_IGNORE_THREAD) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
1315                 if(MSG_IS_NEW(msginfo->flags) || (perm_flags & MSG_NEW)) {
1316                         item->new++;
1317                         changed = TRUE;
1318                 }
1319                 if(MSG_IS_UNREAD(msginfo->flags) || (perm_flags & MSG_UNREAD)) {
1320                         item->unread++;
1321                         changed = TRUE;
1322                 }
1323         }
1324
1325         if (MSG_IS_IMAP(msginfo->flags))
1326                 imap_msg_unset_perm_flags(msginfo, perm_flags);
1327
1328         msginfo->flags.perm_flags &= ~perm_flags;
1329         msginfo->flags.tmp_flags &= ~tmp_flags;
1330
1331         if(changed) {
1332                 folderview_update_item(item, FALSE);
1333         }
1334         CHANGE_FLAGS(msginfo);
1335         procmsg_msginfo_write_flags(msginfo);
1336 }
1337
1338 void procmsg_msginfo_write_flags(MsgInfo *msginfo)
1339 {
1340         gchar *destdir;
1341         FILE *fp;
1342
1343         destdir = folder_item_get_path(msginfo->folder);
1344         if (!is_dir_exist(destdir))
1345                 make_dir_hier(destdir);
1346
1347         if ((fp = procmsg_open_mark_file(destdir, TRUE))) {
1348                 procmsg_write_flags(msginfo, fp);
1349                 fclose(fp);
1350         } else {
1351                 g_warning(_("Can't open mark file.\n"));
1352         }
1353         
1354         g_free(destdir);
1355 }