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