Fix python check for systems with dlopen(3) in libc (BSD family)
[claws.git] / src / mh.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
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 3 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, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24
25 #include "defs.h"
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <dirent.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <time.h>
35
36 #include "folder.h"
37 #include "folder_item_prefs.h"
38 #include "mh.h"
39 #include "procmsg.h"
40 #include "procheader.h"
41 #include "utils.h"
42 #include "codeconv.h"
43 #include "statusbar.h"
44 #include "gtkutils.h"
45 #include "timing.h"
46
47 /* Define possible missing constants for Windows. */
48 #ifdef G_OS_WIN32
49 # ifndef S_IRGRP
50 # define S_IRGRP 0
51 # define S_IWGRP 0
52 # endif
53 # ifndef S_IROTH
54 # define S_IROTH 0
55 # define S_IWOTH 0
56 # endif
57 #endif
58
59
60 static void     mh_folder_init          (Folder         *folder,
61                                          const gchar    *name,
62                                          const gchar    *path);
63
64 static Folder   *mh_folder_new          (const gchar    *name,
65                                          const gchar    *path);
66 static void     mh_folder_destroy       (Folder         *folder);
67 static gchar   *mh_fetch_msg            (Folder         *folder,
68                                          FolderItem     *item,
69                                          gint            num);
70 static MsgInfo *mh_get_msginfo          (Folder         *folder,
71                                          FolderItem     *item,
72                                          gint            num);
73 static gint     mh_add_msg              (Folder         *folder,
74                                          FolderItem     *dest,
75                                          const gchar    *file,
76                                          MsgFlags       *flags);
77 static gint     mh_add_msgs             (Folder         *folder,
78                                          FolderItem     *dest,
79                                          GSList         *file_list,
80                                          GHashTable     *relation);
81 static gint     mh_copy_msg             (Folder         *folder,
82                                          FolderItem     *dest,
83                                          MsgInfo        *msginfo);
84 static gint     mh_copy_msgs            (Folder         *folder, 
85                                          FolderItem     *dest, 
86                                          MsgInfoList    *msglist, 
87                                          GHashTable     *relation);
88 static gint     mh_remove_msg           (Folder         *folder,
89                                          FolderItem     *item,
90                                          gint            num);
91 static gint     mh_remove_msgs          (Folder         *folder, 
92                                          FolderItem     *item, 
93                                          MsgInfoList    *msglist, 
94                                          GHashTable     *relation);
95 static gint     mh_remove_all_msg       (Folder         *folder,
96                                          FolderItem     *item);
97 static gboolean mh_is_msg_changed       (Folder         *folder,
98                                          FolderItem     *item,
99                                          MsgInfo        *msginfo);
100
101 static gint     mh_get_num_list         (Folder         *folder,
102                                          FolderItem     *item, 
103                                          GSList         **list, 
104                                          gboolean       *old_uids_valid);
105 static gint     mh_scan_tree            (Folder         *folder);
106
107 static gint    mh_create_tree           (Folder         *folder);
108 static FolderItem *mh_create_folder     (Folder         *folder,
109                                          FolderItem     *parent,
110                                          const gchar    *name);
111 static gint    mh_rename_folder         (Folder         *folder,
112                                          FolderItem     *item,
113                                          const gchar    *name);
114 static gint    mh_remove_folder         (Folder         *folder,
115                                          FolderItem     *item);
116
117 static gchar   *mh_get_new_msg_filename         (FolderItem     *dest);
118
119 static MsgInfo *mh_parse_msg                    (const gchar    *file,
120                                                  FolderItem     *item);
121 static void     mh_remove_missing_folder_items  (Folder         *folder);
122 static gchar    *mh_filename_from_utf8          (const gchar    *path);
123 static gchar    *mh_filename_to_utf8            (const gchar    *path);
124 static void     mh_scan_tree_recursive          (FolderItem     *item);
125
126 static gboolean mh_rename_folder_func           (GNode          *node,
127                                                  gpointer        data);
128 static gchar   *mh_item_get_path                (Folder *folder, 
129                                                  FolderItem *item);
130
131 static gboolean mh_scan_required        (Folder         *folder,
132                                          FolderItem     *item);
133 static void mh_set_mtime                (Folder         *folder,
134                                          FolderItem *item);
135 static int mh_item_close                (Folder         *folder,
136                                          FolderItem     *item);
137 #if 0
138 static gint mh_get_flags                (Folder *folder, FolderItem *item,
139                                          MsgInfoList *msginfo_list, GHashTable *msgflags);
140 #endif
141 static void mh_write_sequences          (FolderItem     *item, gboolean remove_unseen);
142
143 static FolderClass mh_class;
144
145 FolderClass *mh_get_class(void)
146 {
147         if (mh_class.idstr == NULL) {
148                 mh_class.type = F_MH;
149                 mh_class.idstr = "mh";
150                 mh_class.uistr = "MH";
151                 mh_class.supports_server_search = FALSE;
152                 
153                 /* Folder functions */
154                 mh_class.new_folder = mh_folder_new;
155                 mh_class.destroy_folder = mh_folder_destroy;
156                 mh_class.set_xml = folder_local_set_xml;
157                 mh_class.get_xml = folder_local_get_xml;
158                 mh_class.scan_tree = mh_scan_tree;
159                 mh_class.create_tree = mh_create_tree;
160
161                 /* FolderItem functions */
162                 mh_class.item_get_path = mh_item_get_path;
163                 mh_class.create_folder = mh_create_folder;
164                 mh_class.rename_folder = mh_rename_folder;
165                 mh_class.remove_folder = mh_remove_folder;
166                 mh_class.get_num_list = mh_get_num_list;
167                 mh_class.scan_required = mh_scan_required;
168                 mh_class.set_mtime = mh_set_mtime;
169                 mh_class.close = mh_item_close;
170                 mh_class.get_flags = NULL; /*mh_get_flags */;
171
172                 /* Message functions */
173                 mh_class.get_msginfo = mh_get_msginfo;
174                 mh_class.fetch_msg = mh_fetch_msg;
175                 mh_class.add_msg = mh_add_msg;
176                 mh_class.add_msgs = mh_add_msgs;
177                 mh_class.copy_msg = mh_copy_msg;
178                 mh_class.copy_msgs = mh_copy_msgs;
179                 mh_class.search_msgs = folder_item_search_msgs_local;
180                 mh_class.remove_msg = mh_remove_msg;
181                 mh_class.remove_msgs = mh_remove_msgs;
182                 mh_class.remove_all_msg = mh_remove_all_msg;
183                 mh_class.is_msg_changed = mh_is_msg_changed;
184         }
185
186         return &mh_class;
187 }
188
189 static Folder *mh_folder_new(const gchar *name, const gchar *path)
190 {
191         Folder *folder;
192
193         folder = (Folder *)g_new0(MHFolder, 1);
194         folder->klass = &mh_class;
195         mh_folder_init(folder, name, path);
196
197         return folder;
198 }
199
200 static void mh_folder_destroy(Folder *folder)
201 {
202         folder_local_folder_destroy(LOCAL_FOLDER(folder));
203 }
204
205 static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path)
206 {
207         folder_local_folder_init(folder, name, path);
208
209 }
210
211 gboolean mh_scan_required(Folder *folder, FolderItem *item)
212 {
213         gchar *path;
214         struct stat s;
215
216         path = folder_item_get_path(item);
217         cm_return_val_if_fail(path != NULL, FALSE);
218
219         if (g_stat(path, &s) < 0) {
220                 FILE_OP_ERROR(path, "stat");
221                 g_free(path);
222                 return FALSE;
223         }
224
225         if ((s.st_mtime > item->mtime) &&
226                 (s.st_mtime - 3600 != item->mtime)) {
227                 debug_print("MH scan required, folder updated: %s (%ld > %ld)\n",
228                             path?path:"(null)",
229                             (long int) s.st_mtime,
230                             (long int) item->mtime);
231                 g_free(path);
232                 return TRUE;
233         }
234
235         debug_print("MH scan not required: %s (%ld <= %ld)\n",
236                     path?path:"(null)",
237                     (long int) s.st_mtime,
238                     (long int) item->mtime);
239         g_free(path);
240         return FALSE;
241 }
242
243 static void mh_get_last_num(Folder *folder, FolderItem *item)
244 {
245         gchar *path;
246         DIR *dp;
247         struct dirent *d;
248         gint max = 0;
249         gint num;
250
251         cm_return_if_fail(item != NULL);
252
253         debug_print("mh_get_last_num(): Scanning %s ...\n", item->path?item->path:"(null)");
254
255         path = folder_item_get_path(item);
256         cm_return_if_fail(path != NULL);
257         if (change_dir(path) < 0) {
258                 g_free(path);
259                 return;
260         }
261         g_free(path);
262
263         if ((dp = opendir(".")) == NULL) {
264                 FILE_OP_ERROR(item->path, "opendir");
265                 return;
266         }
267
268         while ((d = readdir(dp)) != NULL) {
269                 if ((num = to_number(d->d_name)) > 0 &&
270                     dirent_is_regular_file(d)) {
271                         if (max < num)
272                                 max = num;
273                 }
274                 if (num % 2000 == 0)
275                         GTK_EVENTS_FLUSH();
276         }
277         closedir(dp);
278
279         debug_print("Last number in dir %s = %d\n", item->path?item->path:"(null)", max);
280         item->last_num = max;
281 }
282
283 gint mh_get_num_list(Folder *folder, FolderItem *item, GSList **list, gboolean *old_uids_valid)
284 {
285
286         gchar *path;
287         DIR *dp;
288         struct dirent *d;
289         gint num, nummsgs = 0;
290
291         cm_return_val_if_fail(item != NULL, -1);
292
293         debug_print("mh_get_num_list(): Scanning %s ...\n", item->path?item->path:"(null)");
294
295         *old_uids_valid = TRUE;
296
297         path = folder_item_get_path(item);
298         cm_return_val_if_fail(path != NULL, -1);
299         if (change_dir(path) < 0) {
300                 g_free(path);
301                 return -1;
302         }
303         g_free(path);
304
305         if ((dp = opendir(".")) == NULL) {
306                 FILE_OP_ERROR(item->path, "opendir");
307                 return -1;
308         }
309
310         while ((d = readdir(dp)) != NULL) {
311                 if ((num = to_number(d->d_name)) > 0) {
312                         *list = g_slist_prepend(*list, GINT_TO_POINTER(num));
313                         nummsgs++;
314                 }
315         }
316         closedir(dp);
317
318         mh_set_mtime(folder, item);
319         return nummsgs;
320 }
321
322 static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num)
323 {
324         gchar *path;
325         gchar *file;
326
327         cm_return_val_if_fail(item != NULL, NULL);
328         cm_return_val_if_fail(num > 0, NULL);
329
330         path = folder_item_get_path(item);
331         file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
332
333         if (!is_file_exist(file)) {
334                 g_free(file);
335                 g_free(path);
336                 return NULL;
337         }
338         g_free(path);
339         return file;
340 }
341
342 static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num)
343 {
344         MsgInfo *msginfo;
345         gchar *file;
346
347         cm_return_val_if_fail(item != NULL, NULL);
348         if (num <= 0)
349                 return NULL;
350
351         file = mh_fetch_msg(folder, item, num);
352         if (!file) return NULL;
353
354         msginfo = mh_parse_msg(file, item);
355         if (msginfo)
356                 msginfo->msgnum = num;
357
358         g_free(file);
359
360         return msginfo;
361 }
362
363 static gchar *mh_get_new_msg_filename(FolderItem *dest)
364 {
365         gchar *destfile;
366         gchar *destpath;
367
368         destpath = folder_item_get_path(dest);
369         cm_return_val_if_fail(destpath != NULL, NULL);
370
371         if (!is_dir_exist(destpath))
372                 make_dir_hier(destpath);
373
374         for (;;) {
375                 destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
376                                            dest->last_num + 1);
377                 if (is_file_entry_exist(destfile)) {
378                         dest->last_num++;
379                         g_free(destfile);
380                 } else
381                         break;
382         }
383
384         g_free(destpath);
385
386         return destfile;
387 }
388
389 static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file, MsgFlags *flags)
390 {
391         gint ret;
392         GSList file_list;
393         MsgFileInfo fileinfo;
394
395         cm_return_val_if_fail(file != NULL, -1);
396
397         fileinfo.msginfo = NULL;
398         fileinfo.file = (gchar *)file;
399         fileinfo.flags = flags;
400         file_list.data = &fileinfo;
401         file_list.next = NULL;
402
403         ret = mh_add_msgs(folder, dest, &file_list, NULL);
404         return ret;
405
406  
407 static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, 
408                  GHashTable *relation)
409
410         gchar *destfile;
411         GSList *cur;
412         MsgFileInfo *fileinfo;
413
414         cm_return_val_if_fail(dest != NULL, -1);
415         cm_return_val_if_fail(file_list != NULL, -1);
416
417         if (dest->last_num < 0) {
418                 mh_get_last_num(folder, dest);
419                 if (dest->last_num < 0) return -1;
420         }
421
422         for (cur = file_list; cur != NULL; cur = cur->next) {
423                 fileinfo = (MsgFileInfo *)cur->data;
424
425                 destfile = mh_get_new_msg_filename(dest);
426                 if (destfile == NULL) return -1;
427
428 #ifdef G_OS_UNIX
429                 if (link(fileinfo->file, destfile) < 0) {
430 #endif
431                         if (copy_file(fileinfo->file, destfile, TRUE) < 0) {
432                                 g_warning(_("can't copy message %s to %s\n"),
433                                           fileinfo->file, destfile);
434                                 g_free(destfile);
435                                 return -1;
436                         }
437 #ifdef G_OS_UNIX
438                 }
439 #endif
440
441                 if (relation != NULL)
442                         g_hash_table_insert(relation, fileinfo, GINT_TO_POINTER(dest->last_num + 1));
443                 g_free(destfile);
444                 dest->last_num++;
445         }
446         mh_write_sequences(dest, TRUE);
447         return dest->last_num;
448 }
449
450 static gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
451 {
452         GSList msglist;
453
454         cm_return_val_if_fail(msginfo != NULL, -1);
455
456         msglist.data = msginfo;
457         msglist.next = NULL;
458
459         return mh_copy_msgs(folder, dest, &msglist, NULL);      
460 }
461
462 static gint mh_copy_msgs(Folder *folder, FolderItem *dest, MsgInfoList *msglist, 
463                          GHashTable *relation)
464 {
465         gboolean dest_need_scan = FALSE;
466         gboolean src_need_scan = FALSE;
467         FolderItem *src = NULL;
468         gchar *srcfile;
469         gchar *destfile;
470         FolderItemPrefs *prefs;
471         MsgInfo *msginfo = NULL;
472         MsgInfoList *cur = NULL;
473         gint curnum = 0, total = 0;
474         gchar *srcpath = NULL;
475         gboolean full_fetch = FALSE;
476         time_t last_dest_mtime = (time_t)0;
477         time_t last_src_mtime = (time_t)0;
478
479         cm_return_val_if_fail(dest != NULL, -1);
480         cm_return_val_if_fail(msglist != NULL, -1);
481         
482         msginfo = (MsgInfo *)msglist->data;
483
484         cm_return_val_if_fail(msginfo != NULL, -1);
485
486         if (msginfo->folder == dest) {
487                 g_warning("the src folder is identical to the dest.\n");
488                 return -1;
489         }
490
491         if (msginfo->folder->folder != dest->folder)
492                 full_fetch = TRUE;
493         
494         if (FOLDER_TYPE(msginfo->folder->folder) == F_MH) {
495                 src = msginfo->folder;
496         }
497
498         if (dest->last_num < 0) {
499                 mh_get_last_num(folder, dest);
500                 if (dest->last_num < 0) return -1;
501         }
502
503         prefs = dest->prefs;
504
505         srcpath = folder_item_get_path(msginfo->folder);
506
507         dest_need_scan = mh_scan_required(dest->folder, dest);
508         last_dest_mtime = dest->mtime;
509
510         if (src) {
511                 src_need_scan = mh_scan_required(src->folder, src);
512                 last_src_mtime = src->mtime;
513         }
514
515         total = g_slist_length(msglist);
516         if (total > 100) {
517                 if (MSG_IS_MOVE(msginfo->flags))
518                         statusbar_print_all(_("Moving messages..."));
519                 else
520                         statusbar_print_all(_("Copying messages..."));
521         }
522         for (cur = msglist; cur; cur = cur->next) {
523                 msginfo = (MsgInfo *)cur->data;
524                 if (!msginfo) {
525                         goto err_reset_status;
526                 }
527                 if (!full_fetch) {
528                         srcfile = g_strconcat(srcpath, 
529                                 G_DIR_SEPARATOR_S, 
530                                 itos(msginfo->msgnum), NULL);
531                 } else {
532                         srcfile = procmsg_get_message_file(msginfo);
533                 }
534                 if (!srcfile) {
535                         goto err_reset_status;
536                 }
537                 destfile = mh_get_new_msg_filename(dest);
538                 if (!destfile) {
539                         g_free(srcfile);
540                         goto err_reset_status;
541                 }
542
543                 if (total > 100) {
544                         statusbar_progress_all(curnum, total, 100);
545                         if (curnum % 100 == 0)
546                                 GTK_EVENTS_FLUSH();
547                         curnum++;
548                 }
549
550                 debug_print("Copying message %s%c%d to %s ...\n",
551                             msginfo->folder->path, G_DIR_SEPARATOR,
552                             msginfo->msgnum, dest->path);
553
554
555                 if (MSG_IS_MOVE(msginfo->flags)) {
556                         msginfo->flags.tmp_flags &= ~MSG_MOVE_DONE;
557                         if (move_file(srcfile, destfile, TRUE) < 0) {
558                                 FILE_OP_ERROR(srcfile, "move");
559                                 if (copy_file(srcfile, destfile, TRUE) < 0) {
560                                         FILE_OP_ERROR(srcfile, "copy");
561                                         g_free(srcfile);
562                                         g_free(destfile);
563                                         goto err_reset_status;
564                                 }
565                         } else {
566                                 /* say unlinking's not necessary */
567                                 msginfo->flags.tmp_flags |= MSG_MOVE_DONE;
568                         }
569                 } else if (copy_file(srcfile, destfile, TRUE) < 0) {
570                         FILE_OP_ERROR(srcfile, "copy");
571                         g_free(srcfile);
572                         g_free(destfile);
573                         goto err_reset_status;
574                 } 
575                 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
576                         if (chmod(destfile, prefs->folder_chmod) < 0)
577                                 FILE_OP_ERROR(destfile, "chmod");
578                 }
579                 if (relation) {
580                         if (g_hash_table_lookup(relation, msginfo) != NULL)
581                                 g_warning("already in : %p", msginfo);
582                         
583                         g_hash_table_insert(relation, msginfo, GINT_TO_POINTER(dest->last_num+1));
584                 }
585                 g_free(srcfile);
586                 g_free(destfile);
587                 dest->last_num++;
588         }
589
590         g_free(srcpath);
591         mh_write_sequences(dest, TRUE);
592
593         if (dest->mtime == last_dest_mtime && !dest_need_scan) {
594                 mh_set_mtime(folder, dest);
595         }
596
597         if (src && src->mtime == last_src_mtime && !src_need_scan) {
598                 mh_set_mtime(folder, src);
599         }
600
601         if (total > 100) {
602                 statusbar_progress_all(0,0,0);
603                 statusbar_pop_all();
604         }
605         return dest->last_num;
606 err_reset_status:
607         g_free(srcpath);
608         mh_write_sequences(dest, TRUE);
609         if (total > 100) {
610                 statusbar_progress_all(0,0,0);
611                 statusbar_pop_all();
612         }
613         return -1;
614
615 }
616
617 static gint mh_remove_msg(Folder *folder, FolderItem *item, gint num)
618 {
619         gboolean need_scan = FALSE;
620         time_t last_mtime = (time_t)0;
621         gchar *file;
622
623         cm_return_val_if_fail(item != NULL, -1);
624
625         file = mh_fetch_msg(folder, item, num);
626         cm_return_val_if_fail(file != NULL, -1);
627
628         need_scan = mh_scan_required(folder, item);
629         last_mtime = item->mtime;
630
631         if (claws_unlink(file) < 0) {
632                 FILE_OP_ERROR(file, "unlink");
633                 g_free(file);
634                 return -1;
635         }
636
637         if (item->mtime == last_mtime && !need_scan) {
638                 mh_set_mtime(folder, item);
639         }
640         g_free(file);
641         return 0;
642 }
643
644 static gint mh_remove_msgs(Folder *folder, FolderItem *item, 
645                     MsgInfoList *msglist, GHashTable *relation)
646 {
647         gboolean need_scan = FALSE;
648         gchar *path, *file;
649         time_t last_mtime = (time_t)0;
650         MsgInfoList *cur;
651         gint total = 0, curnum = 0;
652
653         cm_return_val_if_fail(item != NULL, -1);
654
655         path = folder_item_get_path(item);
656         
657         need_scan = mh_scan_required(folder, item);
658         last_mtime = item->mtime;
659
660         total = g_slist_length(msglist);
661         if (total > 100) {
662                 statusbar_print_all(_("Deleting messages..."));
663         }
664
665         for (cur = msglist; cur; cur = cur->next) {
666                 MsgInfo *msginfo = (MsgInfo *)cur->data;
667                 if (msginfo == NULL)
668                         continue;
669                 if (MSG_IS_MOVE(msginfo->flags) && MSG_IS_MOVE_DONE(msginfo->flags)) {
670                         msginfo->flags.tmp_flags &= ~MSG_MOVE_DONE;
671                         continue;
672                 }
673                 if (total > 100) {
674                         statusbar_progress_all(curnum, total, 100);
675                         if (curnum % 100 == 0)
676                                 GTK_EVENTS_FLUSH();
677                         curnum++;
678                 }
679
680                 file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(msginfo->msgnum), NULL);
681                 if (file == NULL)
682                         continue;
683                 
684                 if (claws_unlink(file) < 0) {
685                         g_free(file);
686                         continue;
687                 }
688                 
689                 g_free(file);
690         }
691
692         if (total > 100) {
693                 statusbar_progress_all(0,0,0);
694                 statusbar_pop_all();
695         }
696         if (item->mtime == last_mtime && !need_scan) {
697                 mh_set_mtime(folder, item);
698         }
699
700         g_free(path);
701         return 0;
702 }
703
704 static gint mh_remove_all_msg(Folder *folder, FolderItem *item)
705 {
706         gchar *path;
707         gint val;
708
709         cm_return_val_if_fail(item != NULL, -1);
710
711         path = folder_item_get_path(item);
712         cm_return_val_if_fail(path != NULL, -1);
713         val = remove_all_numbered_files(path);
714         g_free(path);
715
716         mh_write_sequences(item, TRUE);
717
718         return val;
719 }
720
721 static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item,
722                                   MsgInfo *msginfo)
723 {
724         struct stat s;
725
726         if (g_stat(itos(msginfo->msgnum), &s) < 0 ||
727             msginfo->size  != s.st_size || (
728                 (msginfo->mtime - s.st_mtime != 0) &&
729                 (msginfo->mtime - s.st_mtime != 3600) &&
730                 (msginfo->mtime - s.st_mtime != -3600)))
731                 return TRUE;
732
733         return FALSE;
734 }
735
736 static gint mh_scan_tree(Folder *folder)
737 {
738         FolderItem *item;
739         gchar *rootpath;
740
741         cm_return_val_if_fail(folder != NULL, -1);
742
743         if (!folder->node) {
744                 item = folder_item_new(folder, folder->name, NULL);
745                 item->folder = folder;
746                 folder->node = item->node = g_node_new(item);
747         } else
748                 item = FOLDER_ITEM(folder->node->data);
749
750         rootpath = folder_item_get_path(item);
751         if (change_dir(rootpath) < 0) {
752                 g_free(rootpath);
753                 return -1;
754         }
755         g_free(rootpath);
756
757         mh_create_tree(folder);
758         mh_remove_missing_folder_items(folder);
759         mh_scan_tree_recursive(item);
760
761         return 0;
762 }
763
764 #define MAKE_DIR_IF_NOT_EXIST(dir) \
765 { \
766         if (!is_dir_exist(dir)) { \
767                 if (is_file_exist(dir)) { \
768                         g_warning("File `%s' already exists.\n" \
769                                     "Can't create folder.", dir); \
770                         return -1; \
771                 } \
772                 if (make_dir_hier(dir) < 0) \
773                         return -1; \
774         } \
775 }
776
777 static gint mh_create_tree(Folder *folder)
778 {
779         gchar *rootpath, *f;
780
781         cm_return_val_if_fail(folder != NULL, -1);
782
783         CHDIR_RETURN_VAL_IF_FAIL(get_mail_base_dir(), -1);
784         rootpath = LOCAL_FOLDER(folder)->rootpath;
785         MAKE_DIR_IF_NOT_EXIST(rootpath);
786         CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1);
787
788         /* Create special directories as needed */
789         if (folder->inbox != NULL &&
790                         folder->inbox->path != NULL)
791                 f = folder->inbox->path;
792         else
793                 f = INBOX_DIR;
794         MAKE_DIR_IF_NOT_EXIST(f);
795
796         if (folder->outbox != NULL &&
797                         folder->outbox->path != NULL)
798                 f = folder->outbox->path;
799         else
800                 f = OUTBOX_DIR;
801         MAKE_DIR_IF_NOT_EXIST(f);
802
803         if (folder->draft != NULL &&
804                         folder->draft->path != NULL)
805                 f = folder->draft->path;
806         else
807                 f = DRAFT_DIR;
808         MAKE_DIR_IF_NOT_EXIST(f);
809
810         if (folder->queue != NULL &&
811                         folder->queue->path != NULL)
812                 f = folder->queue->path;
813         else
814                 f = QUEUE_DIR;
815         MAKE_DIR_IF_NOT_EXIST(f);
816
817         if (folder->trash != NULL &&
818                         folder->trash->path != NULL)
819                 f = folder->trash->path;
820         else
821                 f = TRASH_DIR;
822         MAKE_DIR_IF_NOT_EXIST(f);
823
824         return 0;
825 }
826
827 #undef MAKE_DIR_IF_NOT_EXIST
828
829 static gchar *mh_item_get_path(Folder *folder, FolderItem *item)
830 {
831         gchar *folder_path, *path;
832         gchar *real_path;
833         cm_return_val_if_fail(folder != NULL, NULL);
834         cm_return_val_if_fail(item != NULL, NULL);
835
836         folder_path = g_strdup(LOCAL_FOLDER(folder)->rootpath);
837         cm_return_val_if_fail(folder_path != NULL, NULL);
838
839         /* FIXME: [W32] The code below does not correctly merge
840            relative filenames; there should be a function to handle
841            this.  */
842         if ( !is_relative_filename (folder_path) ) {
843                 if (item->path)
844                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
845                                            item->path, NULL);
846                 else
847                         path = g_strdup(folder_path);
848         } else {
849                 if (item->path)
850                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
851                                            folder_path, G_DIR_SEPARATOR_S,
852                                            item->path, NULL);
853                 else
854                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
855                                            folder_path, NULL);
856         }
857         g_free(folder_path);
858         real_path = mh_filename_from_utf8(path);
859         if (!is_dir_exist(real_path) && is_dir_exist(path)) {
860                 /* mmh, older version did put utf8 filenames instead of
861                  * the correct encoding */
862                 g_rename(path, real_path);
863                 folder_item_scan(item);
864         }
865
866         g_free(path);
867         return real_path;
868 }
869
870 static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent,
871                                     const gchar *name)
872 {
873         gchar *path, *real_name;
874         gchar *fullpath;
875         FolderItem *new_item;
876         gchar *mh_sequences_filename;
877         FILE *mh_sequences_file;
878
879         cm_return_val_if_fail(folder != NULL, NULL);
880         cm_return_val_if_fail(parent != NULL, NULL);
881         cm_return_val_if_fail(name != NULL, NULL);
882
883         path = folder_item_get_path(parent);
884         if (!is_dir_exist(path)) 
885                 if (make_dir_hier(path) != 0)
886                         return NULL;
887                 
888         real_name = mh_filename_from_utf8(name);
889         fullpath = g_strconcat(path, G_DIR_SEPARATOR_S, real_name, NULL);
890         g_free(real_name);
891         g_free(path);
892
893         if (make_dir(fullpath) < 0) {
894                 g_free(fullpath);
895                 return NULL;
896         }
897
898         g_free(fullpath);
899
900         if (parent->path)
901                 path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name,
902                                    NULL);
903         else
904                 path = g_strdup(name);
905         new_item = folder_item_new(folder, name, path);
906         folder_item_append(parent, new_item);
907
908         g_free(path);
909
910         path = folder_item_get_path(new_item);
911         mh_sequences_filename = g_strconcat(path, G_DIR_SEPARATOR_S,
912                                             ".mh_sequences", NULL);
913         if ((mh_sequences_file = g_fopen(mh_sequences_filename, "a+b")) != NULL) {
914                 fclose(mh_sequences_file);
915         }
916         g_free(mh_sequences_filename);
917         g_free(path);
918
919         return new_item;
920 }
921
922 static gint mh_rename_folder(Folder *folder, FolderItem *item,
923                              const gchar *name)
924 {
925         gchar *real_name;
926         gchar *oldpath;
927         gchar *dirname;
928         gchar *newpath, *utf8newpath;
929         gchar *paths[2];
930
931         cm_return_val_if_fail(folder != NULL, -1);
932         cm_return_val_if_fail(item != NULL, -1);
933         cm_return_val_if_fail(item->path != NULL, -1);
934         cm_return_val_if_fail(name != NULL, -1);
935
936         oldpath = folder_item_get_path(item);
937         if (!is_dir_exist(oldpath))
938                 make_dir_hier(oldpath);
939
940         dirname = g_path_get_dirname(oldpath);
941         real_name = mh_filename_from_utf8(name);
942         newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, real_name, NULL);
943         g_free(real_name);
944
945         if (g_rename(oldpath, newpath) < 0) {
946                 FILE_OP_ERROR(oldpath, "rename");
947                 g_free(oldpath);
948                 g_free(newpath);
949                 return -1;
950         }
951
952         g_free(oldpath);
953         g_free(newpath);
954
955         if (strchr(item->path, G_DIR_SEPARATOR) != NULL) {
956                 dirname = g_path_get_dirname(item->path);
957                 utf8newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S,
958                                           name, NULL);
959                 g_free(dirname);
960         } else
961                 utf8newpath = g_strdup(name);
962
963         g_free(item->name);
964         item->name = g_strdup(name);
965
966         paths[0] = g_strdup(item->path);
967         paths[1] = utf8newpath;
968         g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
969                         mh_rename_folder_func, paths);
970
971         g_free(paths[0]);
972         g_free(paths[1]);
973         return 0;
974 }
975
976 static gint mh_remove_folder(Folder *folder, FolderItem *item)
977 {
978         gchar *path;
979
980         cm_return_val_if_fail(folder != NULL, -1);
981         cm_return_val_if_fail(item != NULL, -1);
982         cm_return_val_if_fail(item->path != NULL, -1);
983
984         path = folder_item_get_path(item);
985         if (remove_dir_recursive(path) < 0) {
986                 g_warning("can't remove directory `%s'\n", path);
987                 g_free(path);
988                 return -1;
989         }
990
991         g_free(path);
992         folder_item_remove(item);
993         return 0;
994 }
995
996 static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item)
997 {
998         MsgInfo *msginfo;
999         MsgFlags flags;
1000
1001         cm_return_val_if_fail(item != NULL, NULL);
1002         cm_return_val_if_fail(file != NULL, NULL);
1003
1004         flags.perm_flags = MSG_NEW|MSG_UNREAD;
1005         flags.tmp_flags = 0;
1006
1007         if (folder_has_parent_of_type(item, F_QUEUE)) {
1008                 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
1009         } else if (folder_has_parent_of_type(item, F_DRAFT)) {
1010                 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
1011         }
1012
1013         msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
1014         if (!msginfo) return NULL;
1015
1016         msginfo->msgnum = atoi(file);
1017         msginfo->folder = item;
1018
1019         return msginfo;
1020 }
1021
1022 static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data)
1023 {
1024         FolderItem *item;
1025         gchar *path;
1026
1027         cm_return_val_if_fail(node->data != NULL, FALSE);
1028
1029         if (G_NODE_IS_ROOT(node))
1030                 return FALSE;
1031
1032         item = FOLDER_ITEM(node->data);
1033
1034         path = folder_item_get_path(item);
1035         if (!is_dir_exist(path)) {
1036                 debug_print("folder '%s' not found. removing...\n", path?path:"(null)");
1037                 folder_item_remove(item);
1038         }
1039         g_free(path);
1040
1041         return FALSE;
1042 }
1043
1044 static void mh_remove_missing_folder_items(Folder *folder)
1045 {
1046         cm_return_if_fail(folder != NULL);
1047
1048         debug_print("searching missing folders...\n");
1049
1050         g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
1051                         mh_remove_missing_folder_items_func, folder);
1052 }
1053
1054 static void mh_scan_tree_recursive(FolderItem *item)
1055 {
1056         Folder *folder;
1057 #ifdef G_OS_WIN32
1058         GDir *dir;
1059 #else
1060         DIR *dp;
1061         struct dirent *d;
1062 #endif
1063         const gchar *dir_name;
1064         struct stat s;
1065         gchar *real_path, *entry, *utf8entry, *utf8name;
1066         gint n_msg = 0;
1067
1068         cm_return_if_fail(item != NULL);
1069         cm_return_if_fail(item->folder != NULL);
1070
1071         folder = item->folder;
1072
1073         real_path = item->path ? mh_filename_from_utf8(item->path) : g_strdup(".");
1074 #ifdef G_OS_WIN32
1075         dir = g_dir_open(real_path, 0, NULL);
1076         if (!dir) {
1077                 g_warning("failed to open directory: %s\n", real_path);
1078                 g_free(real_path);
1079                 return;
1080         }
1081 #else
1082         dp = opendir(real_path);
1083         if (!dp) {
1084                 FILE_OP_ERROR(real_path, "opendir");
1085                 return;
1086         }
1087 #endif
1088         g_free(real_path);
1089
1090         debug_print("scanning %s ...\n",
1091                     item->path ? item->path
1092                     : LOCAL_FOLDER(item->folder)->rootpath);
1093         if (folder->ui_func)
1094                 folder->ui_func(folder, item, folder->ui_func_data);
1095
1096 #ifdef G_OS_WIN32
1097         while ((dir_name = g_dir_read_name(dir)) != NULL) {
1098 #else
1099         while ((d = readdir(dp)) != NULL) {
1100                 dir_name = d->d_name;
1101 #endif
1102                 if (dir_name[0] == '.') continue;
1103
1104                 utf8name = mh_filename_to_utf8(dir_name);
1105                 if (item->path)
1106                         utf8entry = g_strconcat(item->path, G_DIR_SEPARATOR_S,
1107                                                 utf8name, NULL);
1108                 else
1109                         utf8entry = g_strdup(utf8name);
1110                 entry = mh_filename_from_utf8(utf8entry);
1111
1112                 if (
1113 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
1114                         d->d_type == DT_DIR ||
1115                         (d->d_type == DT_UNKNOWN &&
1116 #endif
1117                         g_stat(entry, &s) == 0 && S_ISDIR(s.st_mode)
1118 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
1119                         )
1120 #endif
1121                    ) {
1122                         FolderItem *new_item = NULL;
1123                         GNode *node;
1124
1125                         node = item->node;
1126                         for (node = node->children; node != NULL; node = node->next) {
1127                                 FolderItem *cur_item = FOLDER_ITEM(node->data);
1128                                 gchar *curpath = mh_filename_from_utf8(cur_item->path);
1129                                 if (!strcmp2(curpath, entry)) {
1130                                         new_item = cur_item;
1131                                         g_free(curpath);
1132                                         break;
1133                                 }
1134                                 g_free(curpath);
1135                         }
1136                         if (!new_item) {
1137                                 debug_print("new folder '%s' found.\n", entry);
1138                                 new_item = folder_item_new(folder, utf8name, utf8entry);
1139                                 folder_item_append(item, new_item);
1140                         }
1141
1142                         if (!item->path) {
1143                                 if (!folder->inbox &&
1144                                     !strcmp(dir_name, INBOX_DIR)) {
1145                                         new_item->stype = F_INBOX;
1146                                         folder->inbox = new_item;
1147                                 } else if (!folder->outbox &&
1148                                            !strcmp(dir_name, OUTBOX_DIR)) {
1149                                         new_item->stype = F_OUTBOX;
1150                                         folder->outbox = new_item;
1151                                 } else if (!folder->draft &&
1152                                            !strcmp(dir_name, DRAFT_DIR)) {
1153                                         new_item->stype = F_DRAFT;
1154                                         folder->draft = new_item;
1155                                 } else if (!folder->queue &&
1156                                            !strcmp(dir_name, QUEUE_DIR)) {
1157                                         new_item->stype = F_QUEUE;
1158                                         folder->queue = new_item;
1159                                 } else if (!folder->trash &&
1160                                            !strcmp(dir_name, TRASH_DIR)) {
1161                                         new_item->stype = F_TRASH;
1162                                         folder->trash = new_item;
1163                                 }
1164                         }
1165
1166                         mh_scan_tree_recursive(new_item);
1167                 } else if (to_number(dir_name) > 0) n_msg++;
1168
1169                 g_free(entry);
1170                 g_free(utf8entry);
1171                 g_free(utf8name);
1172         }
1173
1174 #ifdef G_OS_WIN32
1175         g_dir_close(dir);
1176 #else
1177         closedir(dp);
1178 #endif
1179
1180         mh_set_mtime(folder, item);
1181 }
1182
1183 static gboolean mh_rename_folder_func(GNode *node, gpointer data)
1184 {
1185         FolderItem *item = node->data;
1186         gchar **paths = data;
1187         const gchar *oldpath = paths[0];
1188         const gchar *newpath = paths[1];
1189         gchar *base;
1190         gchar *new_itempath;
1191         gint oldpathlen;
1192
1193         oldpathlen = strlen(oldpath);
1194         if (strncmp(oldpath, item->path, oldpathlen) != 0) {
1195                 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
1196                 return TRUE;
1197         }
1198
1199         base = item->path + oldpathlen;
1200         while (*base == G_DIR_SEPARATOR) base++;
1201         if (*base == '\0')
1202                 new_itempath = g_strdup(newpath);
1203         else
1204                 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
1205                                            NULL);
1206         g_free(item->path);
1207         item->path = new_itempath;
1208
1209         return FALSE;
1210 }
1211
1212 static gchar *mh_filename_from_utf8(const gchar *path)
1213 {
1214         gchar *real_path = g_filename_from_utf8(path, -1, NULL, NULL, NULL);
1215
1216         if (!real_path) {
1217                 g_warning("mh_filename_from_utf8: failed to convert character set\n");
1218                 real_path = g_strdup(path);
1219         }
1220
1221         return real_path;
1222 }
1223
1224 static gchar *mh_filename_to_utf8(const gchar *path)
1225 {
1226         gchar *utf8path = g_filename_to_utf8(path, -1, NULL, NULL, NULL);
1227         if (!utf8path) {
1228                 g_warning("mh_filename_to_utf8: failed to convert character set\n");
1229                 utf8path = g_strdup(path);
1230         }
1231
1232         return utf8path;
1233 }
1234
1235 static gint sort_cache_list_by_msgnum(gconstpointer a, gconstpointer b)
1236 {
1237         MsgInfo *msginfo_a = (MsgInfo *) a;
1238         MsgInfo *msginfo_b = (MsgInfo *) b;
1239
1240         return (msginfo_a->msgnum - msginfo_b->msgnum);
1241 }
1242
1243 static gchar *get_unseen_seq_name(void)
1244 {
1245         static gchar *seq_name = NULL;
1246         if (!seq_name) {
1247                 gchar buf[BUFFSIZE];
1248                 gchar *tmp;
1249                 gchar *profile_path = g_strconcat(
1250                         get_home_dir(), G_DIR_SEPARATOR_S,
1251                         ".mh_profile", NULL);
1252                 FILE *fp = g_fopen(profile_path, "r");
1253                 if (fp) {
1254                         while (fgets(buf, sizeof(buf), fp) != NULL) {
1255                                 if (!strncmp(buf, "Unseen-Sequence:", strlen("Unseen-Sequence:"))) {
1256                                         gchar *seq_tmp = buf+strlen("Unseen-Sequence:");
1257                                         while (*seq_tmp == ' ')
1258                                                 seq_tmp++;
1259                                         seq_name = g_strdup(seq_tmp);
1260                                         seq_name = strretchomp(seq_name);
1261                                         break;
1262                                 }
1263                         }
1264                         fclose(fp);
1265                 }
1266                 if (!seq_name)
1267                         seq_name = g_strdup("unseen");
1268                 tmp = g_strdup_printf("%s:", seq_name);
1269                 g_free(seq_name);
1270                 seq_name = tmp;
1271         }
1272         return seq_name;        
1273 }
1274
1275 static void mh_write_sequences(FolderItem *item, gboolean remove_unseen)
1276 {
1277         gchar *mh_sequences_old, *mh_sequences_new;
1278         FILE *mh_sequences_old_fp, *mh_sequences_new_fp;
1279         gchar buf[BUFFSIZE];
1280         gchar *path = NULL;
1281         gboolean err = FALSE;
1282         START_TIMING("");
1283
1284         if (!item)
1285                 return;
1286         
1287         path = folder_item_get_path(item);
1288
1289         mh_sequences_old = g_strconcat(path, G_DIR_SEPARATOR_S,
1290                                             ".mh_sequences", NULL);
1291         mh_sequences_new = g_strconcat(path, G_DIR_SEPARATOR_S,
1292                                             ".mh_sequences.new", NULL);
1293         if ((mh_sequences_new_fp = g_fopen(mh_sequences_new, "w+b")) != NULL) {
1294                 GSList *msglist = folder_item_get_msg_list(item);
1295                 GSList *cur;
1296                 MsgInfo *info = NULL;
1297                 gint start = -1, end = -1;
1298                 gchar *sequence = g_strdup("");
1299                 gint seq_len = 0;
1300                 msglist = g_slist_sort(msglist, sort_cache_list_by_msgnum);
1301                 cur = msglist;
1302                 
1303                 /* write the unseen sequence if we don't have to scrap it */
1304                 if (!remove_unseen) do {
1305                         info = (MsgInfo *)(cur ? cur->data:NULL);
1306                         if (info && (MSG_IS_UNREAD(info->flags) || MSG_IS_NEW(info->flags))) {
1307                                 if (start < 0)
1308                                         start = end = info->msgnum;
1309                                 else
1310                                         end = info->msgnum;
1311                         } else {
1312                                 if (start > 0 && end > 0) {
1313                                         gchar tmp[32];
1314                                         gint tmp_len = 0;
1315                                         if (start != end)
1316                                                 snprintf(tmp, 31, " %d-%d", start, end);
1317                                         else
1318                                                 snprintf(tmp, 31, " %d", start);
1319                                         
1320                                         tmp_len = strlen(tmp);
1321                                         sequence = g_realloc(sequence, seq_len+tmp_len+1);
1322                                         strcpy(sequence+seq_len, tmp);
1323                                         seq_len += tmp_len;
1324
1325                                         start = end = -1;
1326                                 }
1327                         }
1328                         cur = cur ? cur->next:NULL;
1329                 } while (cur || (start > 0 && end > 0));
1330                 if (sequence && *sequence) {
1331                         if (fprintf(mh_sequences_new_fp, "%s%s\n", 
1332                                         get_unseen_seq_name(), sequence) < 0)
1333                                 err = TRUE;
1334                         else
1335                                 debug_print("wrote unseen sequence: '%s%s'\n", 
1336                                         get_unseen_seq_name(), sequence);
1337                 }
1338                 /* rewrite the rest of the file */
1339                 if ((mh_sequences_old_fp = g_fopen(mh_sequences_old, "r+b")) != NULL) {
1340                         while (fgets(buf, sizeof(buf), mh_sequences_old_fp) != NULL) {
1341                                 if (strncmp(buf, get_unseen_seq_name(), strlen(get_unseen_seq_name())))
1342                                         if (fprintf(mh_sequences_new_fp, "%s", buf) < 0) {
1343                                                 err = TRUE;
1344                                                 break;
1345                                         }
1346                         }
1347                         fclose(mh_sequences_old_fp);
1348                 }
1349                 
1350                 fflush(mh_sequences_new_fp);
1351 #if 0
1352                 fsync(fileno(mh_sequences_new_fp));
1353 #endif
1354                 if (fclose(mh_sequences_new_fp) == EOF)
1355                         err = TRUE;
1356
1357                 if (!err)
1358                         g_rename(mh_sequences_new, mh_sequences_old);
1359                 g_free(sequence);
1360                 procmsg_msg_list_free(msglist);
1361         }
1362         g_free(mh_sequences_old);
1363         g_free(mh_sequences_new);
1364         g_free(path);
1365
1366         END_TIMING();
1367 }
1368
1369 static int mh_item_close(Folder *folder, FolderItem *item)
1370 {
1371         time_t last_mtime = (time_t)0;
1372         gboolean need_scan = mh_scan_required(item->folder, item);
1373         last_mtime = item->mtime;
1374
1375         mh_write_sequences(item, FALSE);
1376
1377         if (item->mtime == last_mtime && !need_scan) {
1378                 mh_set_mtime(folder, item);
1379         }
1380
1381         return 0;
1382 }
1383
1384 static void mh_set_mtime(Folder *folder, FolderItem *item)
1385 {
1386         struct stat s;
1387         gchar *path = folder_item_get_path(item);
1388
1389         cm_return_if_fail(path != NULL);
1390
1391         if (g_stat(path, &s) < 0) {
1392                 FILE_OP_ERROR(path, "stat");
1393                 g_free(path);
1394                 return;
1395         }
1396
1397         item->mtime = s.st_mtime;
1398         debug_print("MH: forced mtime of %s to %ld\n", item->name?item->name:"(null)", item->mtime);
1399         g_free(path);
1400 }