45cad370b832caacb63d070ec54d2e1c52db90e9
[claws.git] / src / mh.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 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 <dirent.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
32
33 #undef MEASURE_TIME
34
35 #ifdef MEASURE_TIME
36 #  include <sys/time.h>
37 #endif
38
39 #include "intl.h"
40 #include "folder.h"
41 #include "mh.h"
42 #include "procmsg.h"
43 #include "procheader.h"
44 #include "utils.h"
45
46 static GSList  *mh_get_uncached_msgs            (GHashTable     *msg_table,
47                                                  FolderItem     *item);
48 static MsgInfo *mh_parse_msg                    (const gchar    *file,
49                                                  FolderItem     *item);
50 static void     mh_scan_tree_recursive          (FolderItem     *item);
51
52 static gboolean mh_rename_folder_func           (GNode          *node,
53                                                  gpointer        data);
54
55
56 GSList *mh_get_msg_list(Folder *folder, FolderItem *item, gboolean use_cache)
57 {
58         GSList *mlist;
59         GHashTable *msg_table;
60         gchar *path;
61         struct stat s;
62         gboolean scan_new = TRUE;
63 #ifdef MEASURE_TIME
64         struct timeval tv_before, tv_after, tv_result;
65
66         gettimeofday(&tv_before, NULL);
67 #endif
68
69         g_return_val_if_fail(item != NULL, NULL);
70
71         path = folder_item_get_path(item);
72         if (stat(path, &s) < 0) {
73                 FILE_OP_ERROR(path, "stat");
74         } else {
75                 if (item->mtime == s.st_mtime) {
76                         debug_print("Folder is not modified.\n");
77                         scan_new = FALSE;
78                 } else
79                         item->mtime = s.st_mtime;
80         }
81         g_free(path);
82
83         if (use_cache && !scan_new) {
84                 mlist = procmsg_read_cache(item, FALSE);
85                 if (!mlist)
86                         mlist = mh_get_uncached_msgs(NULL, item);
87         } else if (use_cache) {
88                 GSList *newlist;
89
90                 mlist = procmsg_read_cache(item, TRUE);
91                 msg_table = procmsg_msg_hash_table_create(mlist);
92
93                 newlist = mh_get_uncached_msgs(msg_table, item);
94                 if (msg_table)
95                         g_hash_table_destroy(msg_table);
96
97                 mlist = g_slist_concat(mlist, newlist);
98         } else
99                 mlist = mh_get_uncached_msgs(NULL, item);
100
101         procmsg_set_flags(mlist, item);
102
103 #ifdef MEASURE_TIME
104         gettimeofday(&tv_after, NULL);
105
106         timersub(&tv_after, &tv_before, &tv_result);
107         g_print("mh_get_msg_list: %s: elapsed time: %ld.%06ld sec\n",
108                 item->path, tv_result.tv_sec, tv_result.tv_usec);
109 #endif
110
111         return mlist;
112 }
113
114 gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num)
115 {
116         gchar *path;
117         gchar *file;
118
119         g_return_val_if_fail(item != NULL, NULL);
120         g_return_val_if_fail(num > 0 && num <= item->last_num, NULL);
121
122         path = folder_item_get_path(item);
123         file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
124         g_free(path);
125         if (!is_file_exist(file)) {
126                 g_free(file);
127                 return NULL;
128         }
129
130         return file;
131 }
132
133 gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
134                 gboolean remove_source)
135 {
136         gchar *destpath;
137         gchar *destfile;
138
139         g_return_val_if_fail(dest != NULL, -1);
140         g_return_val_if_fail(file != NULL, -1);
141
142         if (dest->last_num < 0) {
143                 mh_scan_folder(folder, dest);
144                 if (dest->last_num < 0) return -1;
145         }
146
147         destpath = folder_item_get_path(dest);
148         g_return_val_if_fail(destpath != NULL, -1);
149         destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
150                                    dest->last_num + 1);
151
152         if (link(file, destfile) < 0) {
153                 if (EXDEV == errno) {
154                         if (copy_file(file, destfile) < 0) {
155                                 g_warning(_("can't copy message %s to %s\n"),
156                                           file, destfile);
157                                 g_free(destfile);
158                                 return -1;
159                         }
160                 } else {
161                         FILE_OP_ERROR(file, "link");
162                         g_free(destfile);
163                         return -1;
164                 }
165         }
166
167         if (remove_source) {
168                 if (unlink(file) < 0)
169                         FILE_OP_ERROR(file, "unlink");
170         }
171
172         g_free(destfile);
173         dest->last_num++;
174         return dest->last_num;
175 }
176
177 gint mh_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
178 {
179         gchar *destdir;
180         gchar *srcfile;
181         gchar *destfile;
182         FILE *fp;
183
184         g_return_val_if_fail(dest != NULL, -1);
185         g_return_val_if_fail(msginfo != NULL, -1);
186
187         if (msginfo->folder == dest) {
188                 g_warning(_("the src folder is identical to the dest.\n"));
189                 return -1;
190         }
191
192         if (dest->last_num < 0) {
193                 mh_scan_folder(folder, dest);
194                 if (dest->last_num < 0) return -1;
195         }
196
197         destdir = folder_item_get_path(dest);
198         if ((fp = procmsg_open_mark_file(destdir, TRUE)) == NULL)
199                 g_warning(_("Can't open mark file.\n"));
200
201         debug_print(_("Moving message %s%c%d to %s ...\n"),
202                     msginfo->folder->path, G_DIR_SEPARATOR,
203                     msginfo->msgnum, dest->path);
204         srcfile = procmsg_get_message_file_path(msginfo);
205         destfile = g_strdup_printf("%s%c%d", destdir, G_DIR_SEPARATOR,
206                                    dest->last_num + 1);
207         g_free(destdir);
208
209         if (is_file_exist(destfile)) {
210                 g_warning(_("%s already exists."), destfile);
211                 g_free(srcfile);
212                 g_free(destfile);
213                 if (fp) fclose(fp);
214                 return -1;
215         }
216
217         if (rename(srcfile, destfile) < 0) {
218                 if (EXDEV == errno) {
219                         if (copy_file(srcfile, destfile) < 0) {
220                                 g_free(srcfile);
221                                 g_free(destfile);
222                                 return -1;
223                         }
224                         unlink(srcfile);
225                 } else {
226                         FILE_OP_ERROR(srcfile, "rename");
227                         g_free(srcfile);
228                         g_free(destfile);
229                         if (fp) fclose(fp);
230                         return -1;
231                 }
232         }
233
234         g_free(srcfile);
235         g_free(destfile);
236         dest->last_num++;
237
238         if (fp) {
239                 MsgInfo newmsginfo;
240
241                 newmsginfo.msgnum = dest->last_num;
242                 newmsginfo.flags = msginfo->flags;
243                 if (dest->stype == F_OUTBOX ||
244                     dest->stype == F_QUEUE  ||
245                     dest->stype == F_DRAFT  ||
246                     dest->stype == F_TRASH)
247                         MSG_UNSET_FLAGS(newmsginfo.flags,
248                                         MSG_NEW|MSG_UNREAD|MSG_DELETED);
249
250                 procmsg_write_flags(&newmsginfo, fp);
251                 fclose(fp);
252         }
253
254         return dest->last_num;
255 }
256
257 gint mh_move_msgs_with_dest(Folder *folder, FolderItem *dest, GSList *msglist)
258 {
259         gchar *destdir;
260         gchar *srcfile;
261         gchar *destfile;
262         FILE *fp;
263         GSList *cur;
264         MsgInfo *msginfo;
265
266         g_return_val_if_fail(dest != NULL, -1);
267         g_return_val_if_fail(msglist != NULL, -1);
268
269         if (dest->last_num < 0) {
270                 mh_scan_folder(folder, dest);
271                 if (dest->last_num < 0) return -1;
272         }
273
274         destdir = folder_item_get_path(dest);
275         if ((fp = procmsg_open_mark_file(destdir, TRUE)) == NULL)
276                 g_warning(_("Can't open mark file.\n"));
277
278         for (cur = msglist; cur != NULL; cur = cur->next) {
279                 msginfo = (MsgInfo *)cur->data;
280
281                 if (msginfo->folder == dest) {
282                         g_warning(_("the src folder is identical to the dest.\n"));
283                         continue;
284                 }
285                 debug_print(_("Moving message %s%c%d to %s ...\n"),
286                             msginfo->folder->path, G_DIR_SEPARATOR,
287                             msginfo->msgnum, dest->path);
288
289                 srcfile = procmsg_get_message_file_path(msginfo);
290                 destfile = g_strdup_printf("%s%c%d", destdir, G_DIR_SEPARATOR,
291                                            dest->last_num + 1);
292
293                 if (is_file_exist(destfile)) {
294                         g_warning(_("%s already exists."), destfile);
295                         g_free(srcfile);
296                         g_free(destfile);
297                         break;
298                 }
299
300                 if (rename(srcfile, destfile) < 0) {
301                         if (EXDEV == errno) {
302                                 if (copy_file(srcfile, destfile) < 0) {
303                                         g_free(srcfile);
304                                         g_free(destfile);
305                                         break;
306                                 }
307                                 unlink(srcfile);
308                         } else {
309                                 FILE_OP_ERROR(srcfile, "rename");
310                                 g_free(srcfile);
311                                 g_free(destfile);
312                                 break;
313                         }
314                 }
315
316                 g_free(srcfile);
317                 g_free(destfile);
318                 dest->last_num++;
319
320                 if (fp) {
321                         MsgInfo newmsginfo;
322
323                         newmsginfo.msgnum = dest->last_num;
324                         newmsginfo.flags = msginfo->flags;
325                         if (dest->stype == F_OUTBOX ||
326                             dest->stype == F_QUEUE  ||
327                             dest->stype == F_DRAFT  ||
328                             dest->stype == F_TRASH)
329                                 MSG_UNSET_FLAGS(newmsginfo.flags,
330                                                 MSG_NEW|MSG_UNREAD|MSG_DELETED);
331
332                         procmsg_write_flags(&newmsginfo, fp);
333                 }
334         }
335
336         g_free(destdir);
337         if (fp) fclose(fp);
338
339         return dest->last_num;
340 }
341
342 gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
343 {
344         gchar *destdir;
345         gchar *srcfile;
346         gchar *destfile;
347         FILE *fp;
348
349         g_return_val_if_fail(dest != NULL, -1);
350         g_return_val_if_fail(msginfo != NULL, -1);
351
352         if (msginfo->folder == dest) {
353                 g_warning(_("the src folder is identical to the dest.\n"));
354                 return -1;
355         }
356
357         if (dest->last_num < 0) {
358                 mh_scan_folder(folder, dest);
359                 if (dest->last_num < 0) return -1;
360         }
361
362         destdir = folder_item_get_path(dest);
363         if ((fp = procmsg_open_mark_file(destdir, TRUE)) == NULL)
364                 g_warning(_("Can't open mark file.\n"));
365
366         debug_print(_("Copying message %s%c%d to %s ...\n"),
367                     msginfo->folder->path, G_DIR_SEPARATOR,
368                     msginfo->msgnum, dest->path);
369         srcfile = procmsg_get_message_file_path(msginfo);
370         destfile = g_strdup_printf("%s%c%d", destdir, G_DIR_SEPARATOR,
371                                    dest->last_num + 1);
372         g_free(destdir);
373
374         if (is_file_exist(destfile)) {
375                 g_warning(_("%s already exists."), destfile);
376                 g_free(srcfile);
377                 g_free(destfile);
378                 if (fp) fclose(fp);
379                 return -1;
380         }
381
382         if (copy_file(srcfile, destfile) < 0) {
383                 FILE_OP_ERROR(srcfile, "copy");
384                 g_free(srcfile);
385                 g_free(destfile);
386                 if (fp) fclose(fp);
387                 return -1;
388         }
389
390         g_free(srcfile);
391         g_free(destfile);
392         dest->last_num++;
393
394         if (fp) {
395                 MsgInfo newmsginfo;
396
397                 newmsginfo.msgnum = dest->last_num;
398                 newmsginfo.flags = msginfo->flags;
399                 if (dest->stype == F_OUTBOX ||
400                     dest->stype == F_QUEUE  ||
401                     dest->stype == F_DRAFT  ||
402                     dest->stype == F_TRASH)
403                         MSG_UNSET_FLAGS(newmsginfo.flags,
404                                         MSG_NEW|MSG_UNREAD|MSG_DELETED);
405                 procmsg_write_flags(&newmsginfo, fp);
406                 fclose(fp);
407         }
408
409         return dest->last_num;
410 }
411
412 gint mh_copy_msgs_with_dest(Folder *folder, FolderItem *dest, GSList *msglist)
413 {
414         gchar *destdir;
415         gchar *srcfile;
416         gchar *destfile;
417         FILE *fp;
418         GSList *cur;
419         MsgInfo *msginfo;
420
421         g_return_val_if_fail(dest != NULL, -1);
422         g_return_val_if_fail(msglist != NULL, -1);
423
424         if (dest->last_num < 0) {
425                 mh_scan_folder(folder, dest);
426                 if (dest->last_num < 0) return -1;
427         }
428
429         destdir = folder_item_get_path(dest);
430         if ((fp = procmsg_open_mark_file(destdir, TRUE)) == NULL)
431                 g_warning(_("Can't open mark file.\n"));
432
433         for (cur = msglist; cur != NULL; cur = cur->next) {
434                 msginfo = (MsgInfo *)cur->data;
435
436                 if (msginfo->folder == dest) {
437                         g_warning(_("the src folder is identical to the dest.\n"));
438                         continue;
439                 }
440                 debug_print(_("Copying message %s%c%d to %s ...\n"),
441                             msginfo->folder->path, G_DIR_SEPARATOR,
442                             msginfo->msgnum, dest->path);
443
444                 srcfile = procmsg_get_message_file_path(msginfo);
445                 destfile = g_strdup_printf("%s%c%d", destdir, G_DIR_SEPARATOR,
446                                            dest->last_num + 1);
447
448                 if (is_file_exist(destfile)) {
449                         g_warning(_("%s already exists."), destfile);
450                         g_free(srcfile);
451                         g_free(destfile);
452                         break;
453                 }
454
455                 if (copy_file(srcfile, destfile) < 0) {
456                         FILE_OP_ERROR(srcfile, "copy");
457                         g_free(srcfile);
458                         g_free(destfile);
459                         break;
460                 }
461
462                 g_free(srcfile);
463                 g_free(destfile);
464                 dest->last_num++;
465
466                 if (fp) {
467                         MsgInfo newmsginfo;
468
469                         newmsginfo.msgnum = dest->last_num;
470                         newmsginfo.flags = msginfo->flags;
471                         if (dest->stype == F_OUTBOX ||
472                             dest->stype == F_QUEUE  ||
473                             dest->stype == F_DRAFT  ||
474                             dest->stype == F_TRASH)
475                                 MSG_UNSET_FLAGS(newmsginfo.flags,
476                                                 MSG_NEW|MSG_UNREAD|MSG_DELETED);
477                         procmsg_write_flags(&newmsginfo, fp);
478                 }
479         }
480
481         g_free(destdir);
482         if (fp) fclose(fp);
483
484         return dest->last_num;
485 }
486
487 gint mh_remove_msg(Folder *folder, FolderItem *item, gint num)
488 {
489         gchar *file;
490
491         g_return_val_if_fail(item != NULL, -1);
492
493         file = mh_fetch_msg(folder, item, num);
494         g_return_val_if_fail(file != NULL, -1);
495
496         if (unlink(file) < 0) {
497                 FILE_OP_ERROR(file, "unlink");
498                 g_free(file);
499                 return -1;
500         }
501
502         g_free(file);
503         return 0;
504 }
505
506 gint mh_remove_all_msg(Folder *folder, FolderItem *item)
507 {
508         gchar *path;
509         DIR *dp;
510         struct dirent *d;
511
512         g_return_val_if_fail(item != NULL, -1);
513
514         path = folder_item_get_path(item);
515         g_return_val_if_fail(path != NULL, -1);
516         if (change_dir(path) < 0) {
517                 g_free(path);
518                 return -1;
519         }
520         g_free(path);
521
522         if ((dp = opendir(".")) == NULL) {
523                 FILE_OP_ERROR(item->path, "opendir");
524                 return -1;
525         }
526
527         while ((d = readdir(dp)) != NULL) {
528                 if (to_number(d->d_name) < 0) continue;
529                 if (unlink(d->d_name) < 0)
530                         FILE_OP_ERROR(d->d_name, "unlink");
531         }
532
533         closedir(dp);
534         return 0;
535 }
536
537 gboolean mh_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
538 {
539         struct stat s;
540
541         if (stat(itos(msginfo->msgnum), &s) < 0 ||
542             msginfo->size  != s.st_size ||
543             msginfo->mtime != s.st_mtime)
544                 return TRUE;
545
546         return FALSE;
547 }
548
549 void mh_scan_folder(Folder *folder, FolderItem *item)
550 {
551         gchar *path;
552         DIR *dp;
553         struct dirent *d;
554         struct stat s;
555         gint max = 0;
556         gint num;
557         gint n_msg = 0;
558
559         g_return_if_fail(item != NULL);
560
561         path = folder_item_get_path(item);
562         g_return_if_fail(path != NULL);
563         if (change_dir(path) < 0) {
564                 g_free(path);
565                 return;
566         }
567         g_free(path);
568
569         if ((dp = opendir(".")) == NULL) {
570                 FILE_OP_ERROR(item->path, "opendir");
571                 return;
572         }
573
574         if (folder->ui_func)
575                 folder->ui_func(folder, item, folder->ui_func_data);
576
577         while ((d = readdir(dp)) != NULL) {
578                 if ((num = to_number(d->d_name)) >= 0 &&
579                     stat(d->d_name, &s) == 0 &&
580                     S_ISREG(s.st_mode)) {
581                         n_msg++;
582                         if (max < num)
583                                 max = num;
584                 }
585         }
586
587         closedir(dp);
588
589         if (n_msg == 0)
590                 item->new = item->unread = item->total = 0;
591         else {
592                 gint new, unread, total;
593
594                 procmsg_get_mark_sum(".", &new, &unread, &total);
595                 if (n_msg > total) {
596                         new += n_msg - total;
597                         unread += n_msg - total;
598                 }
599                 item->new = new;
600                 item->unread = unread;
601                 item->total = n_msg;
602         }
603
604         debug_print(_("Last number in dir %s = %d\n"), item->path, max);
605         item->last_num = max;
606 }
607
608 void mh_scan_tree(Folder *folder)
609 {
610         FolderItem *item;
611         gchar *rootpath;
612
613         g_return_if_fail(folder != NULL);
614
615         folder_tree_destroy(folder);
616         item = folder_item_new(folder->name, NULL);
617         item->folder = folder;
618         folder->node = g_node_new(item);
619
620         rootpath = folder_item_get_path(item);
621         if (change_dir(rootpath) < 0) {
622                 g_free(rootpath);
623                 return;
624         }
625         g_free(rootpath);
626
627         mh_scan_tree_recursive(item);
628 }
629
630 #define MAKE_DIR_IF_NOT_EXIST(dir) \
631 { \
632         if (!is_dir_exist(dir)) { \
633                 if (is_file_exist(dir)) { \
634                         g_warning(_("File `%s' already exists.\n" \
635                                     "Can't create folder."), dir); \
636                         return -1; \
637                 } \
638                 if (mkdir(dir, S_IRWXU) < 0) { \
639                         FILE_OP_ERROR(dir, "mkdir"); \
640                         return -1; \
641                 } \
642                 if (chmod(dir, S_IRWXU) < 0) \
643                         FILE_OP_ERROR(dir, "chmod"); \
644         } \
645 }
646
647 gint mh_create_tree(Folder *folder)
648 {
649         gchar *rootpath;
650
651         g_return_val_if_fail(folder != NULL, -1);
652
653         CHDIR_RETURN_VAL_IF_FAIL(get_home_dir(), -1);
654         rootpath = LOCAL_FOLDER(folder)->rootpath;
655         MAKE_DIR_IF_NOT_EXIST(rootpath);
656         CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1);
657         MAKE_DIR_IF_NOT_EXIST(INBOX_DIR);
658         MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR);
659         MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR);
660         MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR);
661         MAKE_DIR_IF_NOT_EXIST(TRASH_DIR);
662
663         return 0;
664 }
665
666 #undef MAKE_DIR_IF_NOT_EXIST
667
668 FolderItem *mh_create_folder(Folder *folder, FolderItem *parent,
669                              const gchar *name)
670 {
671         gchar *path;
672         gchar *fullpath;
673         FolderItem *new_item;
674
675         g_return_val_if_fail(folder != NULL, NULL);
676         g_return_val_if_fail(parent != NULL, NULL);
677         g_return_val_if_fail(name != NULL, NULL);
678
679         path = folder_item_get_path(parent);
680         fullpath = g_strconcat(path, G_DIR_SEPARATOR_S, name, NULL);
681         g_free(path);
682
683         if (mkdir(fullpath, S_IRWXU) < 0) {
684                 FILE_OP_ERROR(fullpath, "mkdir");
685                 g_free(fullpath);
686                 return NULL;
687         }
688         if (chmod(fullpath, S_IRWXU) < 0)
689                 FILE_OP_ERROR(fullpath, "chmod");
690
691         g_free(fullpath);
692
693         if (parent->path)
694                 path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name,
695                                    NULL);
696         else
697                 path = g_strdup(name);
698         new_item = folder_item_new(name, path);
699         folder_item_append(parent, new_item);
700         g_free(path);
701
702         return new_item;
703 }
704
705 gint mh_rename_folder(Folder *folder, FolderItem *item, const gchar *name)
706 {
707         gchar *oldpath;
708         gchar *dirname;
709         gchar *newpath;
710         GNode *node;
711         gchar *paths[2];
712
713         g_return_val_if_fail(folder != NULL, -1);
714         g_return_val_if_fail(item != NULL, -1);
715         g_return_val_if_fail(item->path != NULL, -1);
716         g_return_val_if_fail(name != NULL, -1);
717
718         oldpath = folder_item_get_path(item);
719         dirname = g_dirname(oldpath);
720         newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, name, NULL);
721         g_free(dirname);
722
723         if (rename(oldpath, newpath) < 0) {
724                 FILE_OP_ERROR(oldpath, "rename");
725                 g_free(oldpath);
726                 g_free(newpath);
727                 return -1;
728         }
729
730         g_free(oldpath);
731         g_free(newpath);
732
733         if (strchr(item->path, G_DIR_SEPARATOR) != NULL) {
734                 dirname = g_dirname(item->path);
735                 newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, name, NULL);
736                 g_free(dirname);
737         } else
738                 newpath = g_strdup(name);
739
740         g_free(item->name);
741         item->name = g_strdup(name);
742
743         node = g_node_find(item->folder->node, G_PRE_ORDER, G_TRAVERSE_ALL,
744                            item);
745         paths[0] = g_strdup(item->path);
746         paths[1] = newpath;
747         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
748                         mh_rename_folder_func, paths);
749
750         g_free(paths[0]);
751         g_free(paths[1]);
752         return 0;
753 }
754
755 gint mh_remove_folder(Folder *folder, FolderItem *item)
756 {
757         gchar *path;
758
759         g_return_val_if_fail(folder != NULL, -1);
760         g_return_val_if_fail(item != NULL, -1);
761         g_return_val_if_fail(item->path != NULL, -1);
762
763         path = folder_item_get_path(item);
764         if (remove_dir_recursive(path) < 0) {
765                 g_warning("can't remove directory `%s'\n", path);
766                 g_free(path);
767                 return -1;
768         }
769
770         g_free(path);
771         folder_item_remove(item);
772         return 0;
773 }
774
775
776 static GSList *mh_get_uncached_msgs(GHashTable *msg_table, FolderItem *item)
777 {
778         gchar *path;
779         DIR *dp;
780         struct dirent *d;
781         struct stat s;
782         GSList *newlist = NULL;
783         GSList *last = NULL;
784         MsgInfo *msginfo;
785         gint n_newmsg = 0;
786         gint num;
787
788         g_return_val_if_fail(item != NULL, NULL);
789
790         path = folder_item_get_path(item);
791         g_return_val_if_fail(path != NULL, NULL);
792         if (change_dir(path) < 0) {
793                 g_free(path);
794                 return NULL;
795         }
796         g_free(path);
797
798         if ((dp = opendir(".")) == NULL) {
799                 FILE_OP_ERROR(item->path, "opendir");
800                 return NULL;
801         }
802
803         debug_print(_("\tSearching uncached messages... "));
804
805         if (msg_table) {
806                 while ((d = readdir(dp)) != NULL) {
807                         if ((num = to_number(d->d_name)) < 0) continue;
808                         if (stat(d->d_name, &s) < 0) {
809                                 FILE_OP_ERROR(d->d_name, "stat");
810                                 continue;
811                         }
812                         if (!S_ISREG(s.st_mode)) continue;
813
814                         msginfo = g_hash_table_lookup
815                                 (msg_table, GUINT_TO_POINTER(num));
816
817                         if (!msginfo) {
818                                 /* not found in the cache (uncached message) */
819                                 msginfo = mh_parse_msg(d->d_name, item);
820                                 if (!msginfo) continue;
821
822                                 if (!newlist)
823                                         last = newlist =
824                                                 g_slist_append(NULL, msginfo);
825                                 else {
826                                         last = g_slist_append(last, msginfo);
827                                         last = last->next;
828                                 }
829                                 n_newmsg++;
830                         }
831                 }
832         } else {
833                 /* discard all previous cache */
834                 while ((d = readdir(dp)) != NULL) {
835                         if (to_number(d->d_name) < 0) continue;
836                         if (stat(d->d_name, &s) < 0) {
837                                 FILE_OP_ERROR(d->d_name, "stat");
838                                 continue;
839                         }
840                         if (!S_ISREG(s.st_mode)) continue;
841
842                         msginfo = mh_parse_msg(d->d_name, item);
843                         if (!msginfo) continue;
844
845                         if (!newlist)
846                                 last = newlist = g_slist_append(NULL, msginfo);
847                         else {
848                                 last = g_slist_append(last, msginfo);
849                                 last = last->next;
850                         }
851                         n_newmsg++;
852                 }
853         }
854
855         closedir(dp);
856
857         if (n_newmsg)
858                 debug_print(_("%d uncached message(s) found.\n"), n_newmsg);
859         else
860                 debug_print(_("done.\n"));
861
862         /* sort new messages in numerical order */
863         if (newlist) {
864                 debug_print(_("\tSorting uncached messages in numerical order... "));
865                 newlist = g_slist_sort
866                         (newlist, (GCompareFunc)procmsg_cmp_msgnum_for_sort);
867                 debug_print(_("done.\n"));
868         }
869
870         return newlist;
871 }
872
873 static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item)
874 {
875         struct stat s;
876         MsgInfo *msginfo;
877         MsgFlags flags = MSG_NEW|MSG_UNREAD;
878
879         g_return_val_if_fail(item != NULL, NULL);
880         g_return_val_if_fail(file != NULL, NULL);
881
882         if (item->stype == F_QUEUE) {
883                 MSG_SET_FLAGS(flags, MSG_QUEUED);
884         } else if (item->stype == F_DRAFT) {
885                 MSG_SET_FLAGS(flags, MSG_DRAFT);
886         }
887
888         msginfo = procheader_parse(file, flags, FALSE);
889         if (!msginfo) return NULL;
890
891         msginfo->msgnum = atoi(file);
892         msginfo->folder = item;
893
894         if (stat(file, &s) < 0) {
895                 FILE_OP_ERROR(file, "stat");
896                 msginfo->size = 0;
897                 msginfo->mtime = 0;
898         } else {
899                 msginfo->size = s.st_size;
900                 msginfo->mtime = s.st_mtime;
901         }
902
903         return msginfo;
904 }
905
906 static gboolean mh_is_maildir_one(const gchar *path, const gchar *dir)
907 {
908         gchar *entry;
909         gboolean result;
910
911         entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir, NULL);
912         result = is_dir_exist(entry);
913         g_free(entry);
914
915         return result;
916 }
917
918 /*
919  * check whether PATH is a Maildir style mailbox.
920  * This is the case if the 3 subdir: new, cur, tmp are existing.
921  * This functon assumes that entry is an directory
922  */
923 static gboolean mh_is_maildir(const gchar *path)
924 {
925         return mh_is_maildir_one(path, "new") &&
926                mh_is_maildir_one(path, "cur") &&
927                mh_is_maildir_one(path, "tmp");
928 }
929
930 static void mh_scan_tree_recursive(FolderItem *item)
931 {
932         DIR *dp;
933         struct dirent *d;
934         struct stat s;
935         gchar *entry;
936         gint n_msg = 0;
937
938         g_return_if_fail(item != NULL);
939         g_return_if_fail(item->folder != NULL);
940
941         dp = opendir(item->path ? item->path : ".");
942         if (!dp) {
943                 FILE_OP_ERROR(item->path ? item->path : ".", "opendir");
944                 return;
945         }
946
947         debug_print("scanning %s ...\n",
948                     item->path ? item->path
949                     : LOCAL_FOLDER(item->folder)->rootpath);
950         if (item->folder->ui_func)
951                 item->folder->ui_func(item->folder, item,
952                                       item->folder->ui_func_data);
953
954         while ((d = readdir(dp)) != NULL) {
955                 if (d->d_name[0] == '.') continue;
956
957                 if (item->path)
958                         entry = g_strconcat(item->path, G_DIR_SEPARATOR_S,
959                                             d->d_name, NULL);
960                 else
961                         entry = g_strdup(d->d_name);
962
963                 if (stat(entry, &s) < 0) {
964                         FILE_OP_ERROR(entry, "stat");
965                         g_free(entry);
966                         continue;
967                 }
968
969                 if (S_ISDIR(s.st_mode)) {
970                         FolderItem *new_item;
971
972                         if (mh_is_maildir(entry)) {
973                                 g_free(entry);
974                                 continue;
975                         }
976
977                         new_item = folder_item_new(d->d_name, entry);
978                         folder_item_append(item, new_item);
979                         if (!item->path) {
980                                 if (!strcmp(d->d_name, "inbox")) {
981                                         new_item->stype = F_INBOX;
982                                         item->folder->inbox = new_item;
983                                 } else if (!strcmp(d->d_name, "outbox")) {
984                                         new_item->stype = F_OUTBOX;
985                                         item->folder->outbox = new_item;
986                                 } else if (!strcmp(d->d_name, "draft")) {
987                                         new_item->stype = F_DRAFT;
988                                         item->folder->draft = new_item;
989                                 } else if (!strcmp(d->d_name, "queue")) {
990                                         new_item->stype = F_QUEUE;
991                                         item->folder->queue = new_item;
992                                 } else if (!strcmp(d->d_name, "trash")) {
993                                         new_item->stype = F_TRASH;
994                                         item->folder->trash = new_item;
995                                 }
996                         }
997                         mh_scan_tree_recursive(new_item);
998                 } else if (to_number(d->d_name) != -1) n_msg++;
999
1000                 g_free(entry);
1001         }
1002
1003         closedir(dp);
1004
1005         if (item->path) {
1006                 gint new, unread, total;
1007
1008                 procmsg_get_mark_sum(item->path, &new, &unread, &total);
1009                 if (n_msg > total) {
1010                         new += n_msg - total;
1011                         unread += n_msg - total;
1012                 }
1013                 item->new = new;
1014                 item->unread = unread;
1015                 item->total = n_msg;
1016         }
1017 }
1018
1019 static gboolean mh_rename_folder_func(GNode *node, gpointer data)
1020 {
1021         FolderItem *item = node->data;
1022         gchar **paths = data;
1023         const gchar *oldpath = paths[0];
1024         const gchar *newpath = paths[1];
1025         gchar *base;
1026         gchar *new_itempath;
1027         gint oldpathlen;
1028
1029         oldpathlen = strlen(oldpath);
1030         if (strncmp(oldpath, item->path, oldpathlen) != 0) {
1031                 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
1032                 return TRUE;
1033         }
1034
1035         base = item->path + oldpathlen;
1036         while (*base == G_DIR_SEPARATOR) base++;
1037         if (*base == '\0')
1038                 new_itempath = g_strdup(newpath);
1039         else
1040                 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
1041                                            NULL);
1042         g_free(item->path);
1043         item->path = new_itempath;
1044
1045         return FALSE;
1046 }