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