2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
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.
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.
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/>.
27 #include <glib/gi18n.h>
38 #include "procheader.h"
41 #include "statusbar.h"
45 /* Define possible missing constants for Windows. */
58 static void mh_folder_init (Folder
*folder
,
62 static Folder
*mh_folder_new (const gchar
*name
,
64 static void mh_folder_destroy (Folder
*folder
);
65 static gchar
*mh_fetch_msg (Folder
*folder
,
68 static MsgInfo
*mh_get_msginfo (Folder
*folder
,
71 static gint
mh_add_msg (Folder
*folder
,
75 static gint
mh_add_msgs (Folder
*folder
,
79 static gint
mh_copy_msg (Folder
*folder
,
82 static gint
mh_copy_msgs (Folder
*folder
,
86 static gint
mh_remove_msg (Folder
*folder
,
89 static gint
mh_remove_msgs (Folder
*folder
,
93 static gint
mh_remove_all_msg (Folder
*folder
,
95 static gboolean
mh_is_msg_changed (Folder
*folder
,
99 static gint
mh_get_num_list (Folder
*folder
,
102 gboolean
*old_uids_valid
);
103 static gint
mh_scan_tree (Folder
*folder
);
105 static gint
mh_create_tree (Folder
*folder
);
106 static FolderItem
*mh_create_folder (Folder
*folder
,
109 static gint
mh_rename_folder (Folder
*folder
,
112 static gint
mh_remove_folder (Folder
*folder
,
115 static gchar
*mh_get_new_msg_filename (FolderItem
*dest
);
117 static MsgInfo
*mh_parse_msg (const gchar
*file
,
119 static void mh_remove_missing_folder_items (Folder
*folder
);
120 static gchar
*mh_filename_from_utf8 (const gchar
*path
);
121 static gchar
*mh_filename_to_utf8 (const gchar
*path
);
122 static void mh_scan_tree_recursive (FolderItem
*item
);
124 static gboolean
mh_rename_folder_func (GNode
*node
,
126 static gchar
*mh_item_get_path (Folder
*folder
,
129 static gboolean
mh_scan_required (Folder
*folder
,
131 static int mh_item_close (Folder
*folder
,
134 static gint
mh_get_flags (Folder
*folder
, FolderItem
*item
,
135 MsgInfoList
*msginfo_list
, GRelation
*msgflags
);
137 static void mh_write_sequences (FolderItem
*item
, gboolean remove_unseen
);
139 static FolderClass mh_class
;
141 FolderClass
*mh_get_class(void)
143 if (mh_class
.idstr
== NULL
) {
144 mh_class
.type
= F_MH
;
145 mh_class
.idstr
= "mh";
146 mh_class
.uistr
= "MH";
148 /* Folder functions */
149 mh_class
.new_folder
= mh_folder_new
;
150 mh_class
.destroy_folder
= mh_folder_destroy
;
151 mh_class
.set_xml
= folder_local_set_xml
;
152 mh_class
.get_xml
= folder_local_get_xml
;
153 mh_class
.scan_tree
= mh_scan_tree
;
154 mh_class
.create_tree
= mh_create_tree
;
156 /* FolderItem functions */
157 mh_class
.item_get_path
= mh_item_get_path
;
158 mh_class
.create_folder
= mh_create_folder
;
159 mh_class
.rename_folder
= mh_rename_folder
;
160 mh_class
.remove_folder
= mh_remove_folder
;
161 mh_class
.get_num_list
= mh_get_num_list
;
162 mh_class
.scan_required
= mh_scan_required
;
163 mh_class
.close
= mh_item_close
;
164 mh_class
.get_flags
= NULL
; /*mh_get_flags */;
166 /* Message functions */
167 mh_class
.get_msginfo
= mh_get_msginfo
;
168 mh_class
.fetch_msg
= mh_fetch_msg
;
169 mh_class
.add_msg
= mh_add_msg
;
170 mh_class
.add_msgs
= mh_add_msgs
;
171 mh_class
.copy_msg
= mh_copy_msg
;
172 mh_class
.copy_msgs
= mh_copy_msgs
;
173 mh_class
.remove_msg
= mh_remove_msg
;
174 mh_class
.remove_msgs
= mh_remove_msgs
;
175 mh_class
.remove_all_msg
= mh_remove_all_msg
;
176 mh_class
.is_msg_changed
= mh_is_msg_changed
;
182 static Folder
*mh_folder_new(const gchar
*name
, const gchar
*path
)
186 folder
= (Folder
*)g_new0(MHFolder
, 1);
187 folder
->klass
= &mh_class
;
188 mh_folder_init(folder
, name
, path
);
193 static void mh_folder_destroy(Folder
*folder
)
195 folder_local_folder_destroy(LOCAL_FOLDER(folder
));
198 static void mh_folder_init(Folder
*folder
, const gchar
*name
, const gchar
*path
)
200 folder_local_folder_init(folder
, name
, path
);
204 gboolean
mh_scan_required(Folder
*folder
, FolderItem
*item
)
209 path
= folder_item_get_path(item
);
210 g_return_val_if_fail(path
!= NULL
, FALSE
);
212 if (stat(path
, &s
) < 0) {
213 FILE_OP_ERROR(path
, "stat");
218 if ((s
.st_mtime
> item
->mtime
) &&
219 (s
.st_mtime
- 3600 != item
->mtime
)) {
220 debug_print("MH scan required, folder updated: %s (%ld > %ld)\n",
222 (long int) s
.st_mtime
,
223 (long int) item
->mtime
);
228 debug_print("MH scan not required: %s (%ld <= %ld)\n",
230 (long int) s
.st_mtime
,
231 (long int) item
->mtime
);
236 static void mh_get_last_num(Folder
*folder
, FolderItem
*item
)
244 g_return_if_fail(item
!= NULL
);
246 debug_print("mh_get_last_num(): Scanning %s ...\n", item
->path
?item
->path
:"(null)");
248 path
= folder_item_get_path(item
);
249 g_return_if_fail(path
!= NULL
);
250 if (change_dir(path
) < 0) {
256 if ((dp
= opendir(".")) == NULL
) {
257 FILE_OP_ERROR(item
->path
, "opendir");
261 while ((d
= readdir(dp
)) != NULL
) {
262 if ((num
= to_number(d
->d_name
)) > 0 &&
263 dirent_is_regular_file(d
)) {
272 debug_print("Last number in dir %s = %d\n", item
->path
?item
->path
:"(null)", max
);
273 item
->last_num
= max
;
276 gint
mh_get_num_list(Folder
*folder
, FolderItem
*item
, GSList
**list
, gboolean
*old_uids_valid
)
282 gint num
, nummsgs
= 0;
284 g_return_val_if_fail(item
!= NULL
, -1);
286 debug_print("mh_get_num_list(): Scanning %s ...\n", item
->path
?item
->path
:"(null)");
288 *old_uids_valid
= TRUE
;
290 path
= folder_item_get_path(item
);
291 g_return_val_if_fail(path
!= NULL
, -1);
292 if (change_dir(path
) < 0) {
298 if ((dp
= opendir(".")) == NULL
) {
299 FILE_OP_ERROR(item
->path
, "opendir");
303 while ((d
= readdir(dp
)) != NULL
) {
304 if ((num
= to_number(d
->d_name
)) > 0) {
305 *list
= g_slist_prepend(*list
, GINT_TO_POINTER(num
));
315 static gchar
*mh_fetch_msg(Folder
*folder
, FolderItem
*item
, gint num
)
320 g_return_val_if_fail(item
!= NULL
, NULL
);
321 g_return_val_if_fail(num
> 0, NULL
);
323 path
= folder_item_get_path(item
);
324 file
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(num
), NULL
);
326 if (!is_file_exist(file
)) {
335 static MsgInfo
*mh_get_msginfo(Folder
*folder
, FolderItem
*item
, gint num
)
340 g_return_val_if_fail(item
!= NULL
, NULL
);
344 file
= mh_fetch_msg(folder
, item
, num
);
345 if (!file
) return NULL
;
347 msginfo
= mh_parse_msg(file
, item
);
349 msginfo
->msgnum
= num
;
356 static gchar
*mh_get_new_msg_filename(FolderItem
*dest
)
361 destpath
= folder_item_get_path(dest
);
362 g_return_val_if_fail(destpath
!= NULL
, NULL
);
364 if (!is_dir_exist(destpath
))
365 make_dir_hier(destpath
);
368 destfile
= g_strdup_printf("%s%c%d", destpath
, G_DIR_SEPARATOR
,
370 if (is_file_entry_exist(destfile
)) {
382 static gint
mh_add_msg(Folder
*folder
, FolderItem
*dest
, const gchar
*file
, MsgFlags
*flags
)
386 MsgFileInfo fileinfo
;
388 g_return_val_if_fail(file
!= NULL
, -1);
390 fileinfo
.msginfo
= NULL
;
391 fileinfo
.file
= (gchar
*)file
;
392 fileinfo
.flags
= flags
;
393 file_list
.data
= &fileinfo
;
394 file_list
.next
= NULL
;
396 ret
= mh_add_msgs(folder
, dest
, &file_list
, NULL
);
400 static gint
mh_add_msgs(Folder
*folder
, FolderItem
*dest
, GSList
*file_list
,
405 MsgFileInfo
*fileinfo
;
407 g_return_val_if_fail(dest
!= NULL
, -1);
408 g_return_val_if_fail(file_list
!= NULL
, -1);
410 if (dest
->last_num
< 0) {
411 mh_get_last_num(folder
, dest
);
412 if (dest
->last_num
< 0) return -1;
415 for (cur
= file_list
; cur
!= NULL
; cur
= cur
->next
) {
416 fileinfo
= (MsgFileInfo
*)cur
->data
;
418 destfile
= mh_get_new_msg_filename(dest
);
419 if (destfile
== NULL
) return -1;
422 if (link(fileinfo
->file
, destfile
) < 0) {
424 if (copy_file(fileinfo
->file
, destfile
, TRUE
) < 0) {
425 g_warning(_("can't copy message %s to %s\n"),
426 fileinfo
->file
, destfile
);
434 if (relation
!= NULL
)
435 g_relation_insert(relation
, fileinfo
, GINT_TO_POINTER(dest
->last_num
+ 1));
439 mh_write_sequences(dest
, TRUE
);
440 return dest
->last_num
;
443 static gint
mh_copy_msg(Folder
*folder
, FolderItem
*dest
, MsgInfo
*msginfo
)
447 g_return_val_if_fail(msginfo
!= NULL
, -1);
449 msglist
.data
= msginfo
;
452 return mh_copy_msgs(folder
, dest
, &msglist
, NULL
);
455 static gint
mh_copy_msgs(Folder
*folder
, FolderItem
*dest
, MsgInfoList
*msglist
,
458 gboolean dest_need_scan
= FALSE
;
459 gboolean src_need_scan
= FALSE
;
460 FolderItem
*src
= NULL
;
464 FolderItemPrefs
*prefs
;
465 MsgInfo
*msginfo
= NULL
;
466 MsgInfoList
*cur
= NULL
;
467 gint curnum
= 0, total
= 0;
468 gchar
*srcpath
= NULL
;
469 gboolean full_fetch
= FALSE
;
470 time_t last_dest_mtime
= (time_t)0;
471 time_t last_src_mtime
= (time_t)0;
473 g_return_val_if_fail(dest
!= NULL
, -1);
474 g_return_val_if_fail(msglist
!= NULL
, -1);
476 msginfo
= (MsgInfo
*)msglist
->data
;
478 g_return_val_if_fail(msginfo
!= NULL
, -1);
480 if (msginfo
->folder
== dest
) {
481 g_warning("the src folder is identical to the dest.\n");
485 if (msginfo
->folder
->folder
!= dest
->folder
)
488 if (FOLDER_TYPE(msginfo
->folder
->folder
) == F_MH
) {
489 src
= msginfo
->folder
;
492 if (dest
->last_num
< 0) {
493 mh_get_last_num(folder
, dest
);
494 if (dest
->last_num
< 0) return -1;
499 srcpath
= folder_item_get_path(msginfo
->folder
);
501 dest_need_scan
= mh_scan_required(dest
->folder
, dest
);
502 last_dest_mtime
= dest
->mtime
;
505 src_need_scan
= mh_scan_required(src
->folder
, src
);
506 last_src_mtime
= src
->mtime
;
509 total
= g_slist_length(msglist
);
511 if (MSG_IS_MOVE(msginfo
->flags
))
512 statusbar_print_all(_("Moving messages..."));
514 statusbar_print_all(_("Copying messages..."));
516 for (cur
= msglist
; cur
; cur
= cur
->next
) {
517 msginfo
= (MsgInfo
*)cur
->data
;
519 goto err_reset_status
;
522 srcfile
= g_strconcat(srcpath
,
524 itos(msginfo
->msgnum
), NULL
);
526 srcfile
= procmsg_get_message_file(msginfo
);
529 goto err_reset_status
;
531 destfile
= mh_get_new_msg_filename(dest
);
534 goto err_reset_status
;
538 statusbar_progress_all(curnum
, total
, 100);
539 if (curnum
% 100 == 0)
544 debug_print("Copying message %s%c%d to %s ...\n",
545 msginfo
->folder
->path
, G_DIR_SEPARATOR
,
546 msginfo
->msgnum
, dest
->path
);
549 if (MSG_IS_MOVE(msginfo
->flags
)) {
550 msginfo
->flags
.tmp_flags
&= ~MSG_MOVE_DONE
;
551 if (move_file(srcfile
, destfile
, TRUE
) < 0) {
552 FILE_OP_ERROR(srcfile
, "move");
553 if (copy_file(srcfile
, destfile
, TRUE
) < 0) {
554 FILE_OP_ERROR(srcfile
, "copy");
557 goto err_reset_status
;
560 /* say unlinking's not necessary */
561 msginfo
->flags
.tmp_flags
|= MSG_MOVE_DONE
;
563 } else if (copy_file(srcfile
, destfile
, TRUE
) < 0) {
564 FILE_OP_ERROR(srcfile
, "copy");
567 goto err_reset_status
;
569 if (prefs
&& prefs
->enable_folder_chmod
&& prefs
->folder_chmod
) {
570 if (chmod(destfile
, prefs
->folder_chmod
) < 0)
571 FILE_OP_ERROR(destfile
, "chmod");
574 filemode
= prefs
->folder_chmod
;
575 if (filemode
& S_IRGRP
) filemode
|= S_IWGRP
;
576 if (filemode
& S_IROTH
) filemode
|= S_IWOTH
;
579 g_relation_insert(relation
, msginfo
, GINT_TO_POINTER(dest
->last_num
+1));
586 mh_write_sequences(dest
, TRUE
);
588 if (dest
->mtime
== last_dest_mtime
&& !dest_need_scan
) {
592 if (src
&& src
->mtime
== last_src_mtime
&& !src_need_scan
) {
597 statusbar_progress_all(0,0,0);
600 return dest
->last_num
;
603 mh_write_sequences(dest
, TRUE
);
605 statusbar_progress_all(0,0,0);
612 static gint
mh_remove_msg(Folder
*folder
, FolderItem
*item
, gint num
)
614 gboolean need_scan
= FALSE
;
615 time_t last_mtime
= (time_t)0;
618 g_return_val_if_fail(item
!= NULL
, -1);
620 file
= mh_fetch_msg(folder
, item
, num
);
621 g_return_val_if_fail(file
!= NULL
, -1);
623 need_scan
= mh_scan_required(folder
, item
);
624 last_mtime
= item
->mtime
;
626 if (g_unlink(file
) < 0) {
627 FILE_OP_ERROR(file
, "unlink");
632 if (item
->mtime
== last_mtime
&& !need_scan
) {
639 static gint
mh_remove_msgs(Folder
*folder
, FolderItem
*item
,
640 MsgInfoList
*msglist
, GRelation
*relation
)
642 gboolean need_scan
= FALSE
;
644 time_t last_mtime
= (time_t)0;
646 gint total
= 0, curnum
= 0;
648 g_return_val_if_fail(item
!= NULL
, -1);
650 path
= folder_item_get_path(item
);
652 need_scan
= mh_scan_required(folder
, item
);
653 last_mtime
= item
->mtime
;
655 total
= g_slist_length(msglist
);
657 statusbar_print_all(_("Deleting messages..."));
660 for (cur
= msglist
; cur
; cur
= cur
->next
) {
661 MsgInfo
*msginfo
= (MsgInfo
*)cur
->data
;
664 if (MSG_IS_MOVE(msginfo
->flags
) && MSG_IS_MOVE_DONE(msginfo
->flags
)) {
665 msginfo
->flags
.tmp_flags
&= ~MSG_MOVE_DONE
;
669 statusbar_progress_all(curnum
, total
, 100);
670 if (curnum
% 100 == 0)
675 file
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(msginfo
->msgnum
), NULL
);
679 if (g_unlink(file
) < 0) {
688 statusbar_progress_all(0,0,0);
691 if (item
->mtime
== last_mtime
&& !need_scan
) {
699 static gint
mh_remove_all_msg(Folder
*folder
, FolderItem
*item
)
704 g_return_val_if_fail(item
!= NULL
, -1);
706 path
= folder_item_get_path(item
);
707 g_return_val_if_fail(path
!= NULL
, -1);
708 val
= remove_all_numbered_files(path
);
711 mh_write_sequences(item
, TRUE
);
716 static gboolean
mh_is_msg_changed(Folder
*folder
, FolderItem
*item
,
721 if (g_stat(itos(msginfo
->msgnum
), &s
) < 0 ||
722 msginfo
->size
!= s
.st_size
|| (
723 (msginfo
->mtime
- s
.st_mtime
!= 0) &&
724 (msginfo
->mtime
- s
.st_mtime
!= 3600) &&
725 (msginfo
->mtime
- s
.st_mtime
!= -3600)))
731 static gint
mh_scan_tree(Folder
*folder
)
736 g_return_val_if_fail(folder
!= NULL
, -1);
739 item
= folder_item_new(folder
, folder
->name
, NULL
);
740 item
->folder
= folder
;
741 folder
->node
= item
->node
= g_node_new(item
);
743 item
= FOLDER_ITEM(folder
->node
->data
);
745 rootpath
= folder_item_get_path(item
);
746 if (change_dir(rootpath
) < 0) {
752 mh_create_tree(folder
);
753 mh_remove_missing_folder_items(folder
);
754 mh_scan_tree_recursive(item
);
759 #define MAKE_DIR_IF_NOT_EXIST(dir) \
761 if (!is_dir_exist(dir)) { \
762 if (is_file_exist(dir)) { \
763 g_warning("File `%s' already exists.\n" \
764 "Can't create folder.", dir); \
767 if (make_dir_hier(dir) < 0) \
772 static gint
mh_create_tree(Folder
*folder
)
776 g_return_val_if_fail(folder
!= NULL
, -1);
778 CHDIR_RETURN_VAL_IF_FAIL(get_mail_base_dir(), -1);
779 rootpath
= LOCAL_FOLDER(folder
)->rootpath
;
780 MAKE_DIR_IF_NOT_EXIST(rootpath
);
781 CHDIR_RETURN_VAL_IF_FAIL(rootpath
, -1);
782 MAKE_DIR_IF_NOT_EXIST(INBOX_DIR
);
783 MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR
);
784 MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR
);
785 MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR
);
786 MAKE_DIR_IF_NOT_EXIST(TRASH_DIR
);
791 #undef MAKE_DIR_IF_NOT_EXIST
793 static gchar
*mh_item_get_path(Folder
*folder
, FolderItem
*item
)
795 gchar
*folder_path
, *path
;
797 g_return_val_if_fail(folder
!= NULL
, NULL
);
798 g_return_val_if_fail(item
!= NULL
, NULL
);
800 folder_path
= g_strdup(LOCAL_FOLDER(folder
)->rootpath
);
801 g_return_val_if_fail(folder_path
!= NULL
, NULL
);
803 /* FIXME: [W32] The code below does not correctly merge
804 relative filenames; there should be a function to handle
806 if ( !is_relative_filename (folder_path
) ) {
808 path
= g_strconcat(folder_path
, G_DIR_SEPARATOR_S
,
811 path
= g_strdup(folder_path
);
814 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
815 folder_path
, G_DIR_SEPARATOR_S
,
818 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
822 real_path
= mh_filename_from_utf8(path
);
823 if (!is_dir_exist(real_path
) && is_dir_exist(path
)) {
824 /* mmh, older version did put utf8 filenames instead of
825 * the correct encoding */
826 rename(path
, real_path
);
827 folder_item_scan(item
);
834 static FolderItem
*mh_create_folder(Folder
*folder
, FolderItem
*parent
,
837 gchar
*path
, *real_name
;
839 FolderItem
*new_item
;
840 gchar
*mh_sequences_filename
;
841 FILE *mh_sequences_file
;
843 g_return_val_if_fail(folder
!= NULL
, NULL
);
844 g_return_val_if_fail(parent
!= NULL
, NULL
);
845 g_return_val_if_fail(name
!= NULL
, NULL
);
847 path
= folder_item_get_path(parent
);
848 if (!is_dir_exist(path
))
849 if (make_dir_hier(path
) != 0)
852 real_name
= mh_filename_from_utf8(name
);
853 fullpath
= g_strconcat(path
, G_DIR_SEPARATOR_S
, real_name
, NULL
);
857 if (make_dir(fullpath
) < 0) {
865 path
= g_strconcat(parent
->path
, G_DIR_SEPARATOR_S
, name
,
868 path
= g_strdup(name
);
869 new_item
= folder_item_new(folder
, name
, path
);
870 folder_item_append(parent
, new_item
);
874 path
= folder_item_get_path(new_item
);
875 mh_sequences_filename
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
876 ".mh_sequences", NULL
);
877 if ((mh_sequences_file
= g_fopen(mh_sequences_filename
, "a+b")) != NULL
) {
878 fclose(mh_sequences_file
);
880 g_free(mh_sequences_filename
);
886 static gint
mh_rename_folder(Folder
*folder
, FolderItem
*item
,
892 gchar
*newpath
, *utf8newpath
;
895 g_return_val_if_fail(folder
!= NULL
, -1);
896 g_return_val_if_fail(item
!= NULL
, -1);
897 g_return_val_if_fail(item
->path
!= NULL
, -1);
898 g_return_val_if_fail(name
!= NULL
, -1);
900 oldpath
= folder_item_get_path(item
);
901 if (!is_dir_exist(oldpath
))
902 make_dir_hier(oldpath
);
904 dirname
= g_path_get_dirname(oldpath
);
905 real_name
= mh_filename_from_utf8(name
);
906 newpath
= g_strconcat(dirname
, G_DIR_SEPARATOR_S
, real_name
, NULL
);
909 if (g_rename(oldpath
, newpath
) < 0) {
910 FILE_OP_ERROR(oldpath
, "rename");
919 if (strchr(item
->path
, G_DIR_SEPARATOR
) != NULL
) {
920 dirname
= g_path_get_dirname(item
->path
);
921 utf8newpath
= g_strconcat(dirname
, G_DIR_SEPARATOR_S
,
925 utf8newpath
= g_strdup(name
);
928 item
->name
= g_strdup(name
);
930 paths
[0] = g_strdup(item
->path
);
931 paths
[1] = utf8newpath
;
932 g_node_traverse(item
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
933 mh_rename_folder_func
, paths
);
940 static gint
mh_remove_folder(Folder
*folder
, FolderItem
*item
)
944 g_return_val_if_fail(folder
!= NULL
, -1);
945 g_return_val_if_fail(item
!= NULL
, -1);
946 g_return_val_if_fail(item
->path
!= NULL
, -1);
948 path
= folder_item_get_path(item
);
949 if (remove_dir_recursive(path
) < 0) {
950 g_warning("can't remove directory `%s'\n", path
);
956 folder_item_remove(item
);
960 static MsgInfo
*mh_parse_msg(const gchar
*file
, FolderItem
*item
)
965 g_return_val_if_fail(item
!= NULL
, NULL
);
966 g_return_val_if_fail(file
!= NULL
, NULL
);
968 flags
.perm_flags
= MSG_NEW
|MSG_UNREAD
;
971 if (folder_has_parent_of_type(item
, F_QUEUE
)) {
972 MSG_SET_TMP_FLAGS(flags
, MSG_QUEUED
);
973 } else if (folder_has_parent_of_type(item
, F_DRAFT
)) {
974 MSG_SET_TMP_FLAGS(flags
, MSG_DRAFT
);
977 msginfo
= procheader_parse_file(file
, flags
, FALSE
, FALSE
);
978 if (!msginfo
) return NULL
;
980 msginfo
->msgnum
= atoi(file
);
981 msginfo
->folder
= item
;
986 static gboolean
mh_remove_missing_folder_items_func(GNode
*node
, gpointer data
)
991 g_return_val_if_fail(node
->data
!= NULL
, FALSE
);
993 if (G_NODE_IS_ROOT(node
))
996 item
= FOLDER_ITEM(node
->data
);
998 path
= folder_item_get_path(item
);
999 if (!is_dir_exist(path
)) {
1000 debug_print("folder '%s' not found. removing...\n", path
?path
:"(null)");
1001 folder_item_remove(item
);
1008 static void mh_remove_missing_folder_items(Folder
*folder
)
1010 g_return_if_fail(folder
!= NULL
);
1012 debug_print("searching missing folders...\n");
1014 g_node_traverse(folder
->node
, G_POST_ORDER
, G_TRAVERSE_ALL
, -1,
1015 mh_remove_missing_folder_items_func
, folder
);
1018 static void mh_scan_tree_recursive(FolderItem
*item
)
1027 const gchar
*dir_name
;
1029 gchar
*real_path
, *entry
, *utf8entry
, *utf8name
;
1032 g_return_if_fail(item
!= NULL
);
1033 g_return_if_fail(item
->folder
!= NULL
);
1035 folder
= item
->folder
;
1037 real_path
= item
->path
? mh_filename_from_utf8(item
->path
) : g_strdup(".");
1039 dir
= g_dir_open(real_path
, 0, NULL
);
1041 g_warning("failed to open directory: %s\n", real_path
);
1046 dp
= opendir(real_path
);
1048 FILE_OP_ERROR(real_path
, "opendir");
1054 debug_print("scanning %s ...\n",
1055 item
->path
? item
->path
1056 : LOCAL_FOLDER(item
->folder
)->rootpath
);
1057 if (folder
->ui_func
)
1058 folder
->ui_func(folder
, item
, folder
->ui_func_data
);
1061 while ((dir_name
= g_dir_read_name(dir
)) != NULL
) {
1063 while ((d
= readdir(dp
)) != NULL
) {
1064 dir_name
= d
->d_name
;
1066 if (dir_name
[0] == '.') continue;
1068 utf8name
= mh_filename_to_utf8(dir_name
);
1070 utf8entry
= g_strconcat(item
->path
, G_DIR_SEPARATOR_S
,
1073 utf8entry
= g_strdup(utf8name
);
1074 entry
= mh_filename_from_utf8(utf8entry
);
1077 #if !defined(G_OS_WIN32) && !defined(MAEMO) && defined(HAVE_DIRENT_D_TYPE)
1078 d
->d_type
== DT_DIR
||
1079 (d
->d_type
== DT_UNKNOWN
&&
1081 g_stat(entry
, &s
) == 0 && S_ISDIR(s
.st_mode
)
1082 #if !defined(G_OS_WIN32) && !defined(MAEMO) && defined(HAVE_DIRENT_D_TYPE)
1086 FolderItem
*new_item
= NULL
;
1090 for (node
= node
->children
; node
!= NULL
; node
= node
->next
) {
1091 FolderItem
*cur_item
= FOLDER_ITEM(node
->data
);
1092 gchar
*curpath
= mh_filename_from_utf8(cur_item
->path
);
1093 if (!strcmp2(curpath
, entry
)) {
1094 new_item
= cur_item
;
1101 debug_print("new folder '%s' found.\n", entry
);
1102 new_item
= folder_item_new(folder
, utf8name
, utf8entry
);
1103 folder_item_append(item
, new_item
);
1107 if (!folder
->inbox
&&
1108 !strcmp(dir_name
, INBOX_DIR
)) {
1109 new_item
->stype
= F_INBOX
;
1110 folder
->inbox
= new_item
;
1111 } else if (!folder
->outbox
&&
1112 !strcmp(dir_name
, OUTBOX_DIR
)) {
1113 new_item
->stype
= F_OUTBOX
;
1114 folder
->outbox
= new_item
;
1115 } else if (!folder
->draft
&&
1116 !strcmp(dir_name
, DRAFT_DIR
)) {
1117 new_item
->stype
= F_DRAFT
;
1118 folder
->draft
= new_item
;
1119 } else if (!folder
->queue
&&
1120 !strcmp(dir_name
, QUEUE_DIR
)) {
1121 new_item
->stype
= F_QUEUE
;
1122 folder
->queue
= new_item
;
1123 } else if (!folder
->trash
&&
1124 !strcmp(dir_name
, TRASH_DIR
)) {
1125 new_item
->stype
= F_TRASH
;
1126 folder
->trash
= new_item
;
1130 mh_scan_tree_recursive(new_item
);
1131 } else if (to_number(dir_name
) > 0) n_msg
++;
1147 static gboolean
mh_rename_folder_func(GNode
*node
, gpointer data
)
1149 FolderItem
*item
= node
->data
;
1150 gchar
**paths
= data
;
1151 const gchar
*oldpath
= paths
[0];
1152 const gchar
*newpath
= paths
[1];
1154 gchar
*new_itempath
;
1157 oldpathlen
= strlen(oldpath
);
1158 if (strncmp(oldpath
, item
->path
, oldpathlen
) != 0) {
1159 g_warning("path doesn't match: %s, %s\n", oldpath
, item
->path
);
1163 base
= item
->path
+ oldpathlen
;
1164 while (*base
== G_DIR_SEPARATOR
) base
++;
1166 new_itempath
= g_strdup(newpath
);
1168 new_itempath
= g_strconcat(newpath
, G_DIR_SEPARATOR_S
, base
,
1171 item
->path
= new_itempath
;
1176 static gchar
*mh_filename_from_utf8(const gchar
*path
)
1178 gchar
*real_path
= g_filename_from_utf8(path
, -1, NULL
, NULL
, NULL
);
1181 g_warning("mh_filename_from_utf8: failed to convert character set\n");
1182 real_path
= g_strdup(path
);
1188 static gchar
*mh_filename_to_utf8(const gchar
*path
)
1190 gchar
*utf8path
= g_filename_to_utf8(path
, -1, NULL
, NULL
, NULL
);
1192 g_warning("mh_filename_to_utf8: failed to convert character set\n");
1193 utf8path
= g_strdup(path
);
1199 static gint
sort_cache_list_by_msgnum(gconstpointer a
, gconstpointer b
)
1201 MsgInfo
*msginfo_a
= (MsgInfo
*) a
;
1202 MsgInfo
*msginfo_b
= (MsgInfo
*) b
;
1204 return (msginfo_a
->msgnum
- msginfo_b
->msgnum
);
1207 static gchar
*get_unseen_seq_name(void)
1209 static gchar
*seq_name
= NULL
;
1211 gchar buf
[BUFFSIZE
];
1213 gchar
*profile_path
= g_strconcat(
1214 get_home_dir(), G_DIR_SEPARATOR_S
,
1215 ".mh_profile", NULL
);
1216 FILE *fp
= g_fopen(profile_path
, "r");
1218 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
1219 if (!strncmp(buf
, "Unseen-Sequence:", strlen("Unseen-Sequence:"))) {
1220 gchar
*seq_tmp
= buf
+strlen("Unseen-Sequence:");
1221 while (*seq_tmp
== ' ')
1223 seq_name
= g_strdup(seq_tmp
);
1224 seq_name
= strretchomp(seq_name
);
1231 seq_name
= g_strdup("unseen");
1232 tmp
= g_strdup_printf("%s:", seq_name
);
1240 static gint
mh_get_flags(Folder
*folder
, FolderItem
*item
,
1241 MsgInfoList
*msginfo_list
, GRelation
*msgflags
)
1243 gchar
*mh_sequences_filename
;
1244 FILE *mh_sequences_file
;
1245 gchar buf
[BUFFSIZE
];
1246 gchar
*unseen_list
= NULL
;
1248 MsgInfoList
*mcur
= NULL
;
1250 GTimer *timer = g_timer_new();
1251 g_timer_start(timer);
1256 /* don't update from .mh_sequences if the item's opened: mails may have
1257 * been marked read/unread and it's not yet written in the file. */
1261 path
= folder_item_get_path(item
);
1263 mh_sequences_filename
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1264 ".mh_sequences", NULL
);
1266 if ((mh_sequences_file
= g_fopen(mh_sequences_filename
, "r+b")) != NULL
) {
1267 while (fgets(buf
, sizeof(buf
), mh_sequences_file
) != NULL
) {
1268 if (!strncmp(buf
, get_unseen_seq_name(), strlen(get_unseen_seq_name()))) {
1269 unseen_list
= g_strdup(buf
+strlen(get_unseen_seq_name()));
1273 fclose(mh_sequences_file
);
1276 g_free(mh_sequences_filename
);
1280 gchar
*token
= NULL
, *next
= NULL
, *boundary
= NULL
;
1282 GHashTable
*unseen_table
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
1284 cur
= unseen_list
= strretchomp(unseen_list
);
1285 debug_print("found unseen list in .mh_sequences: %s\n", unseen_list
);
1287 while (*cur
&& *cur
== ' ')
1290 if ((next
= strchr(cur
, ' ')) != NULL
) {
1299 if ((boundary
= strchr(token
, '-')) != NULL
) {
1305 for (i
= atoi(start
); i
<= atoi(end
); i
++) {
1306 g_hash_table_insert(unseen_table
, GINT_TO_POINTER(i
), GINT_TO_POINTER(1));
1308 } else if ((num
= atoi(token
)) > 0) {
1309 g_hash_table_insert(unseen_table
, GINT_TO_POINTER(num
), GINT_TO_POINTER(1));
1314 for (mcur
= msginfo_list
; mcur
; mcur
= mcur
->next
) {
1315 MsgInfo
*msginfo
= (MsgInfo
*)mcur
->data
;
1316 MsgPermFlags flags
= msginfo
->flags
.perm_flags
;
1317 if (g_hash_table_lookup(unseen_table
, GINT_TO_POINTER(msginfo
->msgnum
))) {
1318 flags
|= MSG_UNREAD
;
1319 } else if (!(flags
& MSG_NEW
)) { /* don't mark new msgs as read */
1320 flags
&= ~(MSG_UNREAD
);
1322 if (flags
!= msginfo
->flags
.perm_flags
)
1323 g_relation_insert(msgflags
, msginfo
, GINT_TO_POINTER(flags
));
1325 g_hash_table_destroy(unseen_table
);
1326 g_free(unseen_list
);
1329 g_timer_stop(timer);
1330 g_print("mh_get_flags: %f secs\n", g_timer_elapsed(timer, NULL));
1331 g_timer_destroy(timer);
1337 static void mh_write_sequences(FolderItem
*item
, gboolean remove_unseen
)
1339 gchar
*mh_sequences_old
, *mh_sequences_new
;
1340 FILE *mh_sequences_old_fp
, *mh_sequences_new_fp
;
1341 gchar buf
[BUFFSIZE
];
1348 path
= folder_item_get_path(item
);
1350 mh_sequences_old
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1351 ".mh_sequences", NULL
);
1352 mh_sequences_new
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1353 ".mh_sequences.new", NULL
);
1354 if ((mh_sequences_new_fp
= g_fopen(mh_sequences_new
, "w+b")) != NULL
) {
1355 GSList
*msglist
= folder_item_get_msg_list(item
);
1357 MsgInfo
*info
= NULL
;
1358 gint start
= -1, end
= -1;
1359 gchar
*sequence
= g_strdup("");
1361 msglist
= g_slist_sort(msglist
, sort_cache_list_by_msgnum
);
1364 /* write the unseen sequence if we don't have to scrap it */
1365 if (!remove_unseen
) do {
1366 info
= (MsgInfo
*)(cur
? cur
->data
:NULL
);
1367 if (info
&& (MSG_IS_UNREAD(info
->flags
) || MSG_IS_NEW(info
->flags
))) {
1369 start
= end
= info
->msgnum
;
1373 if (start
> 0 && end
> 0) {
1377 snprintf(tmp
, 31, " %d-%d", start
, end
);
1379 snprintf(tmp
, 31, " %d", start
);
1381 tmp_len
= strlen(tmp
);
1382 sequence
= g_realloc(sequence
, seq_len
+tmp_len
+1);
1383 strcpy(sequence
+seq_len
, tmp
);
1389 cur
= cur
? cur
->next
:NULL
;
1390 } while (cur
|| (start
> 0 && end
> 0));
1391 if (sequence
&& *sequence
) {
1392 fprintf(mh_sequences_new_fp
, "%s%s\n",
1393 get_unseen_seq_name(), sequence
);
1394 debug_print("wrote unseen sequence: '%s%s'\n",
1395 get_unseen_seq_name(), sequence
);
1397 /* rewrite the rest of the file */
1398 if ((mh_sequences_old_fp
= g_fopen(mh_sequences_old
, "r+b")) != NULL
) {
1399 while (fgets(buf
, sizeof(buf
), mh_sequences_old_fp
) != NULL
) {
1400 if (strncmp(buf
, get_unseen_seq_name(), strlen(get_unseen_seq_name())))
1401 fprintf(mh_sequences_new_fp
, "%s", buf
);
1403 fclose(mh_sequences_old_fp
);
1406 fflush(mh_sequences_new_fp
);
1408 fsync(fileno(mh_sequences_new_fp
));
1410 fclose(mh_sequences_new_fp
);
1412 g_rename(mh_sequences_new
, mh_sequences_old
);
1414 procmsg_msg_list_free(msglist
);
1416 g_free(mh_sequences_old
);
1417 g_free(mh_sequences_new
);
1423 static int mh_item_close(Folder
*folder
, FolderItem
*item
)
1425 time_t last_mtime
= (time_t)0;
1426 gboolean need_scan
= mh_scan_required(item
->folder
, item
);
1427 last_mtime
= item
->mtime
;
1429 mh_write_sequences(item
, FALSE
);
1431 if (item
->mtime
== last_mtime
&& !need_scan
) {
1438 void mh_set_mtime(FolderItem
*item
)
1441 gchar
*path
= folder_item_get_path(item
);
1443 g_return_if_fail(path
!= NULL
);
1445 if (stat(path
, &s
) < 0) {
1446 FILE_OP_ERROR(path
, "stat");
1451 item
->mtime
= s
.st_mtime
;
1452 debug_print("MH: forced mtime of %s to %ld\n", item
->name
?item
->name
:"(null)", item
->mtime
);