Statistics
| Revision:

root / src / mh.c @ 461

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