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