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