2005-12-30 [colin] 1.9.100cvs115
[claws.git] / src / mh.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <dirent.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <errno.h>
33
34 #undef MEASURE_TIME
35
36 #ifdef MEASURE_TIME
37 #  include <sys/time.h>
38 #endif
39
40 #include "folder.h"
41 #include "mh.h"
42 #include "procmsg.h"
43 #include "procheader.h"
44 #include "utils.h"
45 #include "codeconv.h"
46
47 /* Define possible missing constants for Windows. */
48 #ifdef G_OS_WIN32
49 # ifndef S_IRGRP
50 # define S_IRGRP 0
51 # define S_IWGRP 0
52 # endif
53 # ifndef S_IROTH
54 # define S_IROTH 0
55 # define S_IWOTH 0
56 # endif
57 #endif
58
59
60 static void     mh_folder_init          (Folder         *folder,
61                                          const gchar    *name,
62                                          const gchar    *path);
63
64 static Folder   *mh_folder_new          (const gchar    *name,
65                                          const gchar    *path);
66 static void     mh_folder_destroy       (Folder         *folder);
67 static gchar   *mh_fetch_msg            (Folder         *folder,
68                                          FolderItem     *item,
69                                          gint            num);
70 static MsgInfo *mh_get_msginfo          (Folder         *folder,
71                                          FolderItem     *item,
72                                          gint            num);
73 static gint     mh_add_msg              (Folder         *folder,
74                                          FolderItem     *dest,
75                                          const gchar    *file,
76                                          MsgFlags       *flags);
77 static gint     mh_add_msgs             (Folder         *folder,
78                                          FolderItem     *dest,
79                                          GSList         *file_list,
80                                          GRelation      *relation);
81 static gint     mh_copy_msg             (Folder         *folder,
82                                          FolderItem     *dest,
83                                          MsgInfo        *msginfo);
84 static gint     mh_copy_msgs            (Folder         *folder, 
85                                          FolderItem     *dest, 
86                                          MsgInfoList    *msglist, 
87                                          GRelation      *relation);
88 static gint     mh_remove_msg           (Folder         *folder,
89                                          FolderItem     *item,
90                                          gint            num);
91 static gint     mh_remove_all_msg       (Folder         *folder,
92                                          FolderItem     *item);
93 static gboolean mh_is_msg_changed       (Folder         *folder,
94                                          FolderItem     *item,
95                                          MsgInfo        *msginfo);
96
97 static gint     mh_get_num_list         (Folder         *folder,
98                                          FolderItem     *item, 
99                                          GSList         **list, 
100                                          gboolean       *old_uids_valid);
101 static gint     mh_scan_tree            (Folder         *folder);
102
103 static gint    mh_create_tree           (Folder         *folder);
104 static FolderItem *mh_create_folder     (Folder         *folder,
105                                          FolderItem     *parent,
106                                          const gchar    *name);
107 static gint    mh_rename_folder         (Folder         *folder,
108                                          FolderItem     *item,
109                                          const gchar    *name);
110 static gint    mh_remove_folder         (Folder         *folder,
111                                          FolderItem     *item);
112
113 static gchar   *mh_get_new_msg_filename         (FolderItem     *dest);
114
115 static MsgInfo *mh_parse_msg                    (const gchar    *file,
116                                                  FolderItem     *item);
117 static void     mh_remove_missing_folder_items  (Folder         *folder);
118 static gchar    *mh_filename_from_utf8          (const gchar    *path);
119 static gchar    *mh_filename_to_utf8            (const gchar    *path);
120 static void     mh_scan_tree_recursive          (FolderItem     *item);
121
122 static gboolean mh_rename_folder_func           (GNode          *node,
123                                                  gpointer        data);
124 static gchar   *mh_item_get_path                (Folder *folder, 
125                                                  FolderItem *item);
126
127 static gboolean mh_scan_required        (Folder         *folder,
128                                          FolderItem     *item);
129
130 static FolderClass mh_class;
131
132 FolderClass *mh_get_class(void)
133 {
134         if (mh_class.idstr == NULL) {
135                 mh_class.type = F_MH;
136                 mh_class.idstr = "mh";
137                 mh_class.uistr = "MH";
138                 
139                 /* Folder functions */
140                 mh_class.new_folder = mh_folder_new;
141                 mh_class.destroy_folder = mh_folder_destroy;
142                 mh_class.set_xml = folder_local_set_xml;
143                 mh_class.get_xml = folder_local_get_xml;
144                 mh_class.scan_tree = mh_scan_tree;
145                 mh_class.create_tree = mh_create_tree;
146
147                 /* FolderItem functions */
148                 mh_class.item_get_path = mh_item_get_path;
149                 mh_class.create_folder = mh_create_folder;
150                 mh_class.rename_folder = mh_rename_folder;
151                 mh_class.remove_folder = mh_remove_folder;
152                 mh_class.get_num_list = mh_get_num_list;
153                 mh_class.scan_required = mh_scan_required;
154                 
155                 /* Message functions */
156                 mh_class.get_msginfo = mh_get_msginfo;
157                 mh_class.fetch_msg = mh_fetch_msg;
158                 mh_class.add_msg = mh_add_msg;
159                 mh_class.add_msgs = mh_add_msgs;
160                 mh_class.copy_msg = mh_copy_msg;
161                 mh_class.copy_msgs = mh_copy_msgs;
162                 mh_class.remove_msg = mh_remove_msg;
163                 mh_class.remove_all_msg = mh_remove_all_msg;
164                 mh_class.is_msg_changed = mh_is_msg_changed;
165         }
166
167         return &mh_class;
168 }
169
170 static Folder *mh_folder_new(const gchar *name, const gchar *path)
171 {
172         Folder *folder;
173
174         folder = (Folder *)g_new0(MHFolder, 1);
175         folder->klass = &mh_class;
176         mh_folder_init(folder, name, path);
177
178         return folder;
179 }
180
181 static void mh_folder_destroy(Folder *folder)
182 {
183         folder_local_folder_destroy(LOCAL_FOLDER(folder));
184 }
185
186 static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path)
187 {
188         folder_local_folder_init(folder, name, path);
189
190 }
191
192 gboolean mh_scan_required(Folder *folder, FolderItem *item)
193 {
194         gchar *path;
195         struct stat s;
196
197         path = folder_item_get_path(item);
198         g_return_val_if_fail(path != NULL, FALSE);
199
200         if (stat(path, &s) < 0) {
201                 FILE_OP_ERROR(path, "stat");
202                 g_free(path);
203                 return FALSE;
204         }
205
206         if ((s.st_mtime > item->mtime) &&
207                 (s.st_mtime - 3600 != item->mtime)) {
208                 debug_print("MH scan required, folder updated: %s (%ld > %ld)\n",
209                             path,
210                             (long int) s.st_mtime,
211                             (long int) item->mtime);
212                 g_free(path);
213                 return TRUE;
214         }
215
216         debug_print("MH scan not required: %s (%ld <= %ld)\n",
217                     path,
218                     (long int) s.st_mtime,
219                     (long int) item->mtime);
220         g_free(path);
221         return FALSE;
222 }
223
224 void mh_get_last_num(Folder *folder, FolderItem *item)
225 {
226         gchar *path;
227         DIR *dp;
228         struct dirent *d;
229         gint max = 0;
230         gint num;
231
232         g_return_if_fail(item != NULL);
233
234         debug_print("mh_get_last_num(): Scanning %s ...\n", item->path);
235
236         path = folder_item_get_path(item);
237         g_return_if_fail(path != NULL);
238         if (change_dir(path) < 0) {
239                 g_free(path);
240                 return;
241         }
242         g_free(path);
243
244         if ((dp = opendir(".")) == NULL) {
245                 FILE_OP_ERROR(item->path, "opendir");
246                 return;
247         }
248
249         while ((d = readdir(dp)) != NULL) {
250                 if ((num = to_number(d->d_name)) > 0 &&
251                     dirent_is_regular_file(d)) {
252                         if (max < num)
253                                 max = num;
254                 }
255         }
256         closedir(dp);
257
258         debug_print("Last number in dir %s = %d\n", item->path, max);
259         item->last_num = max;
260 }
261
262 gint mh_get_num_list(Folder *folder, FolderItem *item, GSList **list, gboolean *old_uids_valid)
263 {
264
265         gchar *path;
266         DIR *dp;
267         struct dirent *d;
268         gint num, nummsgs = 0;
269
270         g_return_val_if_fail(item != NULL, -1);
271
272         debug_print("mh_get_num_list(): Scanning %s ...\n", item->path);
273
274         *old_uids_valid = TRUE;
275
276         path = folder_item_get_path(item);
277         g_return_val_if_fail(path != NULL, -1);
278         if (change_dir(path) < 0) {
279                 g_free(path);
280                 return -1;
281         }
282         g_free(path);
283
284         if ((dp = opendir(".")) == NULL) {
285                 FILE_OP_ERROR(item->path, "opendir");
286                 return -1;
287         }
288
289         while ((d = readdir(dp)) != NULL) {
290                 if ((num = to_number(d->d_name)) > 0) {
291                         *list = g_slist_prepend(*list, GINT_TO_POINTER(num));
292                         nummsgs++;
293                 }
294         }
295         closedir(dp);
296
297         item->mtime = time(NULL);
298         return nummsgs;
299 }
300
301 static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num)
302 {
303         gchar *path;
304         gchar *file;
305
306         g_return_val_if_fail(item != NULL, NULL);
307         g_return_val_if_fail(num > 0, NULL);
308
309         path = folder_item_get_path(item);
310         file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
311
312         if (!is_file_exist(file)) {
313                 g_free(file);
314                 g_free(path);
315                 return NULL;
316         }
317         g_free(path);
318         return file;
319 }
320
321 static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num)
322 {
323         MsgInfo *msginfo;
324         gchar *file;
325
326         g_return_val_if_fail(item != NULL, NULL);
327         g_return_val_if_fail(num > 0, NULL);
328
329         file = mh_fetch_msg(folder, item, num);
330         if (!file) return NULL;
331
332         msginfo = mh_parse_msg(file, item);
333         if (msginfo)
334                 msginfo->msgnum = num;
335
336         g_free(file);
337
338         return msginfo;
339 }
340
341 static gchar *mh_get_new_msg_filename(FolderItem *dest)
342 {
343         gchar *destfile;
344         gchar *destpath;
345
346         destpath = folder_item_get_path(dest);
347         g_return_val_if_fail(destpath != NULL, NULL);
348
349         if (!is_dir_exist(destpath))
350                 make_dir_hier(destpath);
351
352         for (;;) {
353                 destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
354                                            dest->last_num + 1);
355                 if (is_file_entry_exist(destfile)) {
356                         dest->last_num++;
357                         g_free(destfile);
358                 } else
359                         break;
360         }
361
362         g_free(destpath);
363
364         return destfile;
365 }
366
367 static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file, MsgFlags *flags)
368 {
369         gint ret;
370         GSList file_list;
371         MsgFileInfo fileinfo;
372
373         g_return_val_if_fail(file != NULL, -1);
374
375         fileinfo.msginfo = NULL;
376         fileinfo.file = (gchar *)file;
377         fileinfo.flags = flags;
378         file_list.data = &fileinfo;
379         file_list.next = NULL;
380
381         ret = mh_add_msgs(folder, dest, &file_list, NULL);
382         return ret;
383
384  
385 static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, 
386                  GRelation *relation)
387
388         gchar *destfile;
389         GSList *cur;
390         MsgFileInfo *fileinfo;
391
392         g_return_val_if_fail(dest != NULL, -1);
393         g_return_val_if_fail(file_list != NULL, -1);
394
395         if (dest->last_num < 0) {
396                 mh_get_last_num(folder, dest);
397                 if (dest->last_num < 0) return -1;
398         }
399
400         for (cur = file_list; cur != NULL; cur = cur->next) {
401                 fileinfo = (MsgFileInfo *)cur->data;
402
403                 destfile = mh_get_new_msg_filename(dest);
404                 if (destfile == NULL) return -1;
405
406 #ifdef G_OS_UNIX
407                 if (link(fileinfo->file, destfile) < 0) {
408 #endif
409                         if (copy_file(fileinfo->file, destfile, TRUE) < 0) {
410                                 g_warning(_("can't copy message %s to %s\n"),
411                                           fileinfo->file, destfile);
412                                 g_free(destfile);
413                                 return -1;
414                         }
415 #ifdef G_OS_UNIX
416                 }
417 #endif
418
419                 if (relation != NULL)
420                         g_relation_insert(relation, fileinfo, GINT_TO_POINTER(dest->last_num + 1));
421                 g_free(destfile);
422                 dest->last_num++;
423         }
424
425         return dest->last_num;
426 }
427
428 static gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
429 {
430         GSList msglist;
431
432         g_return_val_if_fail(msginfo != NULL, -1);
433
434         msglist.data = msginfo;
435         msglist.next = NULL;
436
437         return mh_copy_msgs(folder, dest, &msglist, NULL);      
438 }
439
440 static gint mh_copy_msgs(Folder *folder, FolderItem *dest, MsgInfoList *msglist, 
441                          GRelation *relation)
442 {
443         gboolean dest_need_scan = FALSE;
444         gchar *srcfile;
445         gchar *destfile;
446         gint filemode = 0;
447         FolderItemPrefs *prefs;
448         MsgInfo *msginfo = NULL;
449         gboolean remove_special_headers = FALSE;
450         MsgInfoList *cur = NULL;
451         g_return_val_if_fail(dest != NULL, -1);
452         g_return_val_if_fail(msglist != NULL, -1);
453         
454         msginfo = (MsgInfo *)msglist->data;
455
456         g_return_val_if_fail(msginfo != NULL, -1);
457
458         if (msginfo->folder == dest) {
459                 g_warning("the src folder is identical to the dest.\n");
460                 return -1;
461         }
462
463         if (dest->last_num < 0) {
464                 mh_get_last_num(folder, dest);
465                 if (dest->last_num < 0) return -1;
466         }
467
468         if ((MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags))
469         && !folder_has_parent_of_type(dest, F_QUEUE)
470         && !folder_has_parent_of_type(dest, F_DRAFT)) {
471                 /* as every msginfo comes from the same folder, it is supposed they
472                  * will either match the preceding condition either all or none.
473                  */
474                 remove_special_headers = TRUE;
475         } else if (!(MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags))
476         && (folder_has_parent_of_type(dest, F_QUEUE)
477          || folder_has_parent_of_type(dest, F_DRAFT))) {
478                 return -1;
479         } 
480
481         prefs = dest->prefs;
482
483         for (cur = msglist; cur; cur = cur->next) {
484                 msginfo = (MsgInfo *)cur->data;
485                 if (!msginfo)
486                         continue;
487                 srcfile = procmsg_get_message_file(msginfo);
488                 destfile = mh_get_new_msg_filename(dest);
489                 if (!destfile) {
490                         g_free(srcfile);
491                         continue;
492                 }
493
494                 debug_print("Copying message %s%c%d to %s ...\n",
495                             msginfo->folder->path, G_DIR_SEPARATOR,
496                             msginfo->msgnum, dest->path);
497
498
499                 if (remove_special_headers) {
500                         if (procmsg_remove_special_headers(srcfile, destfile) !=0) {
501                                 g_free(srcfile);
502                                 g_free(destfile);
503                                 continue;
504                         }
505                 } else if (copy_file(srcfile, destfile, TRUE) < 0) {
506                         FILE_OP_ERROR(srcfile, "copy");
507                         g_free(srcfile);
508                         g_free(destfile);
509                         continue;
510                 }
511                 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
512                         if (chmod(destfile, prefs->folder_chmod) < 0)
513                                 FILE_OP_ERROR(destfile, "chmod");
514
515                         /* for mark file */
516                         filemode = prefs->folder_chmod;
517                         if (filemode & S_IRGRP) filemode |= S_IWGRP;
518                         if (filemode & S_IROTH) filemode |= S_IWOTH;
519                 }
520                 if (relation)
521                         g_relation_insert(relation, msginfo, GINT_TO_POINTER(dest->last_num+1));
522                 g_free(srcfile);
523                 g_free(destfile);
524                 dest->last_num++;
525         }
526
527         dest_need_scan = mh_scan_required(dest->folder, dest);
528         if (!dest_need_scan)
529                 dest->mtime = time(NULL);
530
531         return dest->last_num;
532 }
533
534 static gint mh_remove_msg(Folder *folder, FolderItem *item, gint num)
535 {
536         gboolean need_scan = FALSE;
537         gchar *file;
538
539         g_return_val_if_fail(item != NULL, -1);
540
541         file = mh_fetch_msg(folder, item, num);
542         g_return_val_if_fail(file != NULL, -1);
543
544         need_scan = mh_scan_required(folder, item);
545
546         if (g_unlink(file) < 0) {
547                 FILE_OP_ERROR(file, "unlink");
548                 g_free(file);
549                 return -1;
550         }
551
552         if (!need_scan)
553                 item->mtime = time(NULL);
554
555         g_free(file);
556         return 0;
557 }
558
559 static gint mh_remove_all_msg(Folder *folder, FolderItem *item)
560 {
561         gchar *path;
562         gint val;
563
564         g_return_val_if_fail(item != NULL, -1);
565
566         path = folder_item_get_path(item);
567         g_return_val_if_fail(path != NULL, -1);
568         val = remove_all_numbered_files(path);
569         g_free(path);
570
571         return val;
572 }
573
574 static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item,
575                                   MsgInfo *msginfo)
576 {
577         struct stat s;
578
579         if (g_stat(itos(msginfo->msgnum), &s) < 0 ||
580             msginfo->size  != s.st_size || (
581                 (msginfo->mtime - s.st_mtime != 0) &&
582                 (msginfo->mtime - s.st_mtime != 3600) &&
583                 (msginfo->mtime - s.st_mtime != -3600)))
584                 return TRUE;
585
586         return FALSE;
587 }
588
589 static gint mh_scan_tree(Folder *folder)
590 {
591         FolderItem *item;
592         gchar *rootpath;
593
594         g_return_val_if_fail(folder != NULL, -1);
595
596         if (!folder->node) {
597                 item = folder_item_new(folder, folder->name, NULL);
598                 item->folder = folder;
599                 folder->node = item->node = g_node_new(item);
600         } else
601                 item = FOLDER_ITEM(folder->node->data);
602
603         rootpath = folder_item_get_path(item);
604         if (change_dir(rootpath) < 0) {
605                 g_free(rootpath);
606                 return -1;
607         }
608         g_free(rootpath);
609
610         mh_create_tree(folder);
611         mh_remove_missing_folder_items(folder);
612         mh_scan_tree_recursive(item);
613
614         return 0;
615 }
616
617 #define MAKE_DIR_IF_NOT_EXIST(dir) \
618 { \
619         if (!is_dir_exist(dir)) { \
620                 if (is_file_exist(dir)) { \
621                         g_warning("File `%s' already exists.\n" \
622                                     "Can't create folder.", dir); \
623                         return -1; \
624                 } \
625                 if (make_dir(dir) < 0) \
626                         return -1; \
627         } \
628 }
629
630 static gint mh_create_tree(Folder *folder)
631 {
632         gchar *rootpath;
633
634         g_return_val_if_fail(folder != NULL, -1);
635
636         CHDIR_RETURN_VAL_IF_FAIL(get_mail_base_dir(), -1);
637         rootpath = LOCAL_FOLDER(folder)->rootpath;
638         MAKE_DIR_IF_NOT_EXIST(rootpath);
639         CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1);
640         MAKE_DIR_IF_NOT_EXIST(INBOX_DIR);
641         MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR);
642         MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR);
643         MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR);
644         MAKE_DIR_IF_NOT_EXIST(TRASH_DIR);
645
646         return 0;
647 }
648
649 #undef MAKE_DIR_IF_NOT_EXIST
650
651 static gchar *mh_item_get_path(Folder *folder, FolderItem *item)
652 {
653         gchar *folder_path, *path;
654         gchar *real_path;
655         g_return_val_if_fail(folder != NULL, NULL);
656         g_return_val_if_fail(item != NULL, NULL);
657
658         folder_path = g_strdup(LOCAL_FOLDER(folder)->rootpath);
659         g_return_val_if_fail(folder_path != NULL, NULL);
660
661         /* FIXME: [W32] The code below does not correctly merge
662            relative filenames; there should be a function to handle
663            this.  */
664         if ( !is_relative_filename (folder_path) ) {
665                 if (item->path)
666                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
667                                            item->path, NULL);
668                 else
669                         path = g_strdup(folder_path);
670         } else {
671                 if (item->path)
672                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
673                                            folder_path, G_DIR_SEPARATOR_S,
674                                            item->path, NULL);
675                 else
676                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
677                                            folder_path, NULL);
678         }
679         g_free(folder_path);
680         real_path = mh_filename_from_utf8(path);
681         if (!is_dir_exist(real_path) && is_dir_exist(path)) {
682                 /* mmh, older version did put utf8 filenames instead of
683                  * the correct encoding */
684                 rename(path, real_path);
685                 folder_item_scan(item);
686         }
687
688         g_free(path);
689         return real_path;
690 }
691
692 static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent,
693                                     const gchar *name)
694 {
695         gchar *path, *real_name;
696         gchar *fullpath;
697         FolderItem *new_item;
698         gchar *mh_sequences_filename;
699         FILE *mh_sequences_file;
700
701         g_return_val_if_fail(folder != NULL, NULL);
702         g_return_val_if_fail(parent != NULL, NULL);
703         g_return_val_if_fail(name != NULL, NULL);
704
705         path = folder_item_get_path(parent);
706         if (!is_dir_exist(path)) 
707                 if (make_dir_hier(path) != 0)
708                         return NULL;
709                 
710         real_name = mh_filename_from_utf8(name);
711         fullpath = g_strconcat(path, G_DIR_SEPARATOR_S, real_name, NULL);
712         g_free(real_name);
713         g_free(path);
714
715         if (make_dir(fullpath) < 0) {
716                 g_free(fullpath);
717                 return NULL;
718         }
719
720         g_free(fullpath);
721
722         if (parent->path)
723                 path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name,
724                                    NULL);
725         else
726                 path = g_strdup(name);
727         new_item = folder_item_new(folder, name, path);
728         folder_item_append(parent, new_item);
729
730         g_free(path);
731
732         path = folder_item_get_path(new_item);
733         mh_sequences_filename = g_strconcat(path, G_DIR_SEPARATOR_S,
734                                             ".mh_sequences", NULL);
735         if ((mh_sequences_file = g_fopen(mh_sequences_filename, "a+b")) != NULL) {
736                 fclose(mh_sequences_file);
737         }
738         g_free(mh_sequences_filename);
739         g_free(path);
740
741         return new_item;
742 }
743
744 static gint mh_rename_folder(Folder *folder, FolderItem *item,
745                              const gchar *name)
746 {
747         gchar *real_name;
748         gchar *oldpath;
749         gchar *dirname;
750         gchar *newpath, *utf8newpath;
751         gchar *paths[2];
752
753         g_return_val_if_fail(folder != NULL, -1);
754         g_return_val_if_fail(item != NULL, -1);
755         g_return_val_if_fail(item->path != NULL, -1);
756         g_return_val_if_fail(name != NULL, -1);
757
758         oldpath = folder_item_get_path(item);
759         if (!is_dir_exist(oldpath))
760                 make_dir_hier(oldpath);
761
762         dirname = g_path_get_dirname(oldpath);
763         real_name = mh_filename_from_utf8(name);
764         newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, real_name, NULL);
765         g_free(real_name);
766
767         if (g_rename(oldpath, newpath) < 0) {
768                 FILE_OP_ERROR(oldpath, "rename");
769                 g_free(oldpath);
770                 g_free(newpath);
771                 return -1;
772         }
773
774         g_free(oldpath);
775         g_free(newpath);
776
777         if (strchr(item->path, G_DIR_SEPARATOR) != NULL) {
778                 dirname = g_path_get_dirname(item->path);
779                 utf8newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S,
780                                           name, NULL);
781                 g_free(dirname);
782         } else
783                 utf8newpath = g_strdup(name);
784
785         g_free(item->name);
786         item->name = g_strdup(name);
787
788         paths[0] = g_strdup(item->path);
789         paths[1] = utf8newpath;
790         g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
791                         mh_rename_folder_func, paths);
792
793         g_free(paths[0]);
794         g_free(paths[1]);
795         return 0;
796 }
797
798 static gint mh_remove_folder(Folder *folder, FolderItem *item)
799 {
800         gchar *path;
801
802         g_return_val_if_fail(folder != NULL, -1);
803         g_return_val_if_fail(item != NULL, -1);
804         g_return_val_if_fail(item->path != NULL, -1);
805
806         path = folder_item_get_path(item);
807         if (remove_dir_recursive(path) < 0) {
808                 g_warning("can't remove directory `%s'\n", path);
809                 g_free(path);
810                 return -1;
811         }
812
813         g_free(path);
814         folder_item_remove(item);
815         return 0;
816 }
817
818 static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item)
819 {
820         MsgInfo *msginfo;
821         MsgFlags flags;
822
823         g_return_val_if_fail(item != NULL, NULL);
824         g_return_val_if_fail(file != NULL, NULL);
825
826         flags.perm_flags = MSG_NEW|MSG_UNREAD;
827         flags.tmp_flags = 0;
828
829         if (folder_has_parent_of_type(item, F_QUEUE)) {
830                 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
831         } else if (folder_has_parent_of_type(item, F_DRAFT)) {
832                 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
833         }
834
835         msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
836         if (!msginfo) return NULL;
837
838         msginfo->msgnum = atoi(file);
839         msginfo->folder = item;
840
841         return msginfo;
842 }
843
844 #if 0
845 static gboolean mh_is_maildir_one(const gchar *path, const gchar *dir)
846 {
847         gchar *entry;
848         gboolean result;
849
850         entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir, NULL);
851         result = is_dir_exist(entry);
852         g_free(entry);
853
854         return result;
855 }
856
857 /*
858  * check whether PATH is a Maildir style mailbox.
859  * This is the case if the 3 subdir: new, cur, tmp are existing.
860  * This functon assumes that entry is an directory
861  */
862 static gboolean mh_is_maildir(const gchar *path)
863 {
864         return mh_is_maildir_one(path, "new") &&
865                mh_is_maildir_one(path, "cur") &&
866                mh_is_maildir_one(path, "tmp");
867 }
868 #endif
869
870 static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data)
871 {
872         FolderItem *item;
873         gchar *path;
874
875         g_return_val_if_fail(node->data != NULL, FALSE);
876
877         if (G_NODE_IS_ROOT(node))
878                 return FALSE;
879
880         item = FOLDER_ITEM(node->data);
881
882         path = folder_item_get_path(item);
883         if (!is_dir_exist(path)) {
884                 debug_print("folder '%s' not found. removing...\n", path);
885                 folder_item_remove(item);
886         }
887         g_free(path);
888
889         return FALSE;
890 }
891
892 static void mh_remove_missing_folder_items(Folder *folder)
893 {
894         g_return_if_fail(folder != NULL);
895
896         debug_print("searching missing folders...\n");
897
898         g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
899                         mh_remove_missing_folder_items_func, folder);
900 }
901
902 static void mh_scan_tree_recursive(FolderItem *item)
903 {
904         Folder *folder;
905 #ifdef G_OS_WIN32
906         GDir *dir;
907 #else
908         DIR *dp;
909         struct dirent *d;
910 #endif
911         const gchar *dir_name;
912         struct stat s;
913         gchar *real_path, *entry, *utf8entry, *utf8name;
914         gint n_msg = 0;
915
916         g_return_if_fail(item != NULL);
917         g_return_if_fail(item->folder != NULL);
918
919         folder = item->folder;
920
921         real_path = item->path ? mh_filename_from_utf8(item->path) : g_strdup(".");
922 #ifdef G_OS_WIN32
923         dir = g_dir_open(real_path, 0, NULL);
924         if (!dir) {
925                 g_warning("failed to open directory: %s\n", real_path);
926                 g_free(real_path);
927                 return;
928         }
929 #else
930         dp = opendir(real_path);
931         if (!dp) {
932                 FILE_OP_ERROR(real_path, "opendir");
933                 return;
934         }
935 #endif
936         g_free(real_path);
937
938         debug_print("scanning %s ...\n",
939                     item->path ? item->path
940                     : LOCAL_FOLDER(item->folder)->rootpath);
941         if (folder->ui_func)
942                 folder->ui_func(folder, item, folder->ui_func_data);
943
944 #ifdef G_OS_WIN32
945         while ((dir_name = g_dir_read_name(dir)) != NULL) {
946 #else
947         while ((d = readdir(dp)) != NULL) {
948                 dir_name = d->d_name;
949 #endif
950                 if (dir_name[0] == '.') continue;
951
952                 utf8name = mh_filename_to_utf8(dir_name);
953                 if (item->path)
954                         utf8entry = g_strconcat(item->path, G_DIR_SEPARATOR_S,
955                                                 utf8name, NULL);
956                 else
957                         utf8entry = g_strdup(utf8name);
958                 entry = mh_filename_from_utf8(utf8entry);
959
960                 if (
961 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
962                         d->d_type == DT_DIR ||
963                         (d->d_type == DT_UNKNOWN &&
964 #endif
965                         g_stat(entry, &s) == 0 && S_ISDIR(s.st_mode)
966 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
967                         )
968 #endif
969                    ) {
970                         FolderItem *new_item = NULL;
971                         GNode *node;
972
973 #if 0
974                         if (mh_is_maildir(entry)) {
975                                 g_free(entry);
976                                 g_free(utf8entry);
977                                 g_free(utf8name);
978                                 continue;
979                         }
980 #endif
981
982                         node = item->node;
983                         for (node = node->children; node != NULL; node = node->next) {
984                                 FolderItem *cur_item = FOLDER_ITEM(node->data);
985                                 if (!strcmp2(cur_item->path, entry)) {
986                                         new_item = cur_item;
987                                         break;
988                                 }
989                         }
990                         if (!new_item) {
991                                 debug_print("new folder '%s' found.\n", entry);
992                                 new_item = folder_item_new(folder, utf8name, utf8entry);
993                                 folder_item_append(item, new_item);
994                         }
995
996                         if (!item->path) {
997                                 if (!folder->inbox &&
998                                     !strcmp(dir_name, INBOX_DIR)) {
999                                         new_item->stype = F_INBOX;
1000                                         folder->inbox = new_item;
1001                                 } else if (!folder->outbox &&
1002                                            !strcmp(dir_name, OUTBOX_DIR)) {
1003                                         new_item->stype = F_OUTBOX;
1004                                         folder->outbox = new_item;
1005                                 } else if (!folder->draft &&
1006                                            !strcmp(dir_name, DRAFT_DIR)) {
1007                                         new_item->stype = F_DRAFT;
1008                                         folder->draft = new_item;
1009                                 } else if (!folder->queue &&
1010                                            !strcmp(dir_name, QUEUE_DIR)) {
1011                                         new_item->stype = F_QUEUE;
1012                                         folder->queue = new_item;
1013                                 } else if (!folder->trash &&
1014                                            !strcmp(dir_name, TRASH_DIR)) {
1015                                         new_item->stype = F_TRASH;
1016                                         folder->trash = new_item;
1017                                 }
1018                         }
1019
1020                         mh_scan_tree_recursive(new_item);
1021                 } else if (to_number(dir_name) > 0) n_msg++;
1022
1023                 g_free(entry);
1024                 g_free(utf8entry);
1025                 g_free(utf8name);
1026         }
1027
1028 #ifdef G_OS_WIN32
1029         g_dir_close(dir);
1030 #else
1031         closedir(dp);
1032 #endif
1033
1034         item->mtime = time(NULL);
1035
1036 /*
1037         if (item->path) {
1038                 gint new, unread, total, min, max;
1039
1040                 procmsg_get_mark_sum(item->path, &new, &unread, &total,
1041                                      &min, &max, 0);
1042                 if (n_msg > total) {
1043                         new += n_msg - total;
1044                         unread += n_msg - total;
1045                 }
1046                 item->new = new;
1047                 item->unread = unread;
1048                 item->total = n_msg;
1049         }
1050 */
1051 }
1052
1053 static gboolean mh_rename_folder_func(GNode *node, gpointer data)
1054 {
1055         FolderItem *item = node->data;
1056         gchar **paths = data;
1057         const gchar *oldpath = paths[0];
1058         const gchar *newpath = paths[1];
1059         gchar *base;
1060         gchar *new_itempath;
1061         gint oldpathlen;
1062
1063         oldpathlen = strlen(oldpath);
1064         if (strncmp(oldpath, item->path, oldpathlen) != 0) {
1065                 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
1066                 return TRUE;
1067         }
1068
1069         base = item->path + oldpathlen;
1070         while (*base == G_DIR_SEPARATOR) base++;
1071         if (*base == '\0')
1072                 new_itempath = g_strdup(newpath);
1073         else
1074                 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
1075                                            NULL);
1076         g_free(item->path);
1077         item->path = new_itempath;
1078
1079         return FALSE;
1080 }
1081
1082 static gchar *mh_filename_from_utf8(const gchar *path)
1083 {
1084         gchar *real_path = g_filename_from_utf8(path, -1, NULL, NULL, NULL);
1085
1086         if (!real_path) {
1087                 g_warning("mh_filename_from_utf8: faild to convert character set\n");
1088                 real_path = g_strdup(path);
1089         }
1090
1091         return real_path;
1092 }
1093
1094 static gchar *mh_filename_to_utf8(const gchar *path)
1095 {
1096         gchar *utf8path = g_filename_to_utf8(path, -1, NULL, NULL, NULL);
1097         if (!utf8path) {
1098                 g_warning("mh_filename_to_utf8: faild to convert character set\n");
1099                 utf8path = g_strdup(path);
1100         }
1101
1102         return utf8path;
1103 }