Statistics
| Revision:

root / libsylph / procmsg.c @ 2251

History | View | Annotate | Download (45.8 kB)

1
/*
2
 * LibSylph -- E-Mail client library
3
 * Copyright (C) 1999-2009 Hiroyuki Yamamoto
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * This library 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 GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with this library; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
 */
19
20
#include "defs.h"
21
22
#include <glib.h>
23
#include <glib/gi18n.h>
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <errno.h>
27
28
#include "utils.h"
29
#include "procmsg.h"
30
#include "procheader.h"
31
#include "account.h"
32
#include "procmime.h"
33
#include "prefs_common.h"
34
#include "folder.h"
35
#include "codeconv.h"
36
37
typedef struct _MsgFlagInfo {
38
        guint msgnum;
39
        MsgFlags flags;
40
} MsgFlagInfo;
41
42
static GSList *procmsg_read_cache_queue                (FolderItem        *item,
43
                                                 gboolean         scan_file);
44
45
static void mark_sum_func                        (gpointer         key,
46
                                                 gpointer         value,
47
                                                 gpointer         data);
48
49
static GHashTable *procmsg_read_mark_file        (FolderItem        *item);
50
static void procmsg_write_mark_file                (FolderItem        *item,
51
                                                 GHashTable        *mark_table);
52
53
static FILE *procmsg_open_cache_file_with_buffer(FolderItem        *item,
54
                                                 DataOpenMode         mode,
55
                                                 gchar                *buf,
56
                                                 size_t                 buf_size);
57
58
static gint procmsg_cmp_by_mark                        (gconstpointer         a,
59
                                                 gconstpointer         b);
60
static gint procmsg_cmp_by_unread                (gconstpointer         a,
61
                                                 gconstpointer         b);
62
static gint procmsg_cmp_by_mime                        (gconstpointer         a,
63
                                                 gconstpointer         b);
64
static gint procmsg_cmp_by_label                (gconstpointer         a,
65
                                                 gconstpointer         b);
66
static gint procmsg_cmp_by_number                (gconstpointer         a,
67
                                                 gconstpointer         b);
68
static gint procmsg_cmp_by_size                        (gconstpointer         a,
69
                                                 gconstpointer         b);
70
static gint procmsg_cmp_by_date                        (gconstpointer         a,
71
                                                 gconstpointer         b);
72
static gint procmsg_cmp_by_from                        (gconstpointer         a,
73
                                                 gconstpointer         b);
74
static gint procmsg_cmp_by_to                        (gconstpointer         a,
75
                                                 gconstpointer         b);
76
static gint procmsg_cmp_by_subject                (gconstpointer         a,
77
                                                 gconstpointer         b);
78
79
80
GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
81
{
82
        GHashTable *msg_table;
83
84
        if (mlist == NULL) return NULL;
85
86
        msg_table = g_hash_table_new(NULL, g_direct_equal);
87
        procmsg_msg_hash_table_append(msg_table, mlist);
88
89
        return msg_table;
90
}
91
92
void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
93
{
94
        GSList *cur;
95
        MsgInfo *msginfo;
96
97
        if (msg_table == NULL || mlist == NULL) return;
98
99
        for (cur = mlist; cur != NULL; cur = cur->next) {
100
                msginfo = (MsgInfo *)cur->data;
101
102
                g_hash_table_insert(msg_table,
103
                                    GUINT_TO_POINTER(msginfo->msgnum),
104
                                    msginfo);
105
        }
106
}
107
108
GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
109
{
110
        GHashTable *msg_table;
111
        GSList *cur;
112
        MsgInfo *msginfo;
113
114
        if (mlist == NULL) return NULL;
115
116
        msg_table = g_hash_table_new(NULL, g_direct_equal);
117
118
        for (cur = mlist; cur != NULL; cur = cur->next) {
119
                msginfo = (MsgInfo *)cur->data;
120
                g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
121
        }
122
123
        return msg_table;
124
}
125
126
gint procmsg_read_cache_data_str(FILE *fp, gchar **str)
127
{
128
        gchar buf[BUFFSIZE];
129
        gint ret = 0;
130
        guint32 len;
131
132
        if (fread(&len, sizeof(len), 1, fp) == 1) {
133
                if (len > G_MAXINT)
134
                        ret = -1;
135
                else {
136
                        gchar *tmp = NULL;
137
138
                        while (len > 0) {
139
                                size_t size = MIN(len, BUFFSIZE - 1);
140
141
                                if (fread(buf, size, 1, fp) != 1) {
142
                                        ret = -1;
143
                                        if (tmp) g_free(tmp);
144
                                        *str = NULL;
145
                                        break;
146
                                }
147
148
                                buf[size] = '\0';
149
                                if (tmp) {
150
                                        *str = g_strconcat(tmp, buf, NULL);
151
                                        g_free(tmp);
152
                                        tmp = *str;
153
                                } else
154
                                        tmp = *str = g_strdup(buf);
155
156
                                len -= size;
157
                        }
158
                }
159
        } else
160
                ret = -1;
161
162
        return ret;
163
}
164
165
#define READ_CACHE_DATA(data, fp)                                \
166
{                                                                \
167
        if (procmsg_read_cache_data_str(fp, &data) < 0) {        \
168
                g_warning("Cache data is corrupted\n");                \
169
                procmsg_msginfo_free(msginfo);                        \
170
                procmsg_msg_list_free(mlist);                        \
171
                fclose(fp);                                        \
172
                return NULL;                                        \
173
        }                                                        \
174
}
175
176
#define READ_CACHE_DATA_INT(n, fp)                                \
177
{                                                                \
178
        guint32 idata;                                                \
179
                                                                \
180
        if (fread(&idata, sizeof(idata), 1, fp) != 1) {                \
181
                g_warning("Cache data is corrupted\n");                \
182
                procmsg_msginfo_free(msginfo);                        \
183
                procmsg_msg_list_free(mlist);                        \
184
                fclose(fp);                                        \
185
                return NULL;                                        \
186
        } else                                                        \
187
                n = idata;                                        \
188
}
189
190
GSList *procmsg_read_cache(FolderItem *item, gboolean scan_file)
191
{
192
        GSList *mlist = NULL;
193
        GSList *last = NULL;
194
        FILE *fp;
195
        MsgInfo *msginfo;
196
        MsgFlags default_flags;
197
        gchar file_buf[BUFFSIZE];
198
        guint32 num;
199
        guint refnum;
200
        FolderType type;
201
202
        g_return_val_if_fail(item != NULL, NULL);
203
        g_return_val_if_fail(item->folder != NULL, NULL);
204
        type = FOLDER_TYPE(item->folder);
205
206
        default_flags.perm_flags = MSG_NEW|MSG_UNREAD;
207
        default_flags.tmp_flags = 0;
208
        if (type == F_MH || type == F_IMAP) {
209
                if (item->stype == F_QUEUE) {
210
                        MSG_SET_TMP_FLAGS(default_flags, MSG_QUEUED);
211
                } else if (item->stype == F_DRAFT) {
212
                        MSG_SET_TMP_FLAGS(default_flags, MSG_DRAFT);
213
                }
214
        }
215
        if (type == F_IMAP) {
216
                MSG_SET_TMP_FLAGS(default_flags, MSG_IMAP);
217
        } else if (type == F_NEWS) {
218
                MSG_SET_TMP_FLAGS(default_flags, MSG_NEWS);
219
        }
220
221
        if (type == F_MH) {
222
                gchar *path;
223
224
                path = folder_item_get_path(item);
225
                if (change_dir(path) < 0) {
226
                        g_free(path);
227
                        return NULL;
228
                }
229
                g_free(path);
230
        }
231
232
        if ((fp = procmsg_open_cache_file_with_buffer
233
                (item, DATA_READ, file_buf, sizeof(file_buf))) == NULL) {
234
                item->cache_dirty = TRUE;
235
                return NULL;
236
        }
237
238
        debug_print("Reading summary cache...\n");
239
240
        while (fread(&num, sizeof(num), 1, fp) == 1) {
241
                msginfo = g_new0(MsgInfo, 1);
242
                msginfo->msgnum = num;
243
                READ_CACHE_DATA_INT(msginfo->size, fp);
244
                READ_CACHE_DATA_INT(msginfo->mtime, fp);
245
                READ_CACHE_DATA_INT(msginfo->date_t, fp);
246
                READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
247
248
                READ_CACHE_DATA(msginfo->fromname, fp);
249
250
                READ_CACHE_DATA(msginfo->date, fp);
251
                READ_CACHE_DATA(msginfo->from, fp);
252
                READ_CACHE_DATA(msginfo->to, fp);
253
                READ_CACHE_DATA(msginfo->newsgroups, fp);
254
                READ_CACHE_DATA(msginfo->subject, fp);
255
                READ_CACHE_DATA(msginfo->msgid, fp);
256
                READ_CACHE_DATA(msginfo->inreplyto, fp);
257
258
                READ_CACHE_DATA_INT(refnum, fp);
259
                for (; refnum != 0; refnum--) {
260
                        gchar *ref;
261
262
                        READ_CACHE_DATA(ref, fp);
263
                        msginfo->references =
264
                                g_slist_prepend(msginfo->references, ref);
265
                }
266
                if (msginfo->references)
267
                        msginfo->references =
268
                                g_slist_reverse(msginfo->references);
269
270
                MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags);
271
                MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags);
272
273
                /* if the message file doesn't exist or is changed,
274
                   don't add the data */
275
                if ((type == F_MH && scan_file &&
276
                     folder_item_is_msg_changed(item, msginfo)) || num == 0) {
277
                        procmsg_msginfo_free(msginfo);
278
                        item->cache_dirty = TRUE;
279
                } else {
280
                        msginfo->folder = item;
281
282
                        if (!mlist)
283
                                last = mlist = g_slist_append(NULL, msginfo);
284
                        else {
285
                                last = g_slist_append(last, msginfo);
286
                                last = last->next;
287
                        }
288
                }
289
        }
290
291
        fclose(fp);
292
293
        if (item->cache_queue) {
294
                GSList *qlist;
295
                qlist = procmsg_read_cache_queue(item, scan_file);
296
                mlist = g_slist_concat(mlist, qlist);
297
        }
298
299
        debug_print("done.\n");
300
301
        return mlist;
302
}
303
304
#undef READ_CACHE_DATA
305
#undef READ_CACHE_DATA_INT
306
307
static GSList *procmsg_read_cache_queue(FolderItem *item, gboolean scan_file)
308
{
309
        FolderType type;
310
        MsgInfo *msginfo;
311
        MsgFlags default_flags;
312
        GSList *cur;
313
        GSList *qlist = NULL;
314
        GSList *last = NULL;
315
316
        g_return_val_if_fail(item != NULL, NULL);
317
        g_return_val_if_fail(item->folder != NULL, NULL);
318
319
        if (!item->cache_queue)
320
                return NULL;
321
322
        debug_print("Reading cache queue...\n");
323
324
        type = FOLDER_TYPE(item->folder);
325
        default_flags.perm_flags = MSG_NEW|MSG_UNREAD;
326
        default_flags.tmp_flags = 0;
327
328
        for (cur = item->cache_queue; cur != NULL; cur = cur->next) {
329
                msginfo = (MsgInfo *)cur->data;
330
331
                debug_print("read cache queue: %s/%d\n",
332
                            item->path, msginfo->msgnum);
333
334
                MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags);
335
                MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags);
336
337
                if ((type == F_MH && scan_file &&
338
                     folder_item_is_msg_changed(item, msginfo))) {
339
                        procmsg_msginfo_free(msginfo);
340
                        item->cache_dirty = TRUE;
341
                } else {
342
                        msginfo->folder = item;
343
344
                        if (!qlist)
345
                                last = qlist = g_slist_append(NULL, msginfo);
346
                        else {
347
                                last = g_slist_append(last, msginfo);
348
                                last = last->next;
349
                        }
350
                }
351
        }
352
353
        g_slist_free(item->cache_queue);
354
        item->cache_queue = NULL;
355
        item->cache_dirty = TRUE;
356
357
        return qlist;
358
}
359
360
static void mark_unset_new_func(gpointer key, gpointer value, gpointer data)
361
{
362
        MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW);
363
}
364
365
void procmsg_set_flags(GSList *mlist, FolderItem *item)
366
{
367
        GSList *cur;
368
        gint new = 0, unread = 0, total = 0;
369
        gint lastnum = 0;
370
        gint unflagged = 0;
371
        gboolean mark_queue_exist;
372
        MsgInfo *msginfo;
373
        GHashTable *mark_table;
374
        MsgFlags *flags;
375
376
        g_return_if_fail(item != NULL);
377
        g_return_if_fail(item->folder != NULL);
378
379
        debug_print("Marking the messages...\n");
380
381
        mark_queue_exist = (item->mark_queue != NULL);
382
        mark_table = procmsg_read_mark_file(item);
383
        if (!mark_table) {
384
                item->new = item->unread = item->total = g_slist_length(mlist);
385
                item->updated = TRUE;
386
                item->mark_dirty = TRUE;
387
                return;
388
        }
389
390
        /* unset new flags if new (unflagged) messages exist */
391
        if (!mark_queue_exist) {
392
                for (cur = mlist; cur != NULL; cur = cur->next) {
393
                        msginfo = (MsgInfo *)cur->data;
394
                        flags = g_hash_table_lookup
395
                                (mark_table, GUINT_TO_POINTER(msginfo->msgnum));
396
                        if (!flags) {
397
                                g_hash_table_foreach(mark_table,
398
                                                     mark_unset_new_func, NULL);
399
                                item->mark_dirty = TRUE;
400
                                break;
401
                        }
402
                }
403
        }
404
405
        for (cur = mlist; cur != NULL; cur = cur->next) {
406
                msginfo = (MsgInfo *)cur->data;
407
408
                if (lastnum < msginfo->msgnum)
409
                        lastnum = msginfo->msgnum;
410
411
                flags = g_hash_table_lookup
412
                        (mark_table, GUINT_TO_POINTER(msginfo->msgnum));
413
414
                if (flags != NULL) {
415
                        /* add the permanent flags only */
416
                        msginfo->flags.perm_flags = flags->perm_flags;
417
                        if (MSG_IS_NEW(*flags))
418
                                ++new;
419
                        if (MSG_IS_UNREAD(*flags))
420
                                ++unread;
421
                        if (FOLDER_TYPE(item->folder) == F_IMAP) {
422
                                MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP);
423
                        } else if (FOLDER_TYPE(item->folder) == F_NEWS) {
424
                                MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS);
425
                        }
426
                } else {
427
                        ++unflagged;
428
                        ++new;
429
                        ++unread;
430
                }
431
432
                ++total;
433
        }
434
435
        item->new = new;
436
        item->unread = unread;
437
        item->total = total;
438
        item->unmarked_num = unflagged;
439
        item->last_num = lastnum;
440
        item->updated = TRUE;
441
442
        if (unflagged > 0)
443
                item->mark_dirty = TRUE;
444
445
        debug_print("new: %d unread: %d unflagged: %d total: %d\n",
446
                    new, unread, unflagged, total);
447
448
        hash_free_value_mem(mark_table);
449
        g_hash_table_destroy(mark_table);
450
}
451
452
static void mark_all_read_func(gpointer key, gpointer value, gpointer data)
453
{
454
        MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW|MSG_UNREAD);
455
}
456
457
void procmsg_mark_all_read(FolderItem *item)
458
{
459
        GHashTable *mark_table;
460
461
        debug_print("Marking all messages as read\n");
462
463
        mark_table = procmsg_read_mark_file(item);
464
        if (mark_table) {
465
                g_hash_table_foreach(mark_table, mark_all_read_func, NULL);
466
                procmsg_write_mark_file(item, mark_table);
467
                hash_free_value_mem(mark_table);
468
                g_hash_table_destroy(mark_table);
469
        }
470
471
        if (item->mark_queue) {
472
                GSList *cur;
473
                MsgFlagInfo *flaginfo;
474
475
                for (cur = item->mark_queue; cur != NULL; cur = cur->next) {
476
                        flaginfo = (MsgFlagInfo *)cur->data;
477
                        MSG_UNSET_PERM_FLAGS
478
                                (flaginfo->flags, MSG_NEW|MSG_UNREAD);
479
                }
480
                item->mark_dirty = TRUE;
481
        }
482
483
        item->new = item->unread = 0;
484
}
485
486
static FolderSortType cmp_func_sort_type;
487
488
GSList *procmsg_sort_msg_list(GSList *mlist, FolderSortKey sort_key,
489
                              FolderSortType sort_type)
490
{
491
        GCompareFunc cmp_func;
492
493
        switch (sort_key) {
494
        case SORT_BY_MARK:
495
                cmp_func = procmsg_cmp_by_mark; break;
496
        case SORT_BY_UNREAD:
497
                cmp_func = procmsg_cmp_by_unread; break;
498
        case SORT_BY_MIME:
499
                cmp_func = procmsg_cmp_by_mime; break;
500
        case SORT_BY_LABEL:
501
                cmp_func = procmsg_cmp_by_label; break;
502
        case SORT_BY_NUMBER:
503
                cmp_func = procmsg_cmp_by_number; break;
504
        case SORT_BY_SIZE:
505
                cmp_func = procmsg_cmp_by_size; break;
506
        case SORT_BY_DATE:
507
                cmp_func = procmsg_cmp_by_date; break;
508
        case SORT_BY_FROM:
509
                cmp_func = procmsg_cmp_by_from; break;
510
        case SORT_BY_SUBJECT:
511
                cmp_func = procmsg_cmp_by_subject; break;
512
        case SORT_BY_TO:
513
                cmp_func = procmsg_cmp_by_to; break;
514
        default:
515
                return mlist;
516
        }
517
518
        cmp_func_sort_type = sort_type;
519
520
        mlist = g_slist_sort(mlist, cmp_func);
521
522
        return mlist;
523
}
524
525
gint procmsg_get_last_num_in_msg_list(GSList *mlist)
526
{
527
        GSList *cur;
528
        MsgInfo *msginfo;
529
        gint last = 0;
530
531
        for (cur = mlist; cur != NULL; cur = cur->next) {
532
                msginfo = (MsgInfo *)cur->data;
533
                if (msginfo && msginfo->msgnum > last)
534
                        last = msginfo->msgnum;
535
        }
536
537
        return last;
538
}
539
540
void procmsg_msg_list_free(GSList *mlist)
541
{
542
        GSList *cur;
543
        MsgInfo *msginfo;
544
545
        for (cur = mlist; cur != NULL; cur = cur->next) {
546
                msginfo = (MsgInfo *)cur->data;
547
                procmsg_msginfo_free(msginfo);
548
        }
549
        g_slist_free(mlist);
550
}
551
552
void procmsg_write_cache(MsgInfo *msginfo, FILE *fp)
553
{
554
        MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
555
        GSList *cur;
556
557
        WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
558
        WRITE_CACHE_DATA_INT(msginfo->size, fp);
559
        WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
560
        WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
561
        WRITE_CACHE_DATA_INT(flags, fp);
562
563
        WRITE_CACHE_DATA(msginfo->fromname, fp);
564
565
        WRITE_CACHE_DATA(msginfo->date, fp);
566
        WRITE_CACHE_DATA(msginfo->from, fp);
567
        WRITE_CACHE_DATA(msginfo->to, fp);
568
        WRITE_CACHE_DATA(msginfo->newsgroups, fp);
569
        WRITE_CACHE_DATA(msginfo->subject, fp);
570
        WRITE_CACHE_DATA(msginfo->msgid, fp);
571
        WRITE_CACHE_DATA(msginfo->inreplyto, fp);
572
573
        WRITE_CACHE_DATA_INT(g_slist_length(msginfo->references), fp);
574
        for (cur = msginfo->references; cur != NULL; cur = cur->next) {
575
                WRITE_CACHE_DATA((gchar *)cur->data, fp);
576
        }
577
}
578
579
void procmsg_write_flags(MsgInfo *msginfo, FILE *fp)
580
{
581
        MsgPermFlags flags = msginfo->flags.perm_flags;
582
583
        WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
584
        WRITE_CACHE_DATA_INT(flags, fp);
585
}
586
587
void procmsg_write_cache_list(FolderItem *item, GSList *mlist)
588
{
589
        FILE *fp;
590
        GSList *cur;
591
592
        g_return_if_fail(item != NULL);
593
594
        debug_print("Writing summary cache (%s)\n", item->path);
595
596
        fp = procmsg_open_cache_file(item, DATA_WRITE);
597
        if (fp == NULL)
598
                return;
599
600
        for (cur = mlist; cur != NULL; cur = cur->next) {
601
                MsgInfo *msginfo = (MsgInfo *)cur->data;
602
                procmsg_write_cache(msginfo, fp);
603
        }
604
605
        if (item->cache_queue)
606
                procmsg_flush_cache_queue(item, fp);
607
608
        fclose(fp);
609
        item->cache_dirty = FALSE;
610
}
611
612
void procmsg_write_flags_list(FolderItem *item, GSList *mlist)
613
{
614
        FILE *fp;
615
        GSList *cur;
616
617
        g_return_if_fail(item != NULL);
618
619
        debug_print("Writing summary flags (%s)\n", item->path);
620
621
        fp = procmsg_open_mark_file(item, DATA_WRITE);
622
        if (fp == NULL)
623
                return;
624
625
        for (cur = mlist; cur != NULL; cur = cur->next) {
626
                MsgInfo *msginfo = (MsgInfo *)cur->data;
627
                procmsg_write_flags(msginfo, fp);
628
        }
629
630
        if (item->mark_queue)
631
                procmsg_flush_mark_queue(item, fp);
632
633
        fclose(fp);
634
        item->mark_dirty = FALSE;
635
}
636
637
static gint cmp_by_item(gconstpointer a, gconstpointer b)
638
{
639
        const MsgInfo *msginfo1 = a;
640
        const MsgInfo *msginfo2 = b;
641
642
        if (msginfo1->folder == msginfo2->folder)
643
                return msginfo1->msgnum - msginfo2->msgnum;
644
645
        return msginfo1->folder - msginfo2->folder;
646
}
647
648
void procmsg_write_flags_for_multiple_folders(GSList *mlist)
649
{
650
        GSList *tmp_list, *cur;
651
        FolderItem *prev_item = NULL;
652
        FILE *fp = NULL;
653
654
        if (!mlist)
655
                return;
656
657
        tmp_list = g_slist_copy(mlist);
658
        tmp_list = g_slist_sort(tmp_list, cmp_by_item);
659
660
        for (cur = tmp_list; cur != NULL; cur = cur->next) {
661
                MsgInfo *msginfo = (MsgInfo *)cur->data;
662
                FolderItem *item = msginfo->folder;
663
664
                if (prev_item != item) {
665
                        if (fp)
666
                                fclose(fp);
667
                        fp = procmsg_open_mark_file(item, DATA_APPEND);
668
                        if (!fp) {
669
                                g_warning("can't open mark file\n");
670
                                break;
671
                        }
672
                        item->updated = TRUE;
673
                }
674
                procmsg_write_flags(msginfo, fp);
675
                prev_item = item;
676
        }
677
678
        if (fp)
679
                fclose(fp);
680
        g_slist_free(tmp_list);
681
}
682
683
void procmsg_flush_mark_queue(FolderItem *item, FILE *fp)
684
{
685
        MsgFlagInfo *flaginfo;
686
        MsgInfo msginfo = {0};
687
        gboolean append = FALSE;
688
689
        g_return_if_fail(item != NULL);
690
691
        if (!item->mark_queue)
692
                return;
693
694
        debug_print("flushing mark_queue: %s...\n", item->path);
695
696
        if (!fp) {
697
                append =  TRUE;
698
                fp = procmsg_open_mark_file(item, DATA_APPEND);
699
                g_return_if_fail(fp != NULL);
700
        }
701
702
        while (item->mark_queue != NULL) {
703
                flaginfo = (MsgFlagInfo *)item->mark_queue->data;
704
                msginfo.msgnum = flaginfo->msgnum;
705
                msginfo.flags = flaginfo->flags;
706
                procmsg_write_flags(&msginfo, fp);
707
                item->mark_queue = g_slist_remove(item->mark_queue, flaginfo);
708
                g_free(flaginfo);
709
        }
710
711
        if (append)
712
                fclose(fp);
713
}
714
715
void procmsg_add_mark_queue(FolderItem *item, gint num, MsgFlags flags)
716
{
717
        MsgFlagInfo *flaginfo;
718
719
        flaginfo = g_new(MsgFlagInfo, 1);
720
        flaginfo->msgnum = num;
721
        flaginfo->flags = flags;
722
        item->mark_queue = g_slist_append(item->mark_queue, flaginfo);
723
}
724
725
static void procmsg_flaginfo_list_free(GSList *flaglist)
726
{
727
        GSList *cur;
728
        MsgFlagInfo *flaginfo;
729
730
        for (cur = flaglist; cur != NULL; cur = cur->next) {
731
                flaginfo = (MsgFlagInfo *)cur->data;
732
                g_free(flaginfo);
733
        }
734
        g_slist_free(flaglist);
735
}
736
737
void procmsg_flush_cache_queue(FolderItem *item, FILE *fp)
738
{
739
        MsgInfo *msginfo;
740
        gboolean append = FALSE;
741
742
        g_return_if_fail(item != NULL);
743
744
        if (!item->cache_queue)
745
                return;
746
747
        debug_print("flushing cache_queue: %s ...\n", item->path);
748
749
        if (!fp) {
750
                append =  TRUE;
751
                fp = procmsg_open_cache_file(item, DATA_APPEND);
752
                g_return_if_fail(fp != NULL);
753
        }
754
755
        while (item->cache_queue != NULL) {
756
                msginfo = (MsgInfo *)item->cache_queue->data;
757
                debug_print("flush cache queue: %s/%d\n", item->path, msginfo->msgnum);
758
                procmsg_write_cache(msginfo, fp);
759
                procmsg_msginfo_free(msginfo);
760
                item->cache_queue = g_slist_remove(item->cache_queue, msginfo);
761
        }
762
763
        if (append)
764
                fclose(fp);
765
}
766
767
void procmsg_add_cache_queue(FolderItem *item, gint num, MsgInfo *msginfo)
768
{
769
        MsgInfo *queue_msginfo;
770
771
        g_return_if_fail(msginfo != NULL);
772
773
        queue_msginfo = procmsg_msginfo_copy(msginfo);
774
        queue_msginfo->msgnum = num;
775
        if (queue_msginfo->file_path) {
776
                g_free(queue_msginfo->file_path);
777
                queue_msginfo->file_path = NULL;
778
        }
779
780
        debug_print("procmsg_add_cache_queue: add msg cache: %s/%d\n",
781
                    item->path, num);
782
        item->cache_queue = g_slist_append(item->cache_queue, queue_msginfo);
783
}
784
785
gboolean procmsg_flush_folder(FolderItem *item)
786
{
787
        gboolean flushed = FALSE;
788
        gint n_new, n_unread, n_total, n_min, n_max;
789
790
        g_return_val_if_fail(item != NULL, FALSE);
791
        g_return_val_if_fail(item->folder != NULL, FALSE);
792
793
        if (FOLDER_TYPE(item->folder) != F_MH || item->last_num < 0) {
794
                folder_item_scan(item);
795
                return TRUE;
796
        }
797
798
        if (item->mark_queue && !item->opened)
799
                flushed = TRUE;
800
        procmsg_get_mark_sum(item, &n_new, &n_unread, &n_total, &n_min, &n_max,
801
                             0);
802
        item->unmarked_num = 0;
803
        item->new = n_new;
804
        item->unread = n_unread;
805
        item->total = n_total;
806
807
        if (item->cache_queue && !item->opened) {
808
                procmsg_flush_cache_queue(item, NULL);
809
                flushed = TRUE;
810
        }
811
812
        if (flushed)
813
                debug_print("procmsg_flush_folder: flushed %s\n", item->path);
814
815
        return flushed;
816
}
817
818
static void procmsg_flush_folder_foreach_func(gpointer key, gpointer val,
819
                                              gpointer data)
820
{
821
        procmsg_flush_folder(FOLDER_ITEM(key));
822
}
823
824
void procmsg_flush_folder_foreach(GHashTable *folder_table)
825
{
826
        g_hash_table_foreach(folder_table, procmsg_flush_folder_foreach_func,
827
                             NULL);
828
}
829
830
void procmsg_add_flags(FolderItem *item, gint num, MsgFlags flags)
831
{
832
        FILE *fp;
833
        MsgInfo msginfo;
834
835
        g_return_if_fail(item != NULL);
836
837
        if (item->opened) {
838
                procmsg_add_mark_queue(item, num, flags);
839
                return;
840
        }
841
842
        if ((fp = procmsg_open_mark_file(item, DATA_APPEND)) == NULL) {
843
                g_warning(_("can't open mark file\n"));
844
                return;
845
        }
846
847
        msginfo.msgnum = num;
848
        msginfo.flags = flags;
849
850
        procmsg_write_flags(&msginfo, fp);
851
        fclose(fp);
852
}
853
854
struct MarkSum {
855
        gint *new;
856
        gint *unread;
857
        gint *total;
858
        gint *min;
859
        gint *max;
860
        gint first;
861
};
862
863
static void mark_sum_func(gpointer key, gpointer value, gpointer data)
864
{
865
        MsgFlags *flags = value;
866
        gint num = GPOINTER_TO_INT(key);
867
        struct MarkSum *marksum = data;
868
869
        if (marksum->first <= num) {
870
                if (MSG_IS_NEW(*flags)) (*marksum->new)++;
871
                if (MSG_IS_UNREAD(*flags)) (*marksum->unread)++;
872
                if (num > *marksum->max) *marksum->max = num;
873
                if (num < *marksum->min || *marksum->min == 0) *marksum->min = num;
874
                (*marksum->total)++;
875
        }
876
877
        g_free(flags);
878
}
879
880
void procmsg_get_mark_sum(FolderItem *item,
881
                          gint *new, gint *unread, gint *total,
882
                          gint *min, gint *max,
883
                          gint first)
884
{
885
        GHashTable *mark_table;
886
        struct MarkSum marksum;
887
888
        *new = *unread = *total = *min = *max = 0;
889
        marksum.new    = new;
890
        marksum.unread = unread;
891
        marksum.total  = total;
892
        marksum.min    = min;
893
        marksum.max    = max;
894
        marksum.first  = first;
895
896
        mark_table = procmsg_read_mark_file(item);
897
898
        if (mark_table) {
899
                g_hash_table_foreach(mark_table, mark_sum_func, &marksum);
900
                g_hash_table_destroy(mark_table);
901
        }
902
}
903
904
static GHashTable *procmsg_read_mark_file(FolderItem *item)
905
{
906
        FILE *fp;
907
        GHashTable *mark_table = NULL;
908
        guint32 idata;
909
        guint num;
910
        MsgFlags *flags;
911
        MsgPermFlags perm_flags;
912
        GSList *cur;
913
914
        if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL)
915
                return NULL;
916
917
        mark_table = g_hash_table_new(NULL, g_direct_equal);
918
919
        while (fread(&idata, sizeof(idata), 1, fp) == 1) {
920
                num = idata;
921
                if (fread(&idata, sizeof(idata), 1, fp) != 1) break;
922
                perm_flags = idata;
923
924
                flags = g_hash_table_lookup(mark_table, GUINT_TO_POINTER(num));
925
                if (flags != NULL)
926
                        g_free(flags);
927
928
                flags = g_new0(MsgFlags, 1);
929
                flags->perm_flags = perm_flags;
930
931
                g_hash_table_insert(mark_table, GUINT_TO_POINTER(num), flags);
932
        }
933
934
        fclose(fp);
935
936
        if (item->mark_queue) {
937
                g_hash_table_foreach(mark_table, mark_unset_new_func, NULL);
938
                item->mark_dirty = TRUE;
939
        }
940
941
        for (cur = item->mark_queue; cur != NULL; cur = cur->next) {
942
                MsgFlagInfo *flaginfo = (MsgFlagInfo *)cur->data;
943
944
                flags = g_hash_table_lookup(mark_table,
945
                                            GUINT_TO_POINTER(flaginfo->msgnum));
946
                if (flags != NULL)
947
                        g_free(flags);
948
949
                flags = g_new0(MsgFlags, 1);
950
                flags->perm_flags = flaginfo->flags.perm_flags;
951
952
                g_hash_table_insert(mark_table,
953
                                    GUINT_TO_POINTER(flaginfo->msgnum), flags);
954
                                    
955
        }
956
957
        if (item->mark_queue && !item->opened) {
958
                procmsg_write_mark_file(item, mark_table);
959
                procmsg_flaginfo_list_free(item->mark_queue);
960
                item->mark_queue = NULL;
961
                item->mark_dirty = FALSE;
962
        }
963
964
        return mark_table;
965
}
966
967
static void write_mark_func(gpointer key, gpointer value, gpointer data)
968
{
969
        MsgInfo msginfo;
970
971
        msginfo.msgnum = GPOINTER_TO_UINT(key);
972
        msginfo.flags.perm_flags = ((MsgFlags *)value)->perm_flags;
973
        procmsg_write_flags(&msginfo, (FILE *)data);
974
}
975
976
static void procmsg_write_mark_file(FolderItem *item, GHashTable *mark_table)
977
{
978
        FILE *fp;
979
980
        if ((fp = procmsg_open_mark_file(item, DATA_WRITE)) == NULL) {
981
                g_warning("procmsg_write_mark_file: cannot open mark file.");
982
                return;
983
        }
984
        g_hash_table_foreach(mark_table, write_mark_func, fp);
985
        fclose(fp);
986
}
987
988
FILE *procmsg_open_data_file(const gchar *file, guint version,
989
                             DataOpenMode mode, gchar *buf, size_t buf_size)
990
{
991
        FILE *fp;
992
        guint32 data_ver = 0;
993
994
        g_return_val_if_fail(file != NULL, NULL);
995
996
        if (mode == DATA_WRITE) {
997
                if ((fp = g_fopen(file, "wb")) == NULL) {
998
                        if (errno == EACCES) {
999
                                change_file_mode_rw(NULL, file);
1000
                                if ((fp = g_fopen(file, "wb")) == NULL) {
1001
                                        FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1002
                                        return NULL;
1003
                                }
1004
                        } else {
1005
                                FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1006
                                return NULL;
1007
                        }
1008
                }
1009
                if (change_file_mode_rw(fp, file) < 0)
1010
                        FILE_OP_ERROR(file, "chmod");
1011
1012
                WRITE_CACHE_DATA_INT(version, fp);
1013
                return fp;
1014
        }
1015
1016
        /* check version */
1017
        if ((fp = g_fopen(file, "rb")) == NULL) {
1018
                if (errno == EACCES) {
1019
                        change_file_mode_rw(NULL, file);
1020
                        if ((fp = g_fopen(file, "rb")) == NULL) {
1021
                                FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1022
                        }
1023
                } else {
1024
                        debug_print("Mark/Cache file '%s' not found\n", file);
1025
                }
1026
        }
1027
1028
        if (fp) {
1029
                if (buf && buf_size > 0)
1030
                        setvbuf(fp, buf, _IOFBF, buf_size);
1031
                if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1) {
1032
                        g_warning("%s: cannot read mark/cache file (truncated?)\n", file);
1033
                        fclose(fp);
1034
                        fp = NULL;
1035
                } else if (version != data_ver) {
1036
                        g_message("%s: Mark/Cache version is different (%u != %u). Discarding it.\n",
1037
                                  file, data_ver, version);
1038
                        fclose(fp);
1039
                        fp = NULL;
1040
                }
1041
        }
1042
1043
        if (mode == DATA_READ)
1044
                return fp;
1045
1046
        if (fp) {
1047
                /* reopen with append mode */
1048
                fclose(fp);
1049
                if ((fp = g_fopen(file, "ab")) == NULL) {
1050
                        if (errno == EACCES) {
1051
                                change_file_mode_rw(NULL, file);
1052
                                if ((fp = g_fopen(file, "ab")) == NULL) {
1053
                                        FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1054
                                }
1055
                        } else {
1056
                                FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1057
                        }
1058
                }
1059
        } else {
1060
                /* open with overwrite mode if mark file doesn't exist or
1061
                   version is different */
1062
                fp = procmsg_open_data_file(file, version, DATA_WRITE, buf,
1063
                                            buf_size);
1064
        }
1065
1066
        return fp;
1067
}
1068
1069
static FILE *procmsg_open_cache_file_with_buffer(FolderItem *item,
1070
                                                 DataOpenMode mode,
1071
                                                 gchar *buf, size_t buf_size)
1072
{
1073
        gchar *cachefile;
1074
        FILE *fp;
1075
1076
        cachefile = folder_item_get_cache_file(item);
1077
        fp = procmsg_open_data_file(cachefile, CACHE_VERSION, mode, buf,
1078
                                    buf_size);
1079
        g_free(cachefile);
1080
1081
        return fp;
1082
}
1083
1084
FILE *procmsg_open_cache_file(FolderItem *item, DataOpenMode mode)
1085
{
1086
        gchar *cachefile;
1087
        FILE *fp;
1088
1089
        cachefile = folder_item_get_cache_file(item);
1090
        fp = procmsg_open_data_file(cachefile, CACHE_VERSION, mode, NULL, 0);
1091
        g_free(cachefile);
1092
1093
        return fp;
1094
}
1095
1096
FILE *procmsg_open_mark_file(FolderItem *item, DataOpenMode mode)
1097
{
1098
        gchar *markfile;
1099
        FILE *fp;
1100
1101
        markfile = folder_item_get_mark_file(item);
1102
        fp = procmsg_open_data_file(markfile, MARK_VERSION, mode, NULL, 0);
1103
        g_free(markfile);
1104
1105
        return fp;
1106
}
1107
1108
void procmsg_clear_cache(FolderItem *item)
1109
{
1110
        FILE *fp;
1111
1112
        fp = procmsg_open_cache_file(item, DATA_WRITE);
1113
        if (fp)
1114
                fclose(fp);
1115
}
1116
1117
void procmsg_clear_mark(FolderItem *item)
1118
{
1119
        FILE *fp;
1120
1121
        fp = procmsg_open_mark_file(item, DATA_WRITE);
1122
        if (fp)
1123
                fclose(fp);
1124
}
1125
1126
/* return the reversed thread tree */
1127
GNode *procmsg_get_thread_tree(GSList *mlist)
1128
{
1129
        GNode *root, *parent, *node, *next;
1130
        GHashTable *table;
1131
        MsgInfo *msginfo;
1132
        const gchar *msgid;
1133
        GSList *reflist;
1134
1135
        root = g_node_new(NULL);
1136
        table = g_hash_table_new(g_str_hash, g_str_equal);
1137
1138
        for (; mlist != NULL; mlist = mlist->next) {
1139
                msginfo = (MsgInfo *)mlist->data;
1140
                parent = root;
1141
1142
                /* only look for the real parent first */
1143
                if (msginfo->inreplyto) {
1144
                        parent = g_hash_table_lookup(table, msginfo->inreplyto);
1145
                        if (parent == NULL)
1146
                                parent = root;
1147
                }
1148
1149
                node = g_node_insert_data_before
1150
                        (parent, parent == root ? parent->children : NULL,
1151
                         msginfo);
1152
                if ((msgid = msginfo->msgid) &&
1153
                    g_hash_table_lookup(table, msgid) == NULL)
1154
                        g_hash_table_insert(table, (gchar *)msgid, node);
1155
        }
1156
1157
        /* complete the unfinished threads */
1158
        for (node = root->children; node != NULL; ) {
1159
                next = node->next;
1160
                msginfo = (MsgInfo *)node->data;
1161
                parent = NULL;
1162
1163
                if (msginfo->inreplyto)
1164
                        parent = g_hash_table_lookup(table, msginfo->inreplyto);
1165
1166
                /* try looking for the indirect parent */
1167
                if (!parent && msginfo->references) {
1168
                        for (reflist = msginfo->references;
1169
                             reflist != NULL; reflist = reflist->next)
1170
                                if ((parent = g_hash_table_lookup
1171
                                        (table, reflist->data)) != NULL)
1172
                                        break;
1173
                }
1174
1175
                /* node should not be the parent, and node should not
1176
                   be an ancestor of parent (circular reference) */
1177
                if (parent && parent != node &&
1178
                    !g_node_is_ancestor(node, parent)) {
1179
                        g_node_unlink(node);
1180
                        g_node_insert_before
1181
                                (parent, parent->children, node);
1182
                }
1183
                node = next;
1184
        }
1185
1186
        g_hash_table_destroy(table);
1187
1188
        return root;
1189
}
1190
1191
static gboolean procmsg_thread_date_func(GNode *node, gpointer data)
1192
{
1193
        guint *tdate = (guint *)data;
1194
        MsgInfo *msginfo = (MsgInfo *)node->data;
1195
1196
        if (*tdate < msginfo->date_t)
1197
                *tdate = msginfo->date_t;
1198
1199
        return FALSE;
1200
}
1201
1202
guint procmsg_get_thread_date(GNode *node)
1203
{
1204
        guint tdate = 0;
1205
1206
        g_return_val_if_fail(node != NULL && node->parent != NULL &&
1207
                             node->parent->parent == NULL, 0);
1208
1209
        g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1210
                        procmsg_thread_date_func, &tdate);
1211
1212
        return tdate;
1213
}
1214
1215
gint procmsg_move_messages(GSList *mlist)
1216
{
1217
        GSList *cur, *movelist = NULL;
1218
        MsgInfo *msginfo;
1219
        FolderItem *dest = NULL;
1220
        GHashTable *hash;
1221
        gint val = 0;
1222
1223
        if (!mlist) return 0;
1224
1225
        hash = procmsg_to_folder_hash_table_create(mlist);
1226
        folder_item_scan_foreach(hash);
1227
        g_hash_table_destroy(hash);
1228
1229
        for (cur = mlist; cur != NULL; cur = cur->next) {
1230
                msginfo = (MsgInfo *)cur->data;
1231
                if (!dest) {
1232
                        dest = msginfo->to_folder;
1233
                        movelist = g_slist_append(movelist, msginfo);
1234
                } else if (dest == msginfo->to_folder) {
1235
                        movelist = g_slist_append(movelist, msginfo);
1236
                } else {
1237
                        val = folder_item_move_msgs(dest, movelist);
1238
                        g_slist_free(movelist);
1239
                        movelist = NULL;
1240
                        if (val == -1)
1241
                                return val;
1242
                        dest = msginfo->to_folder;
1243
                        movelist = g_slist_append(movelist, msginfo);
1244
                }
1245
        }
1246
1247
        if (movelist) {
1248
                val = folder_item_move_msgs(dest, movelist);
1249
                g_slist_free(movelist);
1250
        }
1251
1252
        return val == -1 ? -1 : 0;
1253
}
1254
1255
gint procmsg_copy_messages(GSList *mlist)
1256
{
1257
        GSList *cur, *copylist = NULL;
1258
        MsgInfo *msginfo;
1259
        FolderItem *dest = NULL;
1260
        GHashTable *hash;
1261
        gint val = 0;
1262
1263
        if (!mlist) return 0;
1264
1265
        hash = procmsg_to_folder_hash_table_create(mlist);
1266
        folder_item_scan_foreach(hash);
1267
        g_hash_table_destroy(hash);
1268
1269
        for (cur = mlist; cur != NULL; cur = cur->next) {
1270
                msginfo = (MsgInfo *)cur->data;
1271
                if (!dest) {
1272
                        dest = msginfo->to_folder;
1273
                        copylist = g_slist_append(copylist, msginfo);
1274
                } else if (dest == msginfo->to_folder) {
1275
                        copylist = g_slist_append(copylist, msginfo);
1276
                } else {
1277
                        val = folder_item_copy_msgs(dest, copylist);
1278
                        g_slist_free(copylist);
1279
                        copylist = NULL;
1280
                        if (val == -1)
1281
                                return val;
1282
                        dest = msginfo->to_folder;
1283
                        copylist = g_slist_append(copylist, msginfo);
1284
                }
1285
        }
1286
1287
        if (copylist) {
1288
                val = folder_item_copy_msgs(dest, copylist);
1289
                g_slist_free(copylist);
1290
        }
1291
1292
        return val == -1 ? -1 : 0;
1293
}
1294
1295
gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
1296
{
1297
        gchar *path, *file;
1298
1299
        g_return_val_if_fail(msginfo != NULL, NULL);
1300
1301
        if (msginfo->encinfo && msginfo->encinfo->plaintext_file)
1302
                file = g_strdup(msginfo->encinfo->plaintext_file);
1303
        else if (msginfo->file_path)
1304
                return g_strdup(msginfo->file_path);
1305
        else {
1306
                path = folder_item_get_path(msginfo->folder);
1307
                file = g_strconcat(path, G_DIR_SEPARATOR_S,
1308
                                   itos(msginfo->msgnum), NULL);
1309
                g_free(path);
1310
        }
1311
1312
        return file;
1313
}
1314
1315
gchar *procmsg_get_message_file(MsgInfo *msginfo)
1316
{
1317
        gchar *filename = NULL;
1318
1319
        g_return_val_if_fail(msginfo != NULL, NULL);
1320
1321
        if (msginfo->file_path)
1322
                return g_strdup(msginfo->file_path);
1323
1324
        filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
1325
        if (!filename)
1326
                debug_print(_("can't fetch message %d\n"), msginfo->msgnum);
1327
1328
        return filename;
1329
}
1330
1331
GSList *procmsg_get_message_file_list(GSList *mlist)
1332
{
1333
        GSList *file_list = NULL;
1334
        MsgInfo *msginfo;
1335
        MsgFileInfo *fileinfo;
1336
        gchar *file;
1337
1338
        while (mlist != NULL) {
1339
                msginfo = (MsgInfo *)mlist->data;
1340
                file = procmsg_get_message_file(msginfo);
1341
                if (!file) {
1342
                        procmsg_message_file_list_free(file_list);
1343
                        return NULL;
1344
                }
1345
                fileinfo = g_new(MsgFileInfo, 1);
1346
                fileinfo->file = file;
1347
                fileinfo->flags = g_new(MsgFlags, 1);
1348
                *fileinfo->flags = msginfo->flags;
1349
                file_list = g_slist_prepend(file_list, fileinfo);
1350
                mlist = mlist->next;
1351
        }
1352
1353
        file_list = g_slist_reverse(file_list);
1354
1355
        return file_list;
1356
}
1357
1358
void procmsg_message_file_list_free(GSList *file_list)
1359
{
1360
        GSList *cur;
1361
        MsgFileInfo *fileinfo;
1362
1363
        for (cur = file_list; cur != NULL; cur = cur->next) {
1364
                fileinfo = (MsgFileInfo *)cur->data;
1365
                g_free(fileinfo->file);
1366
                g_free(fileinfo->flags);
1367
                g_free(fileinfo);
1368
        }
1369
1370
        g_slist_free(file_list);
1371
}
1372
1373
FILE *procmsg_open_message(MsgInfo *msginfo)
1374
{
1375
        FILE *fp;
1376
        gchar *file;
1377
1378
        g_return_val_if_fail(msginfo != NULL, NULL);
1379
1380
        file = procmsg_get_message_file_path(msginfo);
1381
        g_return_val_if_fail(file != NULL, NULL);
1382
1383
        if (!is_file_exist(file)) {
1384
                g_free(file);
1385
                file = procmsg_get_message_file(msginfo);
1386
                if (!file)
1387
                        return NULL;
1388
        }
1389
1390
        if ((fp = g_fopen(file, "rb")) == NULL) {
1391
                FILE_OP_ERROR(file, "procmsg_open_message: fopen");
1392
                g_free(file);
1393
                return NULL;
1394
        }
1395
1396
        g_free(file);
1397
1398
        if (MSG_IS_QUEUED(msginfo->flags)) {
1399
                gchar buf[BUFFSIZE];
1400
1401
                while (fgets(buf, sizeof(buf), fp) != NULL)
1402
                        if (buf[0] == '\r' || buf[0] == '\n') break;
1403
        }
1404
1405
        return fp;
1406
}
1407
1408
static DecryptMessageFunc decrypt_message_func = NULL;
1409
static gboolean auto_decrypt = TRUE;
1410
1411
void procmsg_set_decrypt_message_func(DecryptMessageFunc func)
1412
{
1413
        decrypt_message_func = func;
1414
}
1415
1416
void procmsg_set_auto_decrypt_message(gboolean enabled)
1417
{
1418
        auto_decrypt = enabled;
1419
}
1420
1421
FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
1422
{
1423
        FILE *fp;
1424
1425
        if (decrypt_message_func && auto_decrypt)
1426
                return decrypt_message_func(msginfo, mimeinfo);
1427
1428
        *mimeinfo = NULL;
1429
        if ((fp = procmsg_open_message(msginfo)) == NULL)
1430
                return NULL;
1431
        *mimeinfo = procmime_scan_mime_header(fp);
1432
1433
        return fp;
1434
}
1435
1436
gboolean procmsg_msg_exist(MsgInfo *msginfo)
1437
{
1438
        gchar *path;
1439
        gboolean ret;
1440
1441
        if (!msginfo) return FALSE;
1442
1443
        path = folder_item_get_path(msginfo->folder);
1444
        change_dir(path);
1445
        ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
1446
        g_free(path);
1447
1448
        return ret;
1449
}
1450
1451
gboolean procmsg_trash_messages_exist(void)
1452
{
1453
        FolderItem *trash;
1454
        GList *cur;
1455
1456
        for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
1457
                trash = FOLDER(cur->data)->trash;
1458
                if (trash && trash->total > 0)
1459
                        return TRUE;
1460
        }
1461
1462
        return FALSE;
1463
}
1464
1465
void procmsg_empty_trash(FolderItem *trash)
1466
{
1467
        if (!trash)
1468
                return;
1469
1470
        g_return_if_fail(trash->stype == F_TRASH);
1471
1472
        if (trash->total > 0) {
1473
                debug_print("Emptying messages in %s ...\n", trash->path);
1474
1475
                folder_item_remove_all_msg(trash);
1476
                procmsg_clear_cache(trash);
1477
                procmsg_clear_mark(trash);
1478
                trash->cache_dirty = FALSE;
1479
                trash->mark_dirty = FALSE;
1480
        }
1481
}
1482
1483
void procmsg_empty_all_trash(void)
1484
{
1485
        FolderItem *trash;
1486
        GList *cur;
1487
1488
        for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
1489
                trash = FOLDER(cur->data)->trash;
1490
                procmsg_empty_trash(trash);
1491
        }
1492
}
1493
1494
static gboolean remove_all_cached_messages_func(GNode *node, gpointer data)
1495
{
1496
        FolderItem *item;
1497
        gchar *dir;
1498
1499
        g_return_val_if_fail(node->data != NULL, FALSE);
1500
1501
        item = FOLDER_ITEM(node->data);
1502
        if (!item->path || item->stype == F_VIRTUAL)
1503
                return FALSE;
1504
1505
        dir = folder_item_get_path(item);
1506
        if (is_dir_exist(dir)) {
1507
                debug_print("removing all cached messages in '%s' ...\n",
1508
                            item->path);
1509
                remove_all_numbered_files(dir);
1510
        }
1511
        g_free(dir);
1512
1513
        return FALSE;
1514
}
1515
1516
void procmsg_remove_all_cached_messages(Folder *folder)
1517
{
1518
        g_return_if_fail(folder != NULL);
1519
        g_return_if_fail(FOLDER_IS_REMOTE(folder));
1520
1521
        debug_print("Removing all caches in the mailbox '%s' ...\n",
1522
                    folder->name);
1523
1524
        g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1525
                        remove_all_cached_messages_func, NULL);
1526
}
1527
1528
gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file)
1529
{
1530
        gint num;
1531
        MsgFlags flag = {0, 0};
1532
1533
        debug_print("saving sent message...\n");
1534
1535
        if (!outbox)
1536
                outbox = folder_get_default_outbox();
1537
        g_return_val_if_fail(outbox != NULL, -1);
1538
1539
        folder_item_scan(outbox);
1540
        if ((num = folder_item_add_msg(outbox, file, &flag, FALSE)) < 0) {
1541
                g_warning("can't save message\n");
1542
                return -1;
1543
        }
1544
1545
        return 0;
1546
}
1547
1548
static guint print_id = 0;
1549
1550
static gint print_command_exec(const gchar *file, const gchar *cmdline)
1551
{
1552
        static const gchar *def_cmd = "lpr %s";
1553
        gchar buf[1024];
1554
1555
#ifdef G_OS_WIN32
1556
        if (canonicalize_file_replace(file) < 0)
1557
                return -1;
1558
#endif
1559
1560
        if (cmdline && str_find_format_times(cmdline, 's') == 1)
1561
                g_snprintf(buf, sizeof(buf) - 1, cmdline, file);
1562
        else {
1563
                if (cmdline) {
1564
                        g_warning(_("Print command line is invalid: `%s'\n"),
1565
                                  cmdline);
1566
                        return -1;
1567
                }
1568
1569
#ifdef G_OS_WIN32
1570
                execute_print_file(file);
1571
                return 0;
1572
#else
1573
                g_snprintf(buf, sizeof(buf) - 1, def_cmd, file);
1574
#endif
1575
        }
1576
1577
        g_strchomp(buf);
1578
        if (buf[strlen(buf) - 1] != '&')
1579
                strcat(buf, "&");
1580
1581
        system(buf);
1582
1583
        return 0;
1584
}
1585
1586
void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline,
1587
                           gboolean all_headers)
1588
{
1589
        gchar *prtmp;
1590
        FILE *msgfp, *tmpfp, *prfp;
1591
        GPtrArray *headers;
1592
        gint i;
1593
        gchar buf[BUFFSIZE];
1594
1595
        g_return_if_fail(msginfo != NULL);
1596
1597
        if ((tmpfp = procmime_get_first_text_content
1598
                (msginfo, conv_get_locale_charset_str())) == NULL) {
1599
                g_warning("Can't get text part\n");
1600
                return;
1601
        }
1602
1603
        prtmp = g_strdup_printf("%s%cprinttmp-%08x.txt",
1604
                                get_mime_tmp_dir(), G_DIR_SEPARATOR,
1605
                                print_id++);
1606
1607
        if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
1608
                FILE_OP_ERROR(prtmp, "procmsg_print_message: fopen");
1609
                g_free(prtmp);
1610
                fclose(tmpfp);
1611
                return;
1612
        }
1613
1614
        if ((msgfp = procmsg_open_message(msginfo)) == NULL) {
1615
                fclose(prfp);
1616
                g_free(prtmp);
1617
                fclose(tmpfp);
1618
                return;
1619
        }
1620
1621
        if (all_headers)
1622
                headers = procheader_get_header_array_asis(msgfp, NULL);
1623
        else
1624
                headers = procheader_get_header_array_for_display(msgfp, NULL);
1625
1626
        fclose(msgfp);
1627
1628
        for (i = 0; i < headers->len; i++) {
1629
                Header *hdr;
1630
                gchar *locale_str;
1631
                const gchar *body;
1632
1633
                hdr = g_ptr_array_index(headers, i);
1634
1635
                if (!g_ascii_strcasecmp(hdr->name, "Subject"))
1636
                        body = msginfo->subject;
1637
                else if (!g_ascii_strcasecmp(hdr->name, "From"))
1638
                        body = msginfo->from;
1639
                else if (!g_ascii_strcasecmp(hdr->name, "To"))
1640
                        body = msginfo->to;
1641
                else if (!g_ascii_strcasecmp(hdr->name, "Cc")) {
1642
                        unfold_line(hdr->body);
1643
                        body = hdr->body;
1644
                        while (g_ascii_isspace(*body))
1645
                                body++;
1646
                } else {
1647
                        body = hdr->body;
1648
                        while (g_ascii_isspace(*body))
1649
                                body++;
1650
                }
1651
1652
                if (body && *body != '\0') {
1653
                        locale_str = conv_codeset_strdup
1654
                                (body, CS_INTERNAL,
1655
                                 conv_get_locale_charset_str());
1656
                        fprintf(prfp, "%s: %s\n", hdr->name,
1657
                                locale_str ? locale_str : body);
1658
                        g_free(locale_str);
1659
                } else {
1660
                        fprintf(prfp, "%s: (none)\n", hdr->name);
1661
                }
1662
        }
1663
1664
        procheader_header_array_destroy(headers);
1665
1666
        fputc('\n', prfp);
1667
1668
        while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1669
                fputs(buf, prfp);
1670
1671
        fclose(prfp);
1672
        fclose(tmpfp);
1673
1674
        print_command_exec(prtmp, cmdline);
1675
1676
        g_free(prtmp);
1677
}
1678
1679
void procmsg_print_message_part(MsgInfo *msginfo, MimeInfo *partinfo,
1680
                                const gchar *cmdline, gboolean all_headers)
1681
{
1682
        FILE *msgfp, *tmpfp, *prfp;
1683
        gchar *prtmp;
1684
        gchar buf[BUFFSIZE];
1685
1686
        if ((msgfp = procmsg_open_message(msginfo)) == NULL) {
1687
                return;
1688
        }
1689
1690
        if ((tmpfp = procmime_get_text_content
1691
                (partinfo, msgfp, conv_get_locale_charset_str())) == NULL) {
1692
                fclose(msgfp);
1693
                return;
1694
        }
1695
        fclose(msgfp);
1696
1697
        prtmp = g_strdup_printf("%s%cprinttmp-%08x.txt",
1698
                                get_mime_tmp_dir(), G_DIR_SEPARATOR,
1699
                                print_id++);
1700
        if ((prfp = g_fopen(prtmp, "wb")) == NULL) {
1701
                FILE_OP_ERROR(prtmp, "procmsg_print_message_part: fopen");
1702
                g_free(prtmp);
1703
                fclose(tmpfp);
1704
                return;
1705
        }
1706
1707
        while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1708
                fputs(buf, prfp);
1709
1710
        fclose(prfp);
1711
        fclose(tmpfp);
1712
1713
        print_command_exec(prtmp, cmdline);
1714
1715
        g_free(prtmp);
1716
}
1717
1718
static gboolean procmsg_get_flags(FolderItem *item, gint num,
1719
                                  MsgPermFlags *flags)
1720
{
1721
        FILE *fp;
1722
        guint32 idata;
1723
        gint read_num;
1724
        MsgPermFlags perm_flags;
1725
        gboolean found = FALSE;
1726
        GSList *cur;
1727
1728
        if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL)
1729
                return FALSE;
1730
1731
        while (fread(&idata, sizeof(idata), 1, fp) == 1) {
1732
                read_num = idata;
1733
                if (fread(&idata, sizeof(idata), 1, fp) != 1)
1734
                        break;
1735
                perm_flags = idata;
1736
                if (read_num == num) {
1737
                        *flags = perm_flags;
1738
                        found = TRUE;
1739
                        break;
1740
                }
1741
        }
1742
1743
        fclose(fp);
1744
        if (found)
1745
                return TRUE;
1746
1747
        for (cur = item->mark_queue; cur != NULL; cur = cur->next) {
1748
                MsgFlagInfo *flaginfo = (MsgFlagInfo *)cur->data;
1749
1750
                if (flaginfo->msgnum == num) {
1751
                        *flags = flaginfo->flags.perm_flags;
1752
                        found = TRUE;
1753
                        break;
1754
                }
1755
        }
1756
1757
        return found;
1758
}
1759
1760
MsgInfo *procmsg_get_msginfo(FolderItem *item, gint num)
1761
{
1762
        MsgInfo *msginfo;
1763
        FolderType type;
1764
1765
        g_return_val_if_fail(item->folder != NULL, NULL);
1766
1767
        msginfo = folder_item_get_msginfo(item, num);
1768
        if (!msginfo)
1769
                return NULL;
1770
1771
        type = FOLDER_TYPE(item->folder);
1772
        if (type == F_MH || type == F_IMAP) {
1773
                if (item->stype == F_QUEUE) {
1774
                        MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1775
                } else if (item->stype == F_DRAFT) {
1776
                        MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1777
                }
1778
        }
1779
        if (type == F_IMAP) {
1780
                MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP);
1781
        } else if (type == F_NEWS) {
1782
                MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS);
1783
        }
1784
1785
        if (type == F_MH || type == F_NEWS) {
1786
                MsgPermFlags flags = 0;
1787
                if (procmsg_get_flags(item, num, &flags))
1788
                        msginfo->flags.perm_flags = flags;
1789
        }
1790
1791
        return msginfo;
1792
}
1793
1794
MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1795
{
1796
        MsgInfo *newmsginfo;
1797
1798
        if (msginfo == NULL) return NULL;
1799
1800
        newmsginfo = g_new0(MsgInfo, 1);
1801
1802
#define MEMBCOPY(mmb)        newmsginfo->mmb = msginfo->mmb
1803
#define MEMBDUP(mmb)        newmsginfo->mmb = msginfo->mmb ? \
1804
                        g_strdup(msginfo->mmb) : NULL
1805
1806
        MEMBCOPY(msgnum);
1807
        MEMBCOPY(size);
1808
        MEMBCOPY(mtime);
1809
        MEMBCOPY(date_t);
1810
1811
        MEMBCOPY(flags);
1812
1813
        MEMBDUP(fromname);
1814
1815
        MEMBDUP(date);
1816
        MEMBDUP(from);
1817
        MEMBDUP(to);
1818
        MEMBDUP(cc);
1819
        MEMBDUP(newsgroups);
1820
        MEMBDUP(subject);
1821
        MEMBDUP(msgid);
1822
        MEMBDUP(inreplyto);
1823
1824
        MEMBCOPY(folder);
1825
        MEMBCOPY(to_folder);
1826
1827
        MEMBDUP(xface);
1828
1829
        MEMBDUP(file_path);
1830
1831
        if (msginfo->encinfo) {
1832
                newmsginfo->encinfo = g_new0(MsgEncryptInfo, 1);
1833
                MEMBDUP(encinfo->plaintext_file);
1834
                MEMBDUP(encinfo->sigstatus);
1835
                MEMBDUP(encinfo->sigstatus_full);
1836
                MEMBCOPY(encinfo->decryption_failed);
1837
        }
1838
1839
        return newmsginfo;
1840
}
1841
1842
MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1843
{
1844
        MsgInfo *full_msginfo;
1845
        gchar *file;
1846
1847
        if (msginfo == NULL) return NULL;
1848
1849
        file = procmsg_get_message_file(msginfo);
1850
        if (!file) {
1851
                g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
1852
                return NULL;
1853
        }
1854
1855
        full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE);
1856
        g_free(file);
1857
        if (!full_msginfo) return NULL;
1858
1859
        full_msginfo->msgnum = msginfo->msgnum;
1860
        full_msginfo->size = msginfo->size;
1861
        full_msginfo->mtime = msginfo->mtime;
1862
        full_msginfo->folder = msginfo->folder;
1863
        full_msginfo->to_folder = msginfo->to_folder;
1864
1865
        full_msginfo->file_path = g_strdup(msginfo->file_path);
1866
1867
        if (msginfo->encinfo) {
1868
                full_msginfo->encinfo = g_new0(MsgEncryptInfo, 1);
1869
                full_msginfo->encinfo->plaintext_file =
1870
                        g_strdup(msginfo->encinfo->plaintext_file);
1871
                full_msginfo->encinfo->sigstatus =
1872
                        g_strdup(msginfo->encinfo->sigstatus);
1873
                full_msginfo->encinfo->sigstatus_full =
1874
                        g_strdup(msginfo->encinfo->sigstatus_full);
1875
                full_msginfo->encinfo->decryption_failed =
1876
                        msginfo->encinfo->decryption_failed;
1877
        }
1878
1879
        return full_msginfo;
1880
}
1881
1882
gboolean procmsg_msginfo_equal(MsgInfo *msginfo_a, MsgInfo *msginfo_b)
1883
{
1884
        if (!msginfo_a || !msginfo_b)
1885
                return FALSE;
1886
1887
        if (msginfo_a == msginfo_b)
1888
                return TRUE;
1889
1890
        if (msginfo_a->folder == msginfo_b->folder &&
1891
            msginfo_a->msgnum == msginfo_b->msgnum &&
1892
            msginfo_a->size   == msginfo_b->size   &&
1893
            msginfo_a->mtime  == msginfo_b->mtime)
1894
                return TRUE;
1895
1896
        return FALSE;
1897
}
1898
1899
void procmsg_msginfo_free(MsgInfo *msginfo)
1900
{
1901
        if (msginfo == NULL) return;
1902
1903
        g_free(msginfo->xface);
1904
1905
        g_free(msginfo->fromname);
1906
1907
        g_free(msginfo->date);
1908
        g_free(msginfo->from);
1909
        g_free(msginfo->to);
1910
        g_free(msginfo->cc);
1911
        g_free(msginfo->newsgroups);
1912
        g_free(msginfo->subject);
1913
        g_free(msginfo->msgid);
1914
        g_free(msginfo->inreplyto);
1915
1916
        slist_free_strings(msginfo->references);
1917
        g_slist_free(msginfo->references);
1918
1919
        g_free(msginfo->file_path);
1920
1921
        if (msginfo->encinfo) {
1922
                g_free(msginfo->encinfo->plaintext_file);
1923
                g_free(msginfo->encinfo->sigstatus);
1924
                g_free(msginfo->encinfo->sigstatus_full);
1925
                g_free(msginfo->encinfo);
1926
        }
1927
1928
        g_free(msginfo);
1929
}
1930
1931
gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1932
{
1933
        const MsgInfo *msginfo1 = a;
1934
        const MsgInfo *msginfo2 = b;
1935
1936
        if (!msginfo1 || !msginfo2)
1937
                return 0;
1938
1939
        return msginfo1->msgnum - msginfo2->msgnum;
1940
}
1941
1942
#define CMP_FUNC_DEF(func_name, val)                                        \
1943
static gint func_name(gconstpointer a, gconstpointer b)                        \
1944
{                                                                        \
1945
        const MsgInfo *msginfo1 = a;                                        \
1946
        const MsgInfo *msginfo2 = b;                                        \
1947
        gint ret;                                                        \
1948
                                                                        \
1949
        if (!msginfo1 || !msginfo2)                                        \
1950
                return 0;                                                \
1951
                                                                        \
1952
        ret = (val);                                                        \
1953
        if (ret == 0)                                                        \
1954
                ret = msginfo1->date_t - msginfo2->date_t;                \
1955
                                                                        \
1956
        return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1);        \
1957
}
1958
1959
CMP_FUNC_DEF(procmsg_cmp_by_mark,
1960
             MSG_IS_MARKED(msginfo1->flags) - MSG_IS_MARKED(msginfo2->flags))
1961
CMP_FUNC_DEF(procmsg_cmp_by_unread,
1962
             MSG_IS_UNREAD(msginfo1->flags) - MSG_IS_UNREAD(msginfo2->flags))
1963
CMP_FUNC_DEF(procmsg_cmp_by_mime,
1964
             MSG_IS_MIME(msginfo1->flags) - MSG_IS_MIME(msginfo2->flags))
1965
CMP_FUNC_DEF(procmsg_cmp_by_label,
1966
             MSG_GET_COLORLABEL(msginfo1->flags) -
1967
             MSG_GET_COLORLABEL(msginfo2->flags))
1968
CMP_FUNC_DEF(procmsg_cmp_by_size, msginfo1->size - msginfo2->size)
1969
1970
#undef CMP_FUNC_DEF
1971
#define CMP_FUNC_DEF(func_name, val)                                        \
1972
static gint func_name(gconstpointer a, gconstpointer b)                        \
1973
{                                                                        \
1974
        const MsgInfo *msginfo1 = a;                                        \
1975
        const MsgInfo *msginfo2 = b;                                        \
1976
                                                                        \
1977
        if (!msginfo1 || !msginfo2)                                        \
1978
                return 0;                                                \
1979
                                                                        \
1980
        return (val) * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1);        \
1981
}
1982
1983
CMP_FUNC_DEF(procmsg_cmp_by_number, msginfo1->msgnum - msginfo2->msgnum)
1984
CMP_FUNC_DEF(procmsg_cmp_by_date, msginfo1->date_t - msginfo2->date_t)
1985
1986
#undef CMP_FUNC_DEF
1987
#define CMP_FUNC_DEF(func_name, var_name)                                \
1988
static gint func_name(gconstpointer a, gconstpointer b)                        \
1989
{                                                                        \
1990
        const MsgInfo *msginfo1 = a;                                        \
1991
        const MsgInfo *msginfo2 = b;                                        \
1992
        gint ret;                                                        \
1993
                                                                        \
1994
        if (!msginfo1->var_name)                                        \
1995
                return (msginfo2->var_name != NULL) *                        \
1996
                        (cmp_func_sort_type == SORT_ASCENDING ? -1 : 1);\
1997
        if (!msginfo2->var_name)                                        \
1998
                return (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1);        \
1999
                                                                        \
2000
        ret = g_ascii_strcasecmp                                        \
2001
                (msginfo1->var_name, msginfo2->var_name);                \
2002
        if (ret == 0)                                                        \
2003
                ret = msginfo1->date_t - msginfo2->date_t;                \
2004
                                                                        \
2005
        return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1);        \
2006
}
2007
2008
CMP_FUNC_DEF(procmsg_cmp_by_from, fromname)
2009
CMP_FUNC_DEF(procmsg_cmp_by_to, to)
2010
2011
#undef CMP_FUNC_DEF
2012
2013
static gint procmsg_cmp_by_subject(gconstpointer a, gconstpointer b)
2014
{
2015
        const MsgInfo *msginfo1 = a;
2016
        const MsgInfo *msginfo2 = b;
2017
        gint ret;
2018
2019
        if (!msginfo1->subject)
2020
                return (msginfo2->subject != NULL) *
2021
                        (cmp_func_sort_type == SORT_ASCENDING ? -1 : 1);
2022
        if (!msginfo2->subject)
2023
                return (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1);
2024
2025
        ret = subject_compare_for_sort(msginfo1->subject, msginfo2->subject);
2026
        if (ret == 0)
2027
                ret = msginfo1->date_t - msginfo2->date_t;
2028
2029
        return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1);
2030
}