Statistics
| Revision:

root / src / mh.c @ 391

History | View | Annotate | Download (31.6 kB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2005 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
#ifdef HAVE_CONFIG_H
21
#  include "config.h"
22
#endif
23
24
#include "defs.h"
25
26
#include <glib.h>
27
#include <glib/gi18n.h>
28
#include <dirent.h>
29
#include <sys/stat.h>
30
#include <unistd.h>
31
#include <string.h>
32
#include <errno.h>
33
34
#undef MEASURE_TIME
35
36
#ifdef MEASURE_TIME
37
#  include <sys/time.h>
38
#endif
39
40
#include "folder.h"
41
#include "mh.h"
42
#include "procmsg.h"
43
#include "procheader.h"
44
#include "utils.h"
45
#include "prefs_common.h"
46
47
static void        mh_folder_init                (Folder                *folder,
48
                                         const gchar        *name,
49
                                         const gchar        *path);
50
51
static Folder        *mh_folder_new                (const gchar        *name,
52
                                         const gchar        *path);
53
static void     mh_folder_destroy        (Folder                *folder);
54
55
static GSList  *mh_get_msg_list                (Folder                *folder,
56
                                         FolderItem        *item,
57
                                         gboolean         use_cache);
58
static gchar   *mh_fetch_msg                (Folder                *folder,
59
                                         FolderItem        *item,
60
                                         gint                 num);
61
static MsgInfo *mh_get_msginfo                (Folder                *folder,
62
                                         FolderItem        *item,
63
                                         gint                 num);
64
static gint     mh_add_msg                (Folder                *folder,
65
                                         FolderItem        *dest,
66
                                         const gchar        *file,
67
                                         MsgFlags        *flags,
68
                                         gboolean         remove_source);
69
static gint     mh_add_msgs                (Folder                *folder,
70
                                         FolderItem        *dest,
71
                                         GSList                *file_list,
72
                                         gboolean         remove_source,
73
                                         gint                *first);
74
static gint     mh_move_msg                (Folder                *folder,
75
                                         FolderItem        *dest,
76
                                         MsgInfo        *msginfo);
77
static gint     mh_move_msgs                (Folder                *folder,
78
                                         FolderItem        *dest,
79
                                         GSList                *msglist);
80
static gint     mh_copy_msg                (Folder                *folder,
81
                                         FolderItem        *dest,
82
                                         MsgInfo        *msginfo);
83
static gint     mh_copy_msgs                (Folder                *folder,
84
                                         FolderItem        *dest,
85
                                         GSList                *msglist);
86
static gint     mh_remove_msg                (Folder                *folder,
87
                                         FolderItem        *item,
88
                                         MsgInfo        *msginfo);
89
static gint     mh_remove_all_msg        (Folder                *folder,
90
                                         FolderItem        *item);
91
static gboolean mh_is_msg_changed        (Folder                *folder,
92
                                         FolderItem        *item,
93
                                         MsgInfo        *msginfo);
94
static gint    mh_close                        (Folder                *folder,
95
                                         FolderItem        *item);
96
97
static gint    mh_scan_folder_full        (Folder                *folder,
98
                                         FolderItem        *item,
99
                                         gboolean         count_sum);
100
static gint    mh_scan_folder                (Folder                *folder,
101
                                         FolderItem        *item);
102
static gint    mh_scan_tree                (Folder                *folder);
103
104
static gint    mh_create_tree                (Folder                *folder);
105
static FolderItem *mh_create_folder        (Folder                *folder,
106
                                         FolderItem        *parent,
107
                                         const gchar        *name);
108
static gint    mh_rename_folder                (Folder                *folder,
109
                                         FolderItem        *item,
110
                                         const gchar        *name);
111
static gint    mh_move_folder                (Folder                *folder,
112
                                         FolderItem        *item,
113
                                         FolderItem        *new_parent);
114
static gint    mh_remove_folder                (Folder                *folder,
115
                                         FolderItem        *item);
116
117
static gchar   *mh_get_new_msg_filename                (FolderItem        *dest);
118
119
static gint        mh_do_move_msgs                        (Folder                *folder,
120
                                                 FolderItem        *dest,
121
                                                 GSList                *msglist);
122
123
static time_t  mh_get_mtime                        (FolderItem        *item);
124
static GSList  *mh_get_uncached_msgs                (GHashTable        *msg_table,
125
                                                 FolderItem        *item);
126
static MsgInfo *mh_parse_msg                        (const gchar        *file,
127
                                                 FolderItem        *item);
128
static void        mh_remove_missing_folder_items        (Folder                *folder);
129
static void        mh_scan_tree_recursive                (FolderItem        *item);
130
131
static gboolean mh_rename_folder_func                (GNode                *node,
132
                                                 gpointer         data);
133
134
static FolderClass mh_class =
135
{
136
        F_MH,
137
138
        mh_folder_new,
139
        mh_folder_destroy,
140
141
        mh_scan_tree,
142
        mh_create_tree,
143
144
        mh_get_msg_list,
145
        mh_fetch_msg,
146
        mh_get_msginfo,
147
        mh_add_msg,
148
        mh_add_msgs,
149
        mh_move_msg,
150
        mh_move_msgs,
151
        mh_copy_msg,
152
        mh_copy_msgs,
153
        mh_remove_msg,
154
        NULL,
155
        mh_remove_all_msg,
156
        mh_is_msg_changed,
157
        mh_close,
158
        mh_scan_folder,
159
160
        mh_create_folder,
161
        mh_rename_folder,
162
        mh_move_folder,
163
        mh_remove_folder,
164
};
165
166
167
FolderClass *mh_get_class(void)
168
{
169
        return &mh_class;
170
}
171
172
static Folder *mh_folder_new(const gchar *name, const gchar *path)
173
{
174
        Folder *folder;
175
176
        folder = (Folder *)g_new0(MHFolder, 1);
177
        mh_folder_init(folder, name, path);
178
179
        return folder;
180
}
181
182
static void mh_folder_destroy(Folder *folder)
183
{
184
        folder_local_folder_destroy(LOCAL_FOLDER(folder));
185
}
186
187
static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path)
188
{
189
        folder->klass = mh_get_class();
190
        folder_local_folder_init(folder, name, path);
191
}
192
193
static GSList *mh_get_msg_list(Folder *folder, FolderItem *item,
194
                               gboolean use_cache)
195
{
196
        GSList *mlist;
197
        GHashTable *msg_table;
198
        time_t cur_mtime;
199
#ifdef MEASURE_TIME
200
        struct timeval tv_before, tv_after, tv_result;
201
202
        gettimeofday(&tv_before, NULL);
203
#endif
204
205
        g_return_val_if_fail(item != NULL, NULL);
206
207
        cur_mtime = mh_get_mtime(item);
208
209
        if (use_cache && item->mtime == cur_mtime) {
210
                debug_print("Folder is not modified.\n");
211
                mlist = procmsg_read_cache(item, FALSE);
212
                if (!mlist) {
213
                        mlist = mh_get_uncached_msgs(NULL, item);
214
                        if (mlist)
215
                                item->cache_dirty = TRUE;
216
                }
217
        } else if (use_cache) {
218
                GSList *newlist, *cur, *next;
219
220
                mlist = procmsg_read_cache
221
                        (item, prefs_common.strict_cache_check);
222
                msg_table = procmsg_msg_hash_table_create(mlist);
223
                newlist = mh_get_uncached_msgs(msg_table, item);
224
                if (newlist)
225
                        item->cache_dirty = TRUE;
226
                if (msg_table)
227
                        g_hash_table_destroy(msg_table);
228
229
                if (!prefs_common.strict_cache_check) {
230
                        /* remove nonexistent messages */
231
                        for (cur = mlist; cur != NULL; cur = next) {
232
                                MsgInfo *msginfo = (MsgInfo *)cur->data;
233
                                next = cur->next;
234
                                if (!MSG_IS_CACHED(msginfo->flags)) {
235
                                        debug_print("removing nonexistent message %d from cache\n", msginfo->msgnum);
236
                                        mlist = g_slist_remove(mlist, msginfo);
237
                                        procmsg_msginfo_free(msginfo);
238
                                        item->cache_dirty = TRUE;
239
                                        item->mark_dirty = TRUE;
240
                                }
241
                        }
242
                }
243
244
                mlist = g_slist_concat(mlist, newlist);
245
        } else {
246
                mlist = mh_get_uncached_msgs(NULL, item);
247
                item->cache_dirty = TRUE;
248
        }
249
250
        item->mtime = cur_mtime;
251
252
        procmsg_set_flags(mlist, item);
253
254
        mlist = procmsg_sort_msg_list(mlist, item->sort_key, item->sort_type);
255
256
#ifdef MEASURE_TIME
257
        gettimeofday(&tv_after, NULL);
258
259
        timersub(&tv_after, &tv_before, &tv_result);
260
        g_print("mh_get_msg_list: %s: elapsed time: %ld.%06ld sec\n",
261
                item->path, tv_result.tv_sec, tv_result.tv_usec);
262
#endif
263
        debug_print("cache_dirty: %d, mark_dirty: %d\n",
264
                    item->cache_dirty, item->mark_dirty);
265
266
        return mlist;
267
}
268
269
static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num)
270
{
271
        gchar *path;
272
        gchar *file;
273
274
        g_return_val_if_fail(item != NULL, NULL);
275
        g_return_val_if_fail(num > 0, NULL);
276
277
        if (item->last_num < 0 || num > item->last_num) {
278
                mh_scan_folder(folder, item);
279
                if (item->last_num < 0) return NULL;
280
        }
281
282
        g_return_val_if_fail(num <= item->last_num, NULL);
283
284
        path = folder_item_get_path(item);
285
        file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
286
        g_free(path);
287
        if (!is_file_exist(file)) {
288
                g_free(file);
289
                return NULL;
290
        }
291
292
        return file;
293
}
294
295
static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num)
296
{
297
        MsgInfo *msginfo;
298
        gchar *file;
299
300
        g_return_val_if_fail(item != NULL, NULL);
301
        g_return_val_if_fail(num > 0, NULL);
302
303
        file = mh_fetch_msg(folder, item, num);
304
        if (!file) return NULL;
305
306
        msginfo = mh_parse_msg(file, item);
307
        if (msginfo)
308
                msginfo->msgnum = num;
309
310
        g_free(file);
311
312
        return msginfo;
313
}
314
315
static gchar *mh_get_new_msg_filename(FolderItem *dest)
316
{
317
        gchar *destfile;
318
        gchar *destpath;
319
320
        destpath = folder_item_get_path(dest);
321
        g_return_val_if_fail(destpath != NULL, NULL);
322
323
        if (!is_dir_exist(destpath))
324
                make_dir_hier(destpath);
325
326
        for (;;) {
327
                destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
328
                                           dest->last_num + 1);
329
                if (is_file_entry_exist(destfile)) {
330
                        dest->last_num++;
331
                        g_free(destfile);
332
                } else
333
                        break;
334
        }
335
336
        g_free(destpath);
337
338
        return destfile;
339
}
340
341
#define SET_DEST_MSG_FLAGS(fp, dest, n, fl)                                \
342
{                                                                        \
343
        MsgInfo newmsginfo;                                                \
344
                                                                        \
345
        newmsginfo.msgnum = n;                                                \
346
        newmsginfo.flags = fl;                                                \
347
        if (dest->stype == F_OUTBOX ||                                        \
348
            dest->stype == F_QUEUE  ||                                        \
349
            dest->stype == F_DRAFT  ||                                        \
350
            dest->stype == F_TRASH)                                        \
351
                MSG_UNSET_PERM_FLAGS(newmsginfo.flags,                        \
352
                                     MSG_NEW|MSG_UNREAD|MSG_DELETED);        \
353
                                                                        \
354
        if (fp)                                                                \
355
                procmsg_write_flags(&newmsginfo, fp);                        \
356
        else if (dest->opened)                                                \
357
                procmsg_add_flags(dest, n, newmsginfo.flags);                \
358
}
359
360
static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
361
                       MsgFlags *flags, gboolean remove_source)
362
{
363
        GSList file_list;
364
        MsgFileInfo fileinfo;
365
366
        g_return_val_if_fail(file != NULL, -1);
367
368
        fileinfo.file = (gchar *)file;
369
        fileinfo.flags = flags;
370
        file_list.data = &fileinfo;
371
        file_list.next = NULL;
372
373
        return mh_add_msgs(folder, dest, &file_list, remove_source, NULL);
374
}
375
376
static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
377
                        gboolean remove_source, gint *first)
378
{
379
        gchar *destfile;
380
        GSList *cur;
381
        MsgFileInfo *fileinfo;
382
        gint first_ = 0;
383
        FILE *fp;
384
385
        g_return_val_if_fail(dest != NULL, -1);
386
        g_return_val_if_fail(file_list != NULL, -1);
387
388
        if (dest->last_num < 0) {
389
                mh_scan_folder(folder, dest);
390
                if (dest->last_num < 0) return -1;
391
        }
392
393
        if ((((MsgFileInfo *)file_list->data)->flags == NULL &&
394
            file_list->next == NULL) || dest->opened)
395
                fp = NULL;
396
        else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL)
397
                g_warning("Can't open mark file.\n");
398
399
        for (cur = file_list; cur != NULL; cur = cur->next) {
400
                fileinfo = (MsgFileInfo *)cur->data;
401
402
                destfile = mh_get_new_msg_filename(dest);
403
                if (destfile == NULL) return -1;
404
                if (first_ == 0 || first_ > dest->last_num + 1)
405
                        first_ = dest->last_num + 1;
406
407
                if (link(fileinfo->file, destfile) < 0) {
408
                        if (copy_file(fileinfo->file, destfile, TRUE) < 0) {
409
                                g_warning(_("can't copy message %s to %s\n"),
410
                                          fileinfo->file, destfile);
411
                                g_free(destfile);
412
                                return -1;
413
                        }
414
                }
415
416
                g_free(destfile);
417
                dest->last_num++;
418
                dest->total++;
419
                dest->updated = TRUE;
420
421
                if (fileinfo->flags) {
422
                        if (MSG_IS_RECEIVED(*fileinfo->flags)) {
423
                                if (dest->unmarked_num == 0)
424
                                        dest->new = 0;
425
                                dest->unmarked_num++;
426
                                procmsg_add_mark_queue(dest, dest->last_num,
427
                                                       *fileinfo->flags);
428
                        } else {
429
                                SET_DEST_MSG_FLAGS(fp, dest, dest->last_num,
430
                                                   *fileinfo->flags);
431
                        }
432
                        if (MSG_IS_NEW(*fileinfo->flags))
433
                                dest->new++;
434
                        if (MSG_IS_UNREAD(*fileinfo->flags))
435
                                dest->unread++;
436
                } else {
437
                        if (dest->unmarked_num == 0)
438
                                dest->new = 0;
439
                        dest->unmarked_num++;
440
                        dest->new++;
441
                        dest->unread++;
442
                }
443
        }
444
445
        if (fp) fclose(fp);
446
447
        if (first)
448
                *first = first_;
449
450
        if (remove_source) {
451
                for (cur = file_list; cur != NULL; cur = cur->next) {
452
                        fileinfo = (MsgFileInfo *)cur->data;
453
                        if (unlink(fileinfo->file) < 0)
454
                                FILE_OP_ERROR(fileinfo->file, "unlink");
455
                }
456
        }
457
458
        return dest->last_num;
459
}
460
461
static gint mh_do_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
462
{
463
        FolderItem *src;
464
        gchar *srcfile;
465
        gchar *destfile;
466
        FILE *fp;
467
        GSList *cur;
468
        MsgInfo *msginfo;
469
470
        g_return_val_if_fail(dest != NULL, -1);
471
        g_return_val_if_fail(msglist != NULL, -1);
472
473
        if (dest->last_num < 0) {
474
                mh_scan_folder(folder, dest);
475
                if (dest->last_num < 0) return -1;
476
        }
477
478
        if (dest->opened)
479
                fp = NULL;
480
        else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL)
481
                g_warning(_("Can't open mark file.\n"));
482
483
        for (cur = msglist; cur != NULL; cur = cur->next) {
484
                msginfo = (MsgInfo *)cur->data;
485
                src = msginfo->folder;
486
487
                if (src == dest) {
488
                        g_warning(_("the src folder is identical to the dest.\n"));
489
                        continue;
490
                }
491
                debug_print("Moving message %s%c%d to %s ...\n",
492
                            src->path, G_DIR_SEPARATOR, msginfo->msgnum,
493
                            dest->path);
494
495
                destfile = mh_get_new_msg_filename(dest);
496
                if (!destfile) break;
497
                srcfile = procmsg_get_message_file(msginfo);
498
499
                if (move_file(srcfile, destfile, FALSE) < 0) {
500
                        g_free(srcfile);
501
                        g_free(destfile);
502
                        break;
503
                }
504
505
                g_free(srcfile);
506
                g_free(destfile);
507
                src->total--;
508
                src->updated = TRUE;
509
                dest->last_num++;
510
                dest->total++;
511
                dest->updated = TRUE;
512
513
                if (fp) {
514
                        SET_DEST_MSG_FLAGS(fp, dest, dest->last_num,
515
                                           msginfo->flags);
516
                }
517
518
                if (MSG_IS_NEW(msginfo->flags)) {
519
                        src->new--;
520
                        dest->new++;
521
                }
522
                if (MSG_IS_UNREAD(msginfo->flags)) {
523
                        src->unread--;
524
                        dest->unread++;
525
                }
526
527
                MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
528
        }
529
530
        if (fp) fclose(fp);
531
532
        return dest->last_num;
533
}
534
535
static gint mh_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
536
{
537
        GSList msglist;
538
539
        g_return_val_if_fail(msginfo != NULL, -1);
540
541
        msglist.data = msginfo;
542
        msglist.next = NULL;
543
544
        return mh_move_msgs(folder, dest, &msglist);
545
}
546
547
static gint mh_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
548
{
549
        MsgInfo *msginfo;
550
        GSList *file_list;
551
        gint ret = 0;
552
        gint first;
553
554
        msginfo = (MsgInfo *)msglist->data;
555
        if (folder == msginfo->folder->folder)
556
                return mh_do_move_msgs(folder, dest, msglist);
557
558
        file_list = procmsg_get_message_file_list(msglist);
559
        g_return_val_if_fail(file_list != NULL, -1);
560
561
        ret = mh_add_msgs(folder, dest, file_list, FALSE, &first);
562
563
        procmsg_message_file_list_free(file_list);
564
565
        if (ret != -1)
566
                ret = folder_item_remove_msgs(msginfo->folder, msglist);
567
568
        return ret;
569
}
570
571
static gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
572
{
573
        GSList msglist;
574
575
        g_return_val_if_fail(msginfo != NULL, -1);
576
577
        msglist.data = msginfo;
578
        msglist.next = NULL;
579
580
        return mh_copy_msgs(folder, dest, &msglist);
581
}
582
583
static gint mh_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
584
{
585
        gchar *srcfile;
586
        gchar *destfile;
587
        FILE *fp;
588
        GSList *cur;
589
        MsgInfo *msginfo;
590
591
        g_return_val_if_fail(dest != NULL, -1);
592
        g_return_val_if_fail(msglist != NULL, -1);
593
594
        if (dest->last_num < 0) {
595
                mh_scan_folder(folder, dest);
596
                if (dest->last_num < 0) return -1;
597
        }
598
599
        if (dest->opened)
600
                fp = NULL;
601
        else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL)
602
                g_warning(_("Can't open mark file.\n"));
603
604
        for (cur = msglist; cur != NULL; cur = cur->next) {
605
                msginfo = (MsgInfo *)cur->data;
606
607
                if (msginfo->folder == dest) {
608
                        g_warning(_("the src folder is identical to the dest.\n"));
609
                        continue;
610
                }
611
                debug_print(_("Copying message %s%c%d to %s ...\n"),
612
                            msginfo->folder->path, G_DIR_SEPARATOR,
613
                            msginfo->msgnum, dest->path);
614
615
                destfile = mh_get_new_msg_filename(dest);
616
                if (!destfile) break;
617
                srcfile = procmsg_get_message_file(msginfo);
618
619
                if (copy_file(srcfile, destfile, TRUE) < 0) {
620
                        FILE_OP_ERROR(srcfile, "copy");
621
                        g_free(srcfile);
622
                        g_free(destfile);
623
                        break;
624
                }
625
626
                g_free(srcfile);
627
                g_free(destfile);
628
                dest->last_num++;
629
                dest->total++;
630
                dest->updated = TRUE;
631
632
                if (fp) {
633
                        SET_DEST_MSG_FLAGS(fp, dest, dest->last_num,
634
                                           msginfo->flags);
635
                }
636
637
                if (MSG_IS_NEW(msginfo->flags))
638
                        dest->new++;
639
                if (MSG_IS_UNREAD(msginfo->flags))
640
                        dest->unread++;
641
        }
642
643
        if (fp) fclose(fp);
644
645
        return dest->last_num;
646
}
647
648
static gint mh_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo)
649
{
650
        gchar *file;
651
652
        g_return_val_if_fail(item != NULL, -1);
653
654
        file = mh_fetch_msg(folder, item, msginfo->msgnum);
655
        g_return_val_if_fail(file != NULL, -1);
656
657
        if (unlink(file) < 0) {
658
                FILE_OP_ERROR(file, "unlink");
659
                g_free(file);
660
                return -1;
661
        }
662
        g_free(file);
663
664
        item->total--;
665
        item->updated = TRUE;
666
        if (MSG_IS_NEW(msginfo->flags))
667
                item->new--;
668
        if (MSG_IS_UNREAD(msginfo->flags))
669
                item->unread--;
670
        MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
671
672
        if (msginfo->msgnum == item->last_num)
673
                mh_scan_folder_full(folder, item, FALSE);
674
675
        return 0;
676
}
677
678
static gint mh_remove_all_msg(Folder *folder, FolderItem *item)
679
{
680
        gchar *path;
681
        gint val;
682
683
        g_return_val_if_fail(item != NULL, -1);
684
685
        path = folder_item_get_path(item);
686
        g_return_val_if_fail(path != NULL, -1);
687
        val = remove_all_numbered_files(path);
688
        g_free(path);
689
        if (val == 0) {
690
                item->new = item->unread = item->total = 0;
691
                item->last_num = 0;
692
                item->updated = TRUE;
693
        }
694
695
        return val;
696
}
697
698
static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item,
699
                                  MsgInfo *msginfo)
700
{
701
        struct stat s;
702
703
        if (stat(itos(msginfo->msgnum), &s) < 0 ||
704
            msginfo->size  != s.st_size ||
705
            msginfo->mtime != s.st_mtime)
706
                return TRUE;
707
708
        return FALSE;
709
}
710
711
static gint mh_close(Folder *folder, FolderItem *item)
712
{
713
        return 0;
714
}
715
716
static gint mh_scan_folder_full(Folder *folder, FolderItem *item,
717
                                gboolean count_sum)
718
{
719
        gchar *path;
720
        DIR *dp;
721
        struct dirent *d;
722
        gint max = 0;
723
        gint num;
724
        gint n_msg = 0;
725
726
        g_return_val_if_fail(item != NULL, -1);
727
728
        debug_print("mh_scan_folder(): Scanning %s ...\n", item->path);
729
730
        path = folder_item_get_path(item);
731
        g_return_val_if_fail(path != NULL, -1);
732
        if (change_dir(path) < 0) {
733
                g_free(path);
734
                return -1;
735
        }
736
        g_free(path);
737
738
        if ((dp = opendir(".")) == NULL) {
739
                FILE_OP_ERROR(item->path, "opendir");
740
                return -1;
741
        }
742
743
        if (folder->ui_func)
744
                folder->ui_func(folder, item, folder->ui_func_data);
745
746
        while ((d = readdir(dp)) != NULL) {
747
                if ((num = to_number(d->d_name)) > 0 &&
748
                    dirent_is_regular_file(d)) {
749
                        n_msg++;
750
                        if (max < num)
751
                                max = num;
752
                }
753
        }
754
755
        closedir(dp);
756
757
        if (n_msg == 0)
758
                item->new = item->unread = item->total = 0;
759
        else if (count_sum) {
760
                gint new, unread, total, min, max_;
761
762
                procmsg_get_mark_sum
763
                        (item, &new, &unread, &total, &min, &max_, 0);
764
765
                if (n_msg > total) {
766
                        item->unmarked_num = new = n_msg - total;
767
                        unread += n_msg - total;
768
                } else
769
                        item->unmarked_num = 0;
770
771
                item->new = new;
772
                item->unread = unread;
773
                item->total = n_msg;
774
        }
775
776
        item->updated = TRUE;
777
778
        debug_print(_("Last number in dir %s = %d\n"), item->path, max);
779
        item->last_num = max;
780
781
        return 0;
782
}
783
784
static gint mh_scan_folder(Folder *folder, FolderItem *item)
785
{
786
        return mh_scan_folder_full(folder, item, TRUE);
787
}
788
789
static gint mh_scan_tree(Folder *folder)
790
{
791
        FolderItem *item;
792
        gchar *rootpath;
793
794
        g_return_val_if_fail(folder != NULL, -1);
795
796
        if (!folder->node) {
797
                item = folder_item_new(folder->name, NULL);
798
                item->folder = folder;
799
                folder->node = item->node = g_node_new(item);
800
        } else
801
                item = FOLDER_ITEM(folder->node->data);
802
803
        rootpath = folder_item_get_path(item);
804
        if (change_dir(rootpath) < 0) {
805
                g_free(rootpath);
806
                return -1;
807
        }
808
        g_free(rootpath);
809
810
        mh_create_tree(folder);
811
        mh_remove_missing_folder_items(folder);
812
        mh_scan_tree_recursive(item);
813
814
        return 0;
815
}
816
817
#define MAKE_DIR_IF_NOT_EXIST(dir) \
818
{ \
819
        if (!is_dir_exist(dir)) { \
820
                if (is_file_exist(dir)) { \
821
                        g_warning(_("File `%s' already exists.\n" \
822
                                    "Can't create folder."), dir); \
823
                        return -1; \
824
                } \
825
                if (make_dir(dir) < 0) \
826
                        return -1; \
827
        } \
828
}
829
830
static gint mh_create_tree(Folder *folder)
831
{
832
        gchar *rootpath;
833
834
        g_return_val_if_fail(folder != NULL, -1);
835
836
        CHDIR_RETURN_VAL_IF_FAIL(get_home_dir(), -1);
837
        rootpath = LOCAL_FOLDER(folder)->rootpath;
838
        MAKE_DIR_IF_NOT_EXIST(rootpath);
839
        CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1);
840
        MAKE_DIR_IF_NOT_EXIST(INBOX_DIR);
841
        MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR);
842
        MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR);
843
        MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR);
844
        MAKE_DIR_IF_NOT_EXIST(TRASH_DIR);
845
846
        return 0;
847
}
848
849
#undef MAKE_DIR_IF_NOT_EXIST
850
851
static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent,
852
                                    const gchar *name)
853
{
854
        gchar *path;
855
        gchar *fs_name;
856
        gchar *fullpath;
857
        FolderItem *new_item;
858
859
        g_return_val_if_fail(folder != NULL, NULL);
860
        g_return_val_if_fail(parent != NULL, NULL);
861
        g_return_val_if_fail(name != NULL, NULL);
862
863
        path = folder_item_get_path(parent);
864
        fs_name = g_filename_from_utf8(name, -1, NULL, NULL, NULL);
865
        fullpath = g_strconcat(path, G_DIR_SEPARATOR_S,
866
                               fs_name ? fs_name : name, NULL);
867
        g_free(fs_name);
868
        g_free(path);
869
870
        if (make_dir(fullpath) < 0) {
871
                g_free(fullpath);
872
                return NULL;
873
        }
874
875
        g_free(fullpath);
876
877
        if (parent->path)
878
                path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name,
879
                                   NULL);
880
        else
881
                path = g_strdup(name);
882
        new_item = folder_item_new(name, path);
883
        folder_item_append(parent, new_item);
884
        g_free(path);
885
886
        return new_item;
887
}
888
889
static gint mh_move_folder_real(Folder *folder, FolderItem *item,
890
                                FolderItem *new_parent, const gchar *name)
891
{
892
        gchar *oldpath;
893
        gchar *newpath;
894
        gchar *dirname;
895
        gchar *new_dir;
896
        gchar *name_;
897
        gchar *utf8_name;
898
        gchar *paths[2];
899
900
        g_return_val_if_fail(folder != NULL, -1);
901
        g_return_val_if_fail(item != NULL, -1);
902
        g_return_val_if_fail(folder == item->folder, -1);
903
        g_return_val_if_fail(item->path != NULL, -1);
904
        g_return_val_if_fail(new_parent != NULL || name != NULL, -1);
905
        if (new_parent) {
906
                g_return_val_if_fail(item != new_parent, -1);
907
                g_return_val_if_fail(item->parent != new_parent, -1);
908
                g_return_val_if_fail(item->folder == new_parent->folder, -1);
909
                if (g_node_is_ancestor(item->node, new_parent->node)) {
910
                        g_warning("folder to be moved is ancestor of new parent\n");
911
                        return -1;
912
                }
913
        }
914
915
        oldpath = folder_item_get_path(item);
916
        if (new_parent) {
917
                if (name) {
918
                        name_ = g_filename_from_utf8(name, -1, NULL, NULL,
919
                                                     NULL);
920
                        if (!name_)
921
                                name_ = g_strdup(name);
922
                        utf8_name = g_strdup(name);
923
                } else {
924
                        name_ = g_path_get_basename(oldpath);
925
                        utf8_name = g_filename_to_utf8(name_, -1, NULL, NULL,
926
                                                       NULL);
927
                        if (!utf8_name)
928
                                utf8_name = g_strdup(name_);
929
                }
930
                new_dir = folder_item_get_path(new_parent);
931
                newpath = g_strconcat(new_dir, G_DIR_SEPARATOR_S, name_, NULL);
932
                g_free(new_dir);
933
        } else {
934
                name_ = g_filename_from_utf8(name, -1, NULL, NULL, NULL);
935
                utf8_name = g_strdup(name);
936
                dirname = g_dirname(oldpath);
937
                newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S,
938
                                      name_ ? name_ : name, NULL);
939
                g_free(dirname);
940
        }
941
        g_free(name_);
942
943
        if (is_file_entry_exist(newpath)) {
944
                g_warning("%s already exists\n", newpath);
945
                g_free(oldpath);
946
                g_free(newpath);
947
                g_free(utf8_name);
948
                return -1;
949
        }
950
951
        debug_print("mh_move_folder: rename(%s, %s)\n", oldpath, newpath);
952
953
        if (rename(oldpath, newpath) < 0) {
954
                FILE_OP_ERROR(oldpath, "rename");
955
                g_free(oldpath);
956
                g_free(newpath);
957
                g_free(utf8_name);
958
                return -1;
959
        }
960
961
        g_free(oldpath);
962
        g_free(newpath);
963
964
        if (new_parent) {
965
                g_node_unlink(item->node);
966
                g_node_append(new_parent->node, item->node);
967
                item->parent = new_parent;
968
                if (new_parent->path != NULL) {
969
                        newpath = g_strconcat(new_parent->path,
970
                                              G_DIR_SEPARATOR_S, utf8_name,
971
                                              NULL);
972
                        g_free(utf8_name);
973
                } else
974
                        newpath = utf8_name;
975
        } else {
976
                if (strchr(item->path, G_DIR_SEPARATOR) != NULL) {
977
                        dirname = g_dirname(item->path);
978
                        newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S,
979
                                              utf8_name, NULL);
980
                        g_free(dirname);
981
                        g_free(utf8_name);
982
                } else
983
                        newpath = utf8_name;
984
        }
985
986
        if (name) {
987
                g_free(item->name);
988
                item->name = g_strdup(name);
989
        }
990
991
        paths[0] = g_strdup(item->path);
992
        paths[1] = newpath;
993
        g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
994
                        mh_rename_folder_func, paths);
995
996
        g_free(paths[0]);
997
        g_free(paths[1]);
998
999
        return 0;
1000
}
1001
1002
static gint mh_move_folder(Folder *folder, FolderItem *item,
1003
                           FolderItem *new_parent)
1004
{
1005
        return mh_move_folder_real(folder, item, new_parent, NULL);
1006
}
1007
1008
static gint mh_rename_folder(Folder *folder, FolderItem *item,
1009
                             const gchar *name)
1010
{
1011
        return mh_move_folder_real(folder, item, NULL, name);
1012
}
1013
1014
static gint mh_remove_folder(Folder *folder, FolderItem *item)
1015
{
1016
        gchar *path;
1017
1018
        g_return_val_if_fail(folder != NULL, -1);
1019
        g_return_val_if_fail(item != NULL, -1);
1020
        g_return_val_if_fail(item->path != NULL, -1);
1021
1022
        path = folder_item_get_path(item);
1023
        if (remove_dir_recursive(path) < 0) {
1024
                g_warning("can't remove directory `%s'\n", path);
1025
                g_free(path);
1026
                return -1;
1027
        }
1028
1029
        g_free(path);
1030
        folder_item_remove(item);
1031
        return 0;
1032
}
1033
1034
1035
static time_t mh_get_mtime(FolderItem *item)
1036
{
1037
        gchar *path;
1038
        struct stat s;
1039
1040
        path = folder_item_get_path(item);
1041
        if (stat(path, &s) < 0) {
1042
                FILE_OP_ERROR(path, "stat");
1043
                return -1;
1044
        } else {
1045
                return MAX(s.st_mtime, s.st_ctime);
1046
        }
1047
}
1048
1049
static GSList *mh_get_uncached_msgs(GHashTable *msg_table, FolderItem *item)
1050
{
1051
        gchar *path;
1052
        DIR *dp;
1053
        struct dirent *d;
1054
        GSList *newlist = NULL;
1055
        GSList *last = NULL;
1056
        MsgInfo *msginfo;
1057
        gint n_newmsg = 0;
1058
        gint num;
1059
1060
        g_return_val_if_fail(item != NULL, NULL);
1061
1062
        path = folder_item_get_path(item);
1063
        g_return_val_if_fail(path != NULL, NULL);
1064
        if (change_dir(path) < 0) {
1065
                g_free(path);
1066
                return NULL;
1067
        }
1068
        g_free(path);
1069
1070
        if ((dp = opendir(".")) == NULL) {
1071
                FILE_OP_ERROR(item->path, "opendir");
1072
                return NULL;
1073
        }
1074
1075
        debug_print("Searching uncached messages...\n");
1076
1077
        if (msg_table) {
1078
                while ((d = readdir(dp)) != NULL) {
1079
                        if ((num = to_number(d->d_name)) <= 0) continue;
1080
1081
                        msginfo = g_hash_table_lookup
1082
                                (msg_table, GUINT_TO_POINTER(num));
1083
1084
                        if (msginfo) {
1085
                                MSG_SET_TMP_FLAGS(msginfo->flags, MSG_CACHED);
1086
                        } else {
1087
                                /* not found in the cache (uncached message) */
1088
                                msginfo = mh_parse_msg(d->d_name, item);
1089
                                if (!msginfo) continue;
1090
1091
                                if (!newlist)
1092
                                        last = newlist =
1093
                                                g_slist_append(NULL, msginfo);
1094
                                else {
1095
                                        last = g_slist_append(last, msginfo);
1096
                                        last = last->next;
1097
                                }
1098
                                n_newmsg++;
1099
                        }
1100
                }
1101
        } else {
1102
                /* discard all previous cache */
1103
                while ((d = readdir(dp)) != NULL) {
1104
                        if (to_number(d->d_name) <= 0) continue;
1105
1106
                        msginfo = mh_parse_msg(d->d_name, item);
1107
                        if (!msginfo) continue;
1108
1109
                        if (!newlist)
1110
                                last = newlist = g_slist_append(NULL, msginfo);
1111
                        else {
1112
                                last = g_slist_append(last, msginfo);
1113
                                last = last->next;
1114
                        }
1115
                        n_newmsg++;
1116
                }
1117
        }
1118
1119
        closedir(dp);
1120
1121
        if (n_newmsg)
1122
                debug_print("%d uncached message(s) found.\n", n_newmsg);
1123
        else
1124
                debug_print("done.\n");
1125
1126
        /* sort new messages in numerical order */
1127
        if (newlist && item->sort_key == SORT_BY_NONE) {
1128
                debug_print("Sorting uncached messages in numerical order...\n");
1129
                newlist = g_slist_sort
1130
                        (newlist, (GCompareFunc)procmsg_cmp_msgnum_for_sort);
1131
                debug_print("done.\n");
1132
        }
1133
1134
        return newlist;
1135
}
1136
1137
static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item)
1138
{
1139
        MsgInfo *msginfo;
1140
        MsgFlags flags;
1141
1142
        g_return_val_if_fail(item != NULL, NULL);
1143
        g_return_val_if_fail(file != NULL, NULL);
1144
1145
        flags.perm_flags = MSG_NEW|MSG_UNREAD;
1146
        flags.tmp_flags = 0;
1147
1148
        if (item->stype == F_QUEUE) {
1149
                MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
1150
        } else if (item->stype == F_DRAFT) {
1151
                MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
1152
        }
1153
1154
        msginfo = procheader_parse_file(file, flags, FALSE);
1155
        if (!msginfo) return NULL;
1156
1157
        msginfo->msgnum = atoi(file);
1158
        msginfo->folder = item;
1159
1160
        return msginfo;
1161
}
1162
1163
#if 0
1164
static gboolean mh_is_maildir_one(const gchar *path, const gchar *dir)
1165
{
1166
        gchar *entry;
1167
        gboolean result;
1168
1169
        entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir, NULL);
1170
        result = is_dir_exist(entry);
1171
        g_free(entry);
1172
1173
        return result;
1174
}
1175
1176
/*
1177
 * check whether PATH is a Maildir style mailbox.
1178
 * This is the case if the 3 subdir: new, cur, tmp are existing.
1179
 * This functon assumes that entry is an directory
1180
 */
1181
static gboolean mh_is_maildir(const gchar *path)
1182
{
1183
        return mh_is_maildir_one(path, "new") &&
1184
               mh_is_maildir_one(path, "cur") &&
1185
               mh_is_maildir_one(path, "tmp");
1186
}
1187
#endif
1188
1189
static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data)
1190
{
1191
        FolderItem *item;
1192
        gchar *path;
1193
1194
        g_return_val_if_fail(node->data != NULL, FALSE);
1195
1196
        if (G_NODE_IS_ROOT(node))
1197
                return FALSE;
1198
1199
        item = FOLDER_ITEM(node->data);
1200
1201
        path = folder_item_get_path(item);
1202
        if (!is_dir_exist(path)) {
1203
                debug_print("folder '%s' not found. removing...\n", path);
1204
                folder_item_remove(item);
1205
        }
1206
        g_free(path);
1207
1208
        return FALSE;
1209
}
1210
1211
static void mh_remove_missing_folder_items(Folder *folder)
1212
{
1213
        g_return_if_fail(folder != NULL);
1214
1215
        debug_print("searching missing folders...\n");
1216
1217
        g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
1218
                        mh_remove_missing_folder_items_func, folder);
1219
}
1220
1221
static void mh_scan_tree_recursive(FolderItem *item)
1222
{
1223
        Folder *folder;
1224
        DIR *dp;
1225
        struct dirent *d;
1226
        struct stat s;
1227
        gchar *fs_path;
1228
        gchar *entry;
1229
        gchar *utf8entry;
1230
        gchar *utf8name;
1231
        gint n_msg = 0;
1232
1233
        g_return_if_fail(item != NULL);
1234
        g_return_if_fail(item->folder != NULL);
1235
1236
        folder = item->folder;
1237
1238
        fs_path = item->path ?
1239
                g_filename_from_utf8(item->path, -1, NULL, NULL, NULL)
1240
                : g_strdup(".");
1241
        if (!fs_path)
1242
                fs_path = g_strdup(item->path);
1243
        dp = opendir(fs_path);
1244
        if (!dp) {
1245
                FILE_OP_ERROR(fs_path, "opendir");
1246
                g_free(fs_path);
1247
                return;
1248
        }
1249
        g_free(fs_path);
1250
1251
        debug_print("scanning %s ...\n",
1252
                    item->path ? item->path
1253
                    : LOCAL_FOLDER(item->folder)->rootpath);
1254
        if (folder->ui_func)
1255
                folder->ui_func(folder, item, folder->ui_func_data);
1256
1257
        while ((d = readdir(dp)) != NULL) {
1258
                if (d->d_name[0] == '.') continue;
1259
1260
                utf8name = g_filename_to_utf8(d->d_name, -1, NULL, NULL, NULL);
1261
                if (!utf8name)
1262
                        utf8name = g_strdup(d->d_name);
1263
1264
                if (item->path)
1265
                        utf8entry = g_strconcat(item->path, G_DIR_SEPARATOR_S,
1266
                                                utf8name, NULL);
1267
                else
1268
                        utf8entry = g_strdup(utf8name);
1269
                entry = g_filename_from_utf8(utf8entry, -1, NULL, NULL, NULL);
1270
                if (!entry)
1271
                        entry = g_strdup(utf8entry);
1272
1273
                if (
1274
#ifdef HAVE_DIRENT_D_TYPE
1275
                        d->d_type == DT_DIR ||
1276
                        (d->d_type == DT_UNKNOWN &&
1277
#endif
1278
                        stat(entry, &s) == 0 && S_ISDIR(s.st_mode)
1279
#ifdef HAVE_DIRENT_D_TYPE
1280
                        )
1281
#endif
1282
                   ) {
1283
                        FolderItem *new_item = NULL;
1284
                        GNode *node;
1285
1286
#if 0
1287
                        if (mh_is_maildir(entry)) {
1288
                                g_free(entry);
1289
                                g_free(utf8entry);
1290
                                g_free(utf8name);
1291
                                continue;
1292
                        }
1293
#endif
1294
                        if (g_utf8_validate(utf8name, -1, NULL) == FALSE) {
1295
                                g_warning(_("Directory name\n"
1296
                                            "'%s' is not a valid UTF-8 string.\n"
1297
                                            "Maybe the locale encoding is used for filename.\n"
1298
                                            "If that is the case, you must set the following environmental variable\n"
1299
                                            "(see README for detail):\n"
1300
                                            "\n"
1301
                                            "\tG_FILENAME_ENCODING=@locale\n"),
1302
                                          utf8name);
1303
                                g_free(entry);
1304
                                g_free(utf8entry);
1305
                                g_free(utf8name);
1306
                                continue;
1307
                        }
1308
1309
                        node = item->node;
1310
                        for (node = node->children; node != NULL; node = node->next) {
1311
                                FolderItem *cur_item = FOLDER_ITEM(node->data);
1312
                                if (!strcmp2(cur_item->path, utf8entry)) {
1313
                                        new_item = cur_item;
1314
                                        break;
1315
                                }
1316
                        }
1317
                        if (!new_item) {
1318
                                debug_print("new folder '%s' found.\n", entry);
1319
                                new_item = folder_item_new(utf8name, utf8entry);
1320
                                folder_item_append(item, new_item);
1321
                        }
1322
1323
                        if (!item->path) {
1324
                                if (!folder->inbox &&
1325
                                    !strcmp(d->d_name, INBOX_DIR)) {
1326
                                        new_item->stype = F_INBOX;
1327
                                        folder->inbox = new_item;
1328
                                } else if (!folder->outbox &&
1329
                                           !strcmp(d->d_name, OUTBOX_DIR)) {
1330
                                        new_item->stype = F_OUTBOX;
1331
                                        folder->outbox = new_item;
1332
                                } else if (!folder->draft &&
1333
                                           !strcmp(d->d_name, DRAFT_DIR)) {
1334
                                        new_item->stype = F_DRAFT;
1335
                                        folder->draft = new_item;
1336
                                } else if (!folder->queue &&
1337
                                           !strcmp(d->d_name, QUEUE_DIR)) {
1338
                                        new_item->stype = F_QUEUE;
1339
                                        folder->queue = new_item;
1340
                                } else if (!folder->trash &&
1341
                                           !strcmp(d->d_name, TRASH_DIR)) {
1342
                                        new_item->stype = F_TRASH;
1343
                                        folder->trash = new_item;
1344
                                }
1345
                        }
1346
1347
                        mh_scan_tree_recursive(new_item);
1348
                } else if (to_number(d->d_name) > 0) n_msg++;
1349
1350
                g_free(entry);
1351
                g_free(utf8entry);
1352
                g_free(utf8name);
1353
        }
1354
1355
        closedir(dp);
1356
1357
        if (item->path) {
1358
                gint new, unread, total, min, max;
1359
1360
                procmsg_get_mark_sum
1361
                        (item, &new, &unread, &total, &min, &max, 0);
1362
                if (n_msg > total) {
1363
                        new += n_msg - total;
1364
                        unread += n_msg - total;
1365
                }
1366
                item->new = new;
1367
                item->unread = unread;
1368
                item->total = n_msg;
1369
                item->updated = TRUE;
1370
        }
1371
}
1372
1373
static gboolean mh_rename_folder_func(GNode *node, gpointer data)
1374
{
1375
        FolderItem *item = node->data;
1376
        gchar **paths = data;
1377
        const gchar *oldpath = paths[0];
1378
        const gchar *newpath = paths[1];
1379
        gchar *base;
1380
        gchar *new_itempath;
1381
        gint oldpathlen;
1382
1383
        oldpathlen = strlen(oldpath);
1384
        if (strncmp(oldpath, item->path, oldpathlen) != 0) {
1385
                g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
1386
                return TRUE;
1387
        }
1388
1389
        base = item->path + oldpathlen;
1390
        while (*base == G_DIR_SEPARATOR) base++;
1391
        if (*base == '\0')
1392
                new_itempath = g_strdup(newpath);
1393
        else
1394
                new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
1395
                                           NULL);
1396
        g_free(item->path);
1397
        item->path = new_itempath;
1398
1399
        return FALSE;
1400
}