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