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