0.9.7claws10
[claws.git] / src / mh.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 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 void     mh_folder_init          (Folder         *folder,
47                                          const gchar    *name,
48                                          const gchar    *path);
49
50 static Folder   *mh_folder_new          (const gchar    *name,
51                                          const gchar    *path);
52 static void     mh_folder_destroy       (Folder         *folder);
53 static gchar   *mh_fetch_msg            (Folder         *folder,
54                                          FolderItem     *item,
55                                          gint            num);
56 static MsgInfo *mh_get_msginfo          (Folder         *folder,
57                                          FolderItem     *item,
58                                          gint            num);
59 static gint     mh_add_msg              (Folder         *folder,
60                                          FolderItem     *dest,
61                                          const gchar    *file,
62                                          MsgFlags       *flags);
63 static gint     mh_add_msgs             (Folder         *folder,
64                                          FolderItem     *dest,
65                                          GSList         *file_list,
66                                          GRelation      *relation);
67 static gint     mh_copy_msg             (Folder         *folder,
68                                          FolderItem     *dest,
69                                          MsgInfo        *msginfo);
70 static gint     mh_remove_msg           (Folder         *folder,
71                                          FolderItem     *item,
72                                          gint            num);
73 static gint     mh_remove_all_msg       (Folder         *folder,
74                                          FolderItem     *item);
75 static gboolean mh_is_msg_changed       (Folder         *folder,
76                                          FolderItem     *item,
77                                          MsgInfo        *msginfo);
78
79 static gint     mh_get_num_list         (Folder         *folder,
80                                          FolderItem     *item, 
81                                          GSList         **list, 
82                                          gboolean       *old_uids_valid);
83 static gint     mh_scan_tree            (Folder         *folder);
84
85 static gint    mh_create_tree           (Folder         *folder);
86 static FolderItem *mh_create_folder     (Folder         *folder,
87                                          FolderItem     *parent,
88                                          const gchar    *name);
89 static gint    mh_rename_folder         (Folder         *folder,
90                                          FolderItem     *item,
91                                          const gchar    *name);
92 static gint    mh_remove_folder         (Folder         *folder,
93                                          FolderItem     *item);
94
95 static gchar   *mh_get_new_msg_filename         (FolderItem     *dest);
96
97 static MsgInfo *mh_parse_msg                    (const gchar    *file,
98                                                  FolderItem     *item);
99 static void     mh_remove_missing_folder_items  (Folder         *folder);
100 static void     mh_scan_tree_recursive          (FolderItem     *item);
101
102 static gboolean mh_rename_folder_func           (GNode          *node,
103                                                  gpointer        data);
104 static gchar   *mh_item_get_path                (Folder *folder, 
105                                                  FolderItem *item);
106
107 static FolderClass mh_class =
108 {
109         F_MH,
110         "mh",
111         "MH",
112
113         /* Folder functions */
114         mh_folder_new,
115         mh_folder_destroy,
116         mh_scan_tree,
117         mh_create_tree,
118
119         /* FolderItem functions */
120         NULL,
121         NULL,
122         NULL,
123         NULL,
124         mh_item_get_path,
125         mh_create_folder,
126         mh_rename_folder,
127         mh_remove_folder,
128         NULL,
129         mh_get_num_list,
130         NULL,
131         NULL,
132         NULL,
133         NULL,
134         
135         /* Message functions */
136         mh_get_msginfo,
137         NULL,
138         mh_fetch_msg,
139         mh_add_msg,
140         mh_add_msgs,
141         mh_copy_msg,
142         NULL,
143         mh_remove_msg,
144         mh_remove_all_msg,
145         mh_is_msg_changed,
146         NULL,
147 };
148
149 FolderClass *mh_get_class(void)
150 {
151         return &mh_class;
152 }
153
154 static Folder *mh_folder_new(const gchar *name, const gchar *path)
155 {
156         Folder *folder;
157
158         folder = (Folder *)g_new0(MHFolder, 1);
159         folder->klass = &mh_class;
160         mh_folder_init(folder, name, path);
161
162         return folder;
163 }
164
165 static void mh_folder_destroy(Folder *folder)
166 {
167         folder_local_folder_destroy(LOCAL_FOLDER(folder));
168 }
169
170 static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path)
171 {
172         folder_local_folder_init(folder, name, path);
173
174 }
175
176 void mh_get_last_num(Folder *folder, FolderItem *item)
177 {
178         gchar *path;
179         DIR *dp;
180         struct dirent *d;
181         gint max = 0;
182         gint num;
183
184         g_return_if_fail(item != NULL);
185
186         debug_print("mh_get_last_num(): Scanning %s ...\n", item->path);
187
188         path = folder_item_get_path(item);
189         g_return_if_fail(path != NULL);
190         if (change_dir(path) < 0) {
191                 g_free(path);
192                 return;
193         }
194         g_free(path);
195
196         if ((dp = opendir(".")) == NULL) {
197                 FILE_OP_ERROR(item->path, "opendir");
198                 return;
199         }
200
201         while ((d = readdir(dp)) != NULL) {
202                 if ((num = to_number(d->d_name)) >= 0 &&
203                     dirent_is_regular_file(d)) {
204                         if (max < num)
205                                 max = num;
206                 }
207         }
208         closedir(dp);
209
210         debug_print("Last number in dir %s = %d\n", item->path, max);
211         item->last_num = max;
212 }
213
214 gint mh_get_num_list(Folder *folder, FolderItem *item, GSList **list, gboolean *old_uids_valid)
215 {
216
217         gchar *path;
218         DIR *dp;
219         struct dirent *d;
220         gint num, nummsgs = 0;
221
222         g_return_val_if_fail(item != NULL, -1);
223
224         debug_print("mh_get_last_num(): Scanning %s ...\n", item->path);
225
226         *old_uids_valid = TRUE;
227
228         path = folder_item_get_path(item);
229         g_return_val_if_fail(path != NULL, -1);
230         if (change_dir(path) < 0) {
231                 g_free(path);
232                 return -1;
233         }
234         g_free(path);
235
236         if ((dp = opendir(".")) == NULL) {
237                 FILE_OP_ERROR(item->path, "opendir");
238                 return -1;
239         }
240
241         while ((d = readdir(dp)) != NULL) {
242                 if ((num = to_number(d->d_name)) >= 0) {
243                         *list = g_slist_prepend(*list, GINT_TO_POINTER(num));
244                         nummsgs++;
245                 }
246         }
247         closedir(dp);
248
249         return nummsgs;
250 }
251
252 static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num)
253 {
254         gchar *path;
255         gchar *file;
256
257         g_return_val_if_fail(item != NULL, NULL);
258         g_return_val_if_fail(num > 0, NULL);
259
260         path = folder_item_get_path(item);
261         file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
262         g_free(path);
263         if (!is_file_exist(file)) {
264                 g_free(file);
265                 return NULL;
266         }
267
268         return file;
269 }
270
271 static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num)
272 {
273         MsgInfo *msginfo;
274         gchar *file;
275
276         g_return_val_if_fail(item != NULL, NULL);
277         g_return_val_if_fail(num > 0, NULL);
278
279         file = mh_fetch_msg(folder, item, num);
280         if (!file) return NULL;
281
282         msginfo = mh_parse_msg(file, item);
283         if (msginfo)
284                 msginfo->msgnum = num;
285
286         g_free(file);
287
288         return msginfo;
289 }
290
291 static gchar *mh_get_new_msg_filename(FolderItem *dest)
292 {
293         gchar *destfile;
294         gchar *destpath;
295
296         destpath = folder_item_get_path(dest);
297         g_return_val_if_fail(destpath != NULL, NULL);
298
299         if (!is_dir_exist(destpath))
300                 make_dir_hier(destpath);
301
302         for (;;) {
303                 destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
304                                            dest->last_num + 1);
305                 if (is_file_entry_exist(destfile)) {
306                         dest->last_num++;
307                         g_free(destfile);
308                 } else
309                         break;
310         }
311
312         g_free(destpath);
313
314         return destfile;
315 }
316
317 static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file, MsgFlags *flags)
318 {
319         gint ret;
320         GSList file_list;
321         MsgFileInfo fileinfo;
322
323         g_return_val_if_fail(file != NULL, -1);
324
325         fileinfo.msginfo = NULL;
326         fileinfo.file = (gchar *)file;
327         fileinfo.flags = flags;
328         file_list.data = &fileinfo;
329         file_list.next = NULL;
330
331         ret = mh_add_msgs(folder, dest, &file_list, NULL);
332         return ret;
333
334  
335 static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, 
336                  GRelation *relation)
337
338         gchar *destfile;
339         GSList *cur;
340         MsgFileInfo *fileinfo;
341
342         g_return_val_if_fail(dest != NULL, -1);
343         g_return_val_if_fail(file_list != NULL, -1);
344
345         if (dest->last_num < 0) {
346                 mh_get_last_num(folder, dest);
347                 if (dest->last_num < 0) return -1;
348         }
349
350         for (cur = file_list; cur != NULL; cur = cur->next) {
351                 fileinfo = (MsgFileInfo *)cur->data;
352
353                 destfile = mh_get_new_msg_filename(dest);
354                 if (destfile == NULL) return -1;
355
356                 if (link(fileinfo->file, destfile) < 0) {
357                         if (copy_file(fileinfo->file, destfile, TRUE) < 0) {
358                                 g_warning(_("can't copy message %s to %s\n"),
359                                           fileinfo->file, destfile);
360                                 g_free(destfile);
361                                 return -1;
362                         }
363                 }
364                 if (relation != NULL)
365                         g_relation_insert(relation, fileinfo, GINT_TO_POINTER(dest->last_num + 1));
366                 g_free(destfile);
367                 dest->last_num++;
368         }
369
370         return dest->last_num;
371 }
372
373 static gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
374 {
375         gchar *srcfile;
376         gchar *destfile;
377         gint filemode = 0;
378         FolderItemPrefs *prefs;
379
380         g_return_val_if_fail(dest != NULL, -1);
381         g_return_val_if_fail(msginfo != NULL, -1);
382
383         if (msginfo->folder == dest) {
384                 g_warning("the src folder is identical to the dest.\n");
385                 return -1;
386         }
387
388         if (dest->last_num < 0) {
389                 mh_get_last_num(folder, dest);
390                 if (dest->last_num < 0) return -1;
391         }
392
393         prefs = dest->prefs;
394
395         srcfile = procmsg_get_message_file(msginfo);
396         destfile = mh_get_new_msg_filename(dest);
397         if (!destfile) {
398                 g_free(srcfile);
399                 return -1;
400         }
401         
402         debug_print("Copying message %s%c%d to %s ...\n",
403                     msginfo->folder->path, G_DIR_SEPARATOR,
404                     msginfo->msgnum, dest->path);
405         
406
407         if ((MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags))
408         &&  dest->stype != F_QUEUE && dest->stype != F_DRAFT) {
409                 if (procmsg_remove_special_headers(srcfile, destfile) !=0) {
410                         g_free(srcfile);
411                         g_free(destfile);
412                         return -1;
413                 }
414         } else if (copy_file(srcfile, destfile, TRUE) < 0) {
415                 FILE_OP_ERROR(srcfile, "copy");
416                 g_free(srcfile);
417                 g_free(destfile);
418                 return -1;
419         }
420
421
422         if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
423                 if (chmod(destfile, prefs->folder_chmod) < 0)
424                         FILE_OP_ERROR(destfile, "chmod");
425
426                 /* for mark file */
427                 filemode = prefs->folder_chmod;
428                 if (filemode & S_IRGRP) filemode |= S_IWGRP;
429                 if (filemode & S_IROTH) filemode |= S_IWOTH;
430         }
431
432         g_free(srcfile);
433         g_free(destfile);
434         dest->last_num++;
435
436         return dest->last_num;
437 }
438
439 static gint mh_remove_msg(Folder *folder, FolderItem *item, gint num)
440 {
441         gchar *file;
442
443         g_return_val_if_fail(item != NULL, -1);
444
445         file = mh_fetch_msg(folder, item, num);
446         g_return_val_if_fail(file != NULL, -1);
447
448         if (unlink(file) < 0) {
449                 FILE_OP_ERROR(file, "unlink");
450                 g_free(file);
451                 return -1;
452         }
453
454         g_free(file);
455         return 0;
456 }
457
458 static gint mh_remove_all_msg(Folder *folder, FolderItem *item)
459 {
460         gchar *path;
461         gint val;
462
463         g_return_val_if_fail(item != NULL, -1);
464
465         path = folder_item_get_path(item);
466         g_return_val_if_fail(path != NULL, -1);
467         val = remove_all_numbered_files(path);
468         g_free(path);
469
470         return val;
471 }
472
473 static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item,
474                                   MsgInfo *msginfo)
475 {
476         struct stat s;
477
478         if (stat(itos(msginfo->msgnum), &s) < 0 ||
479             msginfo->size  != s.st_size ||
480             msginfo->mtime != s.st_mtime)
481                 return TRUE;
482
483         return FALSE;
484 }
485
486 static gint mh_scan_tree(Folder *folder)
487 {
488         FolderItem *item;
489         gchar *rootpath;
490
491         g_return_val_if_fail(folder != NULL, -1);
492
493         if (!folder->node) {
494                 item = folder_item_new(folder, folder->name, NULL);
495                 item->folder = folder;
496                 folder->node = item->node = g_node_new(item);
497         } else
498                 item = FOLDER_ITEM(folder->node->data);
499
500         rootpath = folder_item_get_path(item);
501         if (change_dir(rootpath) < 0) {
502                 g_free(rootpath);
503                 return -1;
504         }
505         g_free(rootpath);
506
507         mh_create_tree(folder);
508         mh_remove_missing_folder_items(folder);
509         mh_scan_tree_recursive(item);
510
511         return 0;
512 }
513
514 #define MAKE_DIR_IF_NOT_EXIST(dir) \
515 { \
516         if (!is_dir_exist(dir)) { \
517                 if (is_file_exist(dir)) { \
518                         g_warning("File `%s' already exists.\n" \
519                                     "Can't create folder.", dir); \
520                         return -1; \
521                 } \
522                 if (make_dir(dir) < 0) \
523                         return -1; \
524         } \
525 }
526
527 static gint mh_create_tree(Folder *folder)
528 {
529         gchar *rootpath;
530
531         g_return_val_if_fail(folder != NULL, -1);
532
533         CHDIR_RETURN_VAL_IF_FAIL(get_home_dir(), -1);
534         rootpath = LOCAL_FOLDER(folder)->rootpath;
535         MAKE_DIR_IF_NOT_EXIST(rootpath);
536         CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1);
537         MAKE_DIR_IF_NOT_EXIST(INBOX_DIR);
538         MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR);
539         MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR);
540         MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR);
541         MAKE_DIR_IF_NOT_EXIST(TRASH_DIR);
542
543         return 0;
544 }
545
546 #undef MAKE_DIR_IF_NOT_EXIST
547
548 static gchar *mh_item_get_path(Folder *folder, FolderItem *item)
549 {
550         gchar *folder_path, *path;
551
552         g_return_val_if_fail(folder != NULL, NULL);
553         g_return_val_if_fail(item != NULL, NULL);
554
555         folder_path = g_strdup(LOCAL_FOLDER(folder)->rootpath);
556         g_return_val_if_fail(folder_path != NULL, NULL);
557
558         if (folder_path[0] == G_DIR_SEPARATOR) {
559                 if (item->path)
560                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
561                                            item->path, NULL);
562                 else
563                         path = g_strdup(folder_path);
564         } else {
565                 if (item->path)
566                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
567                                            folder_path, G_DIR_SEPARATOR_S,
568                                            item->path, NULL);
569                 else
570                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
571                                            folder_path, NULL);
572         }
573         g_free(folder_path);
574
575         return path;
576 }
577
578 static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent,
579                                     const gchar *name)
580 {
581         gchar *path;
582         gchar *fullpath;
583         FolderItem *new_item;
584         gchar *mh_sequences_filename;
585         FILE *mh_sequences_file;
586
587         g_return_val_if_fail(folder != NULL, NULL);
588         g_return_val_if_fail(parent != NULL, NULL);
589         g_return_val_if_fail(name != NULL, NULL);
590
591         path = folder_item_get_path(parent);
592         if (!is_dir_exist(path)) 
593                 if (make_dir_hier(path) != 0)
594                         return NULL;
595                 
596         fullpath = g_strconcat(path, G_DIR_SEPARATOR_S, name, NULL);
597         g_free(path);
598
599         if (make_dir(fullpath) < 0) {
600                 g_free(fullpath);
601                 return NULL;
602         }
603
604         g_free(fullpath);
605
606         if (parent->path)
607                 path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name,
608                                    NULL);
609         else
610                 path = g_strdup(name);
611         new_item = folder_item_new(folder, name, path);
612         folder_item_append(parent, new_item);
613
614         g_free(path);
615
616         path = folder_item_get_path(new_item);
617         mh_sequences_filename = g_strconcat(path, G_DIR_SEPARATOR_S,
618                                             ".mh_sequences", NULL);
619         if ((mh_sequences_file = fopen(mh_sequences_filename, "a+b")) != NULL) {
620                 fclose(mh_sequences_file);
621         }
622         g_free(mh_sequences_filename);
623         g_free(path);
624
625         return new_item;
626 }
627
628 static gint mh_rename_folder(Folder *folder, FolderItem *item,
629                              const gchar *name)
630 {
631         gchar *oldpath;
632         gchar *dirname;
633         gchar *newpath;
634         gchar *paths[2];
635
636         g_return_val_if_fail(folder != NULL, -1);
637         g_return_val_if_fail(item != NULL, -1);
638         g_return_val_if_fail(item->path != NULL, -1);
639         g_return_val_if_fail(name != NULL, -1);
640
641         oldpath = folder_item_get_path(item);
642         if (!is_dir_exist(oldpath))
643                 make_dir_hier(oldpath);
644
645         dirname = g_dirname(oldpath);
646         newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, name, NULL);
647         g_free(dirname);
648
649         if (rename(oldpath, newpath) < 0) {
650                 FILE_OP_ERROR(oldpath, "rename");
651                 g_free(oldpath);
652                 g_free(newpath);
653                 return -1;
654         }
655
656         g_free(oldpath);
657         g_free(newpath);
658
659         if (strchr(item->path, G_DIR_SEPARATOR) != NULL) {
660                 dirname = g_dirname(item->path);
661                 newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, name, NULL);
662                 g_free(dirname);
663         } else
664                 newpath = g_strdup(name);
665
666         g_free(item->name);
667         item->name = g_strdup(name);
668
669         paths[0] = g_strdup(item->path);
670         paths[1] = newpath;
671         g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
672                         mh_rename_folder_func, paths);
673
674         g_free(paths[0]);
675         g_free(paths[1]);
676         return 0;
677 }
678
679 static gint mh_remove_folder(Folder *folder, FolderItem *item)
680 {
681         gchar *path;
682
683         g_return_val_if_fail(folder != NULL, -1);
684         g_return_val_if_fail(item != NULL, -1);
685         g_return_val_if_fail(item->path != NULL, -1);
686
687         path = folder_item_get_path(item);
688         if (remove_dir_recursive(path) < 0) {
689                 g_warning("can't remove directory `%s'\n", path);
690                 g_free(path);
691                 return -1;
692         }
693
694         g_free(path);
695         folder_item_remove(item);
696         return 0;
697 }
698
699 static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item)
700 {
701         struct stat s;
702         MsgInfo *msginfo;
703         MsgFlags flags;
704
705         g_return_val_if_fail(item != NULL, NULL);
706         g_return_val_if_fail(file != NULL, NULL);
707
708         flags.perm_flags = MSG_NEW|MSG_UNREAD;
709         flags.tmp_flags = 0;
710
711         if (item->stype == F_QUEUE) {
712                 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
713         } else if (item->stype == F_DRAFT) {
714                 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
715         }
716
717         if (stat(file, &s) < 0) {
718                 FILE_OP_ERROR(file, "stat");
719                 return NULL;
720         }
721         if (!S_ISREG(s.st_mode))
722                 return NULL;
723
724         msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
725         if (!msginfo) return NULL;
726
727         msginfo->msgnum = atoi(file);
728         msginfo->folder = item;
729         msginfo->size = s.st_size;
730         msginfo->mtime = s.st_mtime;
731
732         return msginfo;
733 }
734
735 #if 0
736 static gboolean mh_is_maildir_one(const gchar *path, const gchar *dir)
737 {
738         gchar *entry;
739         gboolean result;
740
741         entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir, NULL);
742         result = is_dir_exist(entry);
743         g_free(entry);
744
745         return result;
746 }
747
748 /*
749  * check whether PATH is a Maildir style mailbox.
750  * This is the case if the 3 subdir: new, cur, tmp are existing.
751  * This functon assumes that entry is an directory
752  */
753 static gboolean mh_is_maildir(const gchar *path)
754 {
755         return mh_is_maildir_one(path, "new") &&
756                mh_is_maildir_one(path, "cur") &&
757                mh_is_maildir_one(path, "tmp");
758 }
759 #endif
760
761 static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data)
762 {
763         FolderItem *item;
764         gchar *path;
765
766         g_return_val_if_fail(node->data != NULL, FALSE);
767
768         if (G_NODE_IS_ROOT(node))
769                 return FALSE;
770
771         item = FOLDER_ITEM(node->data);
772
773         path = folder_item_get_path(item);
774         if (!is_dir_exist(path)) {
775                 debug_print("folder '%s' not found. removing...\n", path);
776                 folder_item_remove(item);
777         }
778         g_free(path);
779
780         return FALSE;
781 }
782
783 static void mh_remove_missing_folder_items(Folder *folder)
784 {
785         g_return_if_fail(folder != NULL);
786
787         debug_print("searching missing folders...\n");
788
789         g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
790                         mh_remove_missing_folder_items_func, folder);
791 }
792
793 static void mh_scan_tree_recursive(FolderItem *item)
794 {
795         Folder *folder;
796         DIR *dp;
797         struct dirent *d;
798         struct stat s;
799         gchar *entry;
800         gint n_msg = 0;
801
802         g_return_if_fail(item != NULL);
803         g_return_if_fail(item->folder != NULL);
804
805         folder = item->folder;
806
807         dp = opendir(item->path ? item->path : ".");
808         if (!dp) {
809                 FILE_OP_ERROR(item->path ? item->path : ".", "opendir");
810                 return;
811         }
812
813         debug_print("scanning %s ...\n",
814                     item->path ? item->path
815                     : LOCAL_FOLDER(item->folder)->rootpath);
816         if (folder->ui_func)
817                 folder->ui_func(folder, item, folder->ui_func_data);
818
819         while ((d = readdir(dp)) != NULL) {
820                 if (d->d_name[0] == '.') continue;
821
822                 if (item->path)
823                         entry = g_strconcat(item->path, G_DIR_SEPARATOR_S,
824                                             d->d_name, NULL);
825                 else
826                         entry = g_strdup(d->d_name);
827
828                 if (
829 #ifdef HAVE_DIRENT_D_TYPE
830                         d->d_type == DT_DIR ||
831                         (d->d_type == DT_UNKNOWN &&
832 #endif
833                         stat(entry, &s) == 0 && S_ISDIR(s.st_mode)
834 #ifdef HAVE_DIRENT_D_TYPE
835                         )
836 #endif
837                    ) {
838                         FolderItem *new_item = NULL;
839                         GNode *node;
840
841 #if 0
842                         if (mh_is_maildir(entry)) {
843                                 g_free(entry);
844                                 continue;
845                         }
846 #endif
847
848                         node = item->node;
849                         for (node = node->children; node != NULL; node = node->next) {
850                                 FolderItem *cur_item = FOLDER_ITEM(node->data);
851                                 if (!strcmp2(cur_item->path, entry)) {
852                                         new_item = cur_item;
853                                         break;
854                                 }
855                         }
856                         if (!new_item) {
857                                 debug_print("new folder '%s' found.\n", entry);
858                                 new_item = folder_item_new(folder, d->d_name, entry);
859                                 folder_item_append(item, new_item);
860                         }
861
862                         if (!item->path) {
863                                 if (!folder->inbox &&
864                                     !strcmp(d->d_name, INBOX_DIR)) {
865                                         new_item->stype = F_INBOX;
866                                         folder->inbox = new_item;
867                                 } else if (!folder->outbox &&
868                                            !strcmp(d->d_name, OUTBOX_DIR)) {
869                                         new_item->stype = F_OUTBOX;
870                                         folder->outbox = new_item;
871                                 } else if (!folder->draft &&
872                                            !strcmp(d->d_name, DRAFT_DIR)) {
873                                         new_item->stype = F_DRAFT;
874                                         folder->draft = new_item;
875                                 } else if (!folder->queue &&
876                                            !strcmp(d->d_name, QUEUE_DIR)) {
877                                         new_item->stype = F_QUEUE;
878                                         folder->queue = new_item;
879                                 } else if (!folder->trash &&
880                                            !strcmp(d->d_name, TRASH_DIR)) {
881                                         new_item->stype = F_TRASH;
882                                         folder->trash = new_item;
883                                 }
884                         }
885
886                         mh_scan_tree_recursive(new_item);
887                 } else if (to_number(d->d_name) != -1) n_msg++;
888
889                 g_free(entry);
890         }
891
892         closedir(dp);
893
894 /*
895         if (item->path) {
896                 gint new, unread, total, min, max;
897
898                 procmsg_get_mark_sum(item->path, &new, &unread, &total,
899                                      &min, &max, 0);
900                 if (n_msg > total) {
901                         new += n_msg - total;
902                         unread += n_msg - total;
903                 }
904                 item->new = new;
905                 item->unread = unread;
906                 item->total = n_msg;
907         }
908 */
909 }
910
911 static gboolean mh_rename_folder_func(GNode *node, gpointer data)
912 {
913         FolderItem *item = node->data;
914         gchar **paths = data;
915         const gchar *oldpath = paths[0];
916         const gchar *newpath = paths[1];
917         gchar *base;
918         gchar *new_itempath;
919         gint oldpathlen;
920
921         oldpathlen = strlen(oldpath);
922         if (strncmp(oldpath, item->path, oldpathlen) != 0) {
923                 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
924                 return TRUE;
925         }
926
927         base = item->path + oldpathlen;
928         while (*base == G_DIR_SEPARATOR) base++;
929         if (*base == '\0')
930                 new_itempath = g_strdup(newpath);
931         else
932                 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
933                                            NULL);
934         g_free(item->path);
935         item->path = new_itempath;
936
937         return FALSE;
938 }