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