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