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