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