2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 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/>.
22 #include "claws-features.h"
28 #include <glib/gi18n.h>
37 #include "folder_item_prefs.h"
40 #include "procheader.h"
43 #include "statusbar.h"
47 #include "file-utils.h"
49 /* Define possible missing constants for Windows. */
62 static void mh_folder_init (Folder
*folder
,
66 static Folder
*mh_folder_new (const gchar
*name
,
68 static void mh_folder_destroy (Folder
*folder
);
69 static gchar
*mh_fetch_msg (Folder
*folder
,
72 static MsgInfo
*mh_get_msginfo (Folder
*folder
,
75 static gint
mh_add_msg (Folder
*folder
,
79 static gint
mh_add_msgs (Folder
*folder
,
82 GHashTable
*relation
);
83 static gint
mh_copy_msg (Folder
*folder
,
86 static gint
mh_copy_msgs (Folder
*folder
,
89 GHashTable
*relation
);
90 static gint
mh_remove_msg (Folder
*folder
,
93 static gint
mh_remove_msgs (Folder
*folder
,
96 GHashTable
*relation
);
97 static gint
mh_remove_all_msg (Folder
*folder
,
99 static gboolean
mh_is_msg_changed (Folder
*folder
,
103 static gint
mh_get_num_list (Folder
*folder
,
106 gboolean
*old_uids_valid
);
107 static gint
mh_scan_tree (Folder
*folder
);
109 static gint
mh_create_tree (Folder
*folder
);
110 static FolderItem
*mh_create_folder (Folder
*folder
,
113 static gint
mh_rename_folder (Folder
*folder
,
116 static gint
mh_remove_folder (Folder
*folder
,
119 static gchar
*mh_get_new_msg_filename (FolderItem
*dest
);
121 static MsgInfo
*mh_parse_msg (const gchar
*file
,
123 static void mh_remove_missing_folder_items (Folder
*folder
);
124 static gchar
*mh_filename_from_utf8 (const gchar
*path
);
125 static gchar
*mh_filename_to_utf8 (const gchar
*path
);
126 static void mh_scan_tree_recursive (FolderItem
*item
);
128 static gboolean
mh_rename_folder_func (GNode
*node
,
130 static gchar
*mh_item_get_path (Folder
*folder
,
133 static gboolean
mh_scan_required (Folder
*folder
,
135 static void mh_set_mtime (Folder
*folder
,
137 static int mh_item_close (Folder
*folder
,
140 static gint
mh_get_flags (Folder
*folder
, FolderItem
*item
,
141 MsgInfoList
*msginfo_list
, GHashTable
*msgflags
);
143 static void mh_write_sequences (FolderItem
*item
, gboolean remove_unseen
);
145 static FolderClass mh_class
;
147 FolderClass
*mh_get_class(void)
149 if (mh_class
.idstr
== NULL
) {
150 mh_class
.type
= F_MH
;
151 mh_class
.idstr
= "mh";
152 mh_class
.uistr
= "MH";
153 mh_class
.supports_server_search
= FALSE
;
155 /* Folder functions */
156 mh_class
.new_folder
= mh_folder_new
;
157 mh_class
.destroy_folder
= mh_folder_destroy
;
158 mh_class
.set_xml
= folder_local_set_xml
;
159 mh_class
.get_xml
= folder_local_get_xml
;
160 mh_class
.scan_tree
= mh_scan_tree
;
161 mh_class
.create_tree
= mh_create_tree
;
163 /* FolderItem functions */
164 mh_class
.item_get_path
= mh_item_get_path
;
165 mh_class
.create_folder
= mh_create_folder
;
166 mh_class
.rename_folder
= mh_rename_folder
;
167 mh_class
.remove_folder
= mh_remove_folder
;
168 mh_class
.get_num_list
= mh_get_num_list
;
169 mh_class
.scan_required
= mh_scan_required
;
170 mh_class
.set_mtime
= mh_set_mtime
;
171 mh_class
.close
= mh_item_close
;
172 mh_class
.get_flags
= NULL
; /*mh_get_flags */;
174 /* Message functions */
175 mh_class
.get_msginfo
= mh_get_msginfo
;
176 mh_class
.fetch_msg
= mh_fetch_msg
;
177 mh_class
.add_msg
= mh_add_msg
;
178 mh_class
.add_msgs
= mh_add_msgs
;
179 mh_class
.copy_msg
= mh_copy_msg
;
180 mh_class
.copy_msgs
= mh_copy_msgs
;
181 mh_class
.search_msgs
= folder_item_search_msgs_local
;
182 mh_class
.remove_msg
= mh_remove_msg
;
183 mh_class
.remove_msgs
= mh_remove_msgs
;
184 mh_class
.remove_all_msg
= mh_remove_all_msg
;
185 mh_class
.is_msg_changed
= mh_is_msg_changed
;
191 static Folder
*mh_folder_new(const gchar
*name
, const gchar
*path
)
195 folder
= (Folder
*)g_new0(MHFolder
, 1);
196 folder
->klass
= &mh_class
;
197 mh_folder_init(folder
, name
, path
);
202 static void mh_folder_destroy(Folder
*folder
)
204 folder_local_folder_destroy(LOCAL_FOLDER(folder
));
207 static void mh_folder_init(Folder
*folder
, const gchar
*name
, const gchar
*path
)
209 folder_local_folder_init(folder
, name
, path
);
213 gboolean
mh_scan_required(Folder
*folder
, FolderItem
*item
)
218 path
= folder_item_get_path(item
);
219 cm_return_val_if_fail(path
!= NULL
, FALSE
);
221 if (g_stat(path
, &s
) < 0) {
222 FILE_OP_ERROR(path
, "stat");
227 if ((s
.st_mtime
> item
->mtime
) &&
228 (s
.st_mtime
- 3600 != item
->mtime
)) {
229 debug_print("MH scan required, folder updated: %s (%ld > %ld)\n",
231 (long int) s
.st_mtime
,
232 (long int) item
->mtime
);
237 debug_print("MH scan not required: %s (%ld <= %ld)\n",
239 (long int) s
.st_mtime
,
240 (long int) item
->mtime
);
245 static void mh_get_last_num(Folder
*folder
, FolderItem
*item
)
247 gchar
*path
, *fullpath
;
250 GError
*error
= NULL
;
254 cm_return_if_fail(item
!= NULL
);
256 debug_print("mh_get_last_num(): Scanning %s ...\n", item
->path
?item
->path
:"(null)");
258 path
= folder_item_get_path(item
);
259 cm_return_if_fail(path
!= NULL
);
261 if ((dp
= g_dir_open(path
, 0, &error
)) == NULL
) {
262 g_warning("Couldn't open directory '%s': %s (%d)",
263 path
, error
->message
, error
->code
);
269 while ((d
= g_dir_read_name(dp
)) != NULL
) {
270 fullpath
= g_strconcat(path
, G_DIR_SEPARATOR_S
, d
, NULL
);
271 if ((num
= to_number(d
)) > 0 &&
272 g_file_test(fullpath
, G_FILE_TEST_IS_REGULAR
)) {
284 debug_print("Last number in dir %s = %d\n", item
->path
?item
->path
:"(null)", max
);
285 item
->last_num
= max
;
288 gint
mh_get_num_list(Folder
*folder
, FolderItem
*item
, GSList
**list
, gboolean
*old_uids_valid
)
294 GError
*error
= NULL
;
295 gint num
, nummsgs
= 0;
297 cm_return_val_if_fail(item
!= NULL
, -1);
299 debug_print("mh_get_num_list(): Scanning %s ...\n", item
->path
?item
->path
:"(null)");
301 *old_uids_valid
= TRUE
;
303 path
= folder_item_get_path(item
);
304 cm_return_val_if_fail(path
!= NULL
, -1);
306 if ((dp
= g_dir_open(path
, 0, &error
)) == NULL
) {
307 g_message("Couldn't open current directory: %s (%d).\n",
308 error
->message
, error
->code
);
315 while ((d
= g_dir_read_name(dp
)) != NULL
) {
316 if ((num
= to_number(d
)) > 0) {
317 *list
= g_slist_prepend(*list
, GINT_TO_POINTER(num
));
323 mh_set_mtime(folder
, item
);
327 static gchar
*mh_fetch_msg(Folder
*folder
, FolderItem
*item
, gint num
)
332 cm_return_val_if_fail(item
!= NULL
, NULL
);
333 cm_return_val_if_fail(num
> 0, NULL
);
335 path
= folder_item_get_path(item
);
336 file
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(num
), NULL
);
338 if (!is_file_exist(file
)) {
347 static MsgInfo
*mh_get_msginfo(Folder
*folder
, FolderItem
*item
, gint num
)
352 cm_return_val_if_fail(item
!= NULL
, NULL
);
356 file
= mh_fetch_msg(folder
, item
, num
);
357 if (!file
) return NULL
;
359 msginfo
= mh_parse_msg(file
, item
);
361 msginfo
->msgnum
= num
;
368 static gchar
*mh_get_new_msg_filename(FolderItem
*dest
)
373 destpath
= folder_item_get_path(dest
);
374 cm_return_val_if_fail(destpath
!= NULL
, NULL
);
376 if (dest
->last_num
< 0) {
377 mh_get_last_num(dest
->folder
, dest
);
378 if (dest
->last_num
< 0) return NULL
;
381 if (!is_dir_exist(destpath
))
382 make_dir_hier(destpath
);
385 destfile
= g_strdup_printf("%s%c%d", destpath
, G_DIR_SEPARATOR
,
387 if (is_file_entry_exist(destfile
)) {
399 static gint
mh_add_msg(Folder
*folder
, FolderItem
*dest
, const gchar
*file
, MsgFlags
*flags
)
403 MsgFileInfo fileinfo
;
405 cm_return_val_if_fail(file
!= NULL
, -1);
407 fileinfo
.msginfo
= NULL
;
408 fileinfo
.file
= (gchar
*)file
;
409 fileinfo
.flags
= flags
;
410 file_list
.data
= &fileinfo
;
411 file_list
.next
= NULL
;
413 ret
= mh_add_msgs(folder
, dest
, &file_list
, NULL
);
417 static gint
mh_add_msgs(Folder
*folder
, FolderItem
*dest
, GSList
*file_list
,
418 GHashTable
*relation
)
422 MsgFileInfo
*fileinfo
;
424 cm_return_val_if_fail(dest
!= NULL
, -1);
425 cm_return_val_if_fail(file_list
!= NULL
, -1);
427 if (dest
->last_num
< 0) {
428 mh_get_last_num(folder
, dest
);
429 if (dest
->last_num
< 0) return -1;
432 for (cur
= file_list
; cur
!= NULL
; cur
= cur
->next
) {
433 fileinfo
= (MsgFileInfo
*)cur
->data
;
435 destfile
= mh_get_new_msg_filename(dest
);
436 if (destfile
== NULL
) return -1;
439 if (link(fileinfo
->file
, destfile
) < 0) {
441 if (copy_file(fileinfo
->file
, destfile
, TRUE
) < 0) {
442 g_warning("can't copy message %s to %s",
443 fileinfo
->file
, destfile
);
451 if (relation
!= NULL
)
452 g_hash_table_insert(relation
, fileinfo
, GINT_TO_POINTER(dest
->last_num
+ 1));
456 mh_write_sequences(dest
, TRUE
);
457 return dest
->last_num
;
460 static gint
mh_copy_msg(Folder
*folder
, FolderItem
*dest
, MsgInfo
*msginfo
)
464 cm_return_val_if_fail(msginfo
!= NULL
, -1);
466 msglist
.data
= msginfo
;
469 return mh_copy_msgs(folder
, dest
, &msglist
, NULL
);
472 static gint
mh_copy_msgs(Folder
*folder
, FolderItem
*dest
, MsgInfoList
*msglist
,
473 GHashTable
*relation
)
475 gboolean dest_need_scan
= FALSE
;
476 gboolean src_need_scan
= FALSE
;
477 FolderItem
*src
= NULL
;
480 FolderItemPrefs
*prefs
;
481 MsgInfo
*msginfo
= NULL
;
482 MsgInfoList
*cur
= NULL
;
483 gint curnum
= 0, total
= 0;
484 gchar
*srcpath
= NULL
;
485 gboolean full_fetch
= FALSE
;
486 time_t last_dest_mtime
= (time_t)0;
487 time_t last_src_mtime
= (time_t)0;
489 cm_return_val_if_fail(dest
!= NULL
, -1);
490 cm_return_val_if_fail(msglist
!= NULL
, -1);
492 msginfo
= (MsgInfo
*)msglist
->data
;
494 cm_return_val_if_fail(msginfo
!= NULL
, -1);
496 if (msginfo
->folder
== dest
) {
497 g_warning("the src folder is identical to the dest.");
501 if (msginfo
->folder
->folder
!= dest
->folder
)
504 if (FOLDER_TYPE(msginfo
->folder
->folder
) == F_MH
) {
505 src
= msginfo
->folder
;
508 if (dest
->last_num
< 0) {
509 mh_get_last_num(folder
, dest
);
510 if (dest
->last_num
< 0) return -1;
515 srcpath
= folder_item_get_path(msginfo
->folder
);
517 dest_need_scan
= mh_scan_required(dest
->folder
, dest
);
518 last_dest_mtime
= dest
->mtime
;
521 src_need_scan
= mh_scan_required(src
->folder
, src
);
522 last_src_mtime
= src
->mtime
;
525 total
= g_slist_length(msglist
);
527 if (MSG_IS_MOVE(msginfo
->flags
))
528 statusbar_print_all(_("Moving messages..."));
530 statusbar_print_all(_("Copying messages..."));
532 for (cur
= msglist
; cur
; cur
= cur
->next
) {
533 msginfo
= (MsgInfo
*)cur
->data
;
535 goto err_reset_status
;
538 srcfile
= g_strconcat(srcpath
,
540 itos(msginfo
->msgnum
), NULL
);
542 srcfile
= procmsg_get_message_file(msginfo
);
545 goto err_reset_status
;
547 destfile
= mh_get_new_msg_filename(dest
);
550 goto err_reset_status
;
554 statusbar_progress_all(curnum
, total
, 100);
555 if (curnum
% 100 == 0)
560 debug_print("Copying message %s%c%d to %s ...\n",
561 msginfo
->folder
->path
, G_DIR_SEPARATOR
,
562 msginfo
->msgnum
, dest
->path
);
565 if (MSG_IS_MOVE(msginfo
->flags
)) {
566 msginfo
->flags
.tmp_flags
&= ~MSG_MOVE_DONE
;
567 if (move_file(srcfile
, destfile
, TRUE
) < 0) {
568 FILE_OP_ERROR(srcfile
, "move");
569 if (copy_file(srcfile
, destfile
, TRUE
) < 0) {
570 FILE_OP_ERROR(srcfile
, "copy");
573 goto err_reset_status
;
576 /* say unlinking's not necessary */
577 msginfo
->flags
.tmp_flags
|= MSG_MOVE_DONE
;
579 } else if (copy_file(srcfile
, destfile
, TRUE
) < 0) {
580 FILE_OP_ERROR(srcfile
, "copy");
583 goto err_reset_status
;
585 if (prefs
&& prefs
->enable_folder_chmod
&& prefs
->folder_chmod
) {
586 if (chmod(destfile
, prefs
->folder_chmod
) < 0)
587 FILE_OP_ERROR(destfile
, "chmod");
590 if (g_hash_table_lookup(relation
, msginfo
) != NULL
)
591 g_warning("already in: %p", msginfo
);
593 g_hash_table_insert(relation
, msginfo
, GINT_TO_POINTER(dest
->last_num
+1));
601 mh_write_sequences(dest
, TRUE
);
603 if (dest
->mtime
== last_dest_mtime
&& !dest_need_scan
) {
604 mh_set_mtime(folder
, dest
);
607 if (src
&& src
->mtime
== last_src_mtime
&& !src_need_scan
) {
608 mh_set_mtime(folder
, src
);
612 statusbar_progress_all(0,0,0);
615 return dest
->last_num
;
618 mh_write_sequences(dest
, TRUE
);
620 statusbar_progress_all(0,0,0);
627 static gint
mh_remove_msg(Folder
*folder
, FolderItem
*item
, gint num
)
629 gboolean need_scan
= FALSE
;
630 time_t last_mtime
= (time_t)0;
633 cm_return_val_if_fail(item
!= NULL
, -1);
635 file
= mh_fetch_msg(folder
, item
, num
);
636 cm_return_val_if_fail(file
!= NULL
, -1);
638 need_scan
= mh_scan_required(folder
, item
);
639 last_mtime
= item
->mtime
;
641 if (claws_unlink(file
) < 0) {
642 FILE_OP_ERROR(file
, "unlink");
647 if (item
->mtime
== last_mtime
&& !need_scan
) {
648 mh_set_mtime(folder
, item
);
654 static gint
mh_remove_msgs(Folder
*folder
, FolderItem
*item
,
655 MsgInfoList
*msglist
, GHashTable
*relation
)
657 gboolean need_scan
= FALSE
;
659 time_t last_mtime
= (time_t)0;
661 gint total
= 0, curnum
= 0;
663 cm_return_val_if_fail(item
!= NULL
, -1);
665 path
= folder_item_get_path(item
);
667 need_scan
= mh_scan_required(folder
, item
);
668 last_mtime
= item
->mtime
;
670 total
= g_slist_length(msglist
);
672 statusbar_print_all(_("Deleting messages..."));
675 for (cur
= msglist
; cur
; cur
= cur
->next
) {
676 MsgInfo
*msginfo
= (MsgInfo
*)cur
->data
;
679 if (MSG_IS_MOVE(msginfo
->flags
) && MSG_IS_MOVE_DONE(msginfo
->flags
)) {
680 msginfo
->flags
.tmp_flags
&= ~MSG_MOVE_DONE
;
684 statusbar_progress_all(curnum
, total
, 100);
685 if (curnum
% 100 == 0)
690 file
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(msginfo
->msgnum
), NULL
);
694 if (claws_unlink(file
) < 0) {
703 statusbar_progress_all(0,0,0);
706 if (item
->mtime
== last_mtime
&& !need_scan
) {
707 mh_set_mtime(folder
, item
);
714 static gint
mh_remove_all_msg(Folder
*folder
, FolderItem
*item
)
719 cm_return_val_if_fail(item
!= NULL
, -1);
721 path
= folder_item_get_path(item
);
722 cm_return_val_if_fail(path
!= NULL
, -1);
723 val
= remove_all_numbered_files(path
);
726 mh_write_sequences(item
, TRUE
);
731 static gboolean
mh_is_msg_changed(Folder
*folder
, FolderItem
*item
,
738 GError
*error
= NULL
;
746 parent_path
= folder_item_get_path(item
);
747 path
= g_strdup_printf("%s%c%d", parent_path
,
748 G_DIR_SEPARATOR
, msginfo
->msgnum
);
752 f
= g_file_new_for_path(path
);
754 fi
= g_file_query_info(f
, "standard::size,time::modified",
755 G_FILE_QUERY_INFO_NONE
, NULL
, &error
);
757 g_warning(error
->message
);
763 g_file_info_get_modification_time(fi
, &tv
);
764 if (msginfo
->size
!= g_file_info_get_size(fi
) || (
765 (msginfo
->mtime
- tv
.tv_sec
!= 0) &&
766 abs(msginfo
->mtime
- tv
.tv_sec
) != 3600)) {
772 r
= g_stat(path
, &s
);
775 msginfo
->size
!= s
.st_size
|| (
776 (msginfo
->mtime
- s
.st_mtime
!= 0) &&
777 (msginfo
->mtime
- s
.st_mtime
!= 3600) &&
778 (msginfo
->mtime
- s
.st_mtime
!= -3600))) {
786 static gint
mh_scan_tree(Folder
*folder
)
790 cm_return_val_if_fail(folder
!= NULL
, -1);
793 item
= folder_item_new(folder
, folder
->name
, NULL
);
794 item
->folder
= folder
;
795 folder
->node
= item
->node
= g_node_new(item
);
797 item
= FOLDER_ITEM(folder
->node
->data
);
799 mh_create_tree(folder
);
800 mh_remove_missing_folder_items(folder
);
801 mh_scan_tree_recursive(item
);
806 #define MAKE_DIR_IF_NOT_EXIST(dir) \
808 if (!is_dir_exist(dir)) { \
809 if (is_file_exist(dir)) { \
810 g_warning("File '%s' already exists. " \
811 "Can't create folder.", dir); \
814 if (make_dir_hier(dir) < 0) \
816 debug_print("Created dir '%s'\n", dir); \
820 static gint
mh_create_tree(Folder
*folder
)
822 gchar
*rootpath
, *f
, *path
;
824 cm_return_val_if_fail(folder
!= NULL
, -1);
826 rootpath
= LOCAL_FOLDER(folder
)->rootpath
;
828 if (*rootpath
== '/') {
829 #elif defined G_OS_WIN32
830 if (g_ascii_isalpha(*rootpath
) && !strncmp(rootpath
+ 1, ":\\", 2)) {
832 /* Folder path is absolute. */
833 rootpath
= g_strdup(rootpath
);
835 /* Folder path is relative, using mail base dir. */
836 rootpath
= g_strconcat(get_mail_base_dir(), G_DIR_SEPARATOR_S
,
840 MAKE_DIR_IF_NOT_EXIST(rootpath
);
842 /* Create special directories as needed */
843 if (folder
->inbox
!= NULL
&&
844 folder
->inbox
->path
!= NULL
)
845 f
= folder
->inbox
->path
;
848 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
849 MAKE_DIR_IF_NOT_EXIST(path
);
852 if (folder
->outbox
!= NULL
&&
853 folder
->outbox
->path
!= NULL
)
854 f
= folder
->outbox
->path
;
857 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
858 MAKE_DIR_IF_NOT_EXIST(path
);
861 if (folder
->draft
!= NULL
&&
862 folder
->draft
->path
!= NULL
)
863 f
= folder
->draft
->path
;
866 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
867 MAKE_DIR_IF_NOT_EXIST(path
);
870 if (folder
->queue
!= NULL
&&
871 folder
->queue
->path
!= NULL
)
872 f
= folder
->queue
->path
;
875 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
876 MAKE_DIR_IF_NOT_EXIST(path
);
879 if (folder
->trash
!= NULL
&&
880 folder
->trash
->path
!= NULL
)
881 f
= folder
->trash
->path
;
884 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
885 MAKE_DIR_IF_NOT_EXIST(path
);
892 #undef MAKE_DIR_IF_NOT_EXIST
894 static gchar
*mh_item_get_path(Folder
*folder
, FolderItem
*item
)
896 gchar
*folder_path
, *path
;
898 cm_return_val_if_fail(folder
!= NULL
, NULL
);
899 cm_return_val_if_fail(item
!= NULL
, NULL
);
901 folder_path
= g_strdup(LOCAL_FOLDER(folder
)->rootpath
);
902 cm_return_val_if_fail(folder_path
!= NULL
, NULL
);
904 /* FIXME: [W32] The code below does not correctly merge
905 relative filenames; there should be a function to handle
907 if ( !is_relative_filename (folder_path
) ) {
909 path
= g_strconcat(folder_path
, G_DIR_SEPARATOR_S
,
912 path
= g_strdup(folder_path
);
915 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
916 folder_path
, G_DIR_SEPARATOR_S
,
919 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
923 real_path
= mh_filename_from_utf8(path
);
924 if (!is_dir_exist(real_path
) && is_dir_exist(path
)) {
925 /* mmh, older version did put utf8 filenames instead of
926 * the correct encoding */
927 if (g_rename(path
, real_path
) == 0)
928 folder_item_scan(item
);
935 static gboolean
mh_renumber_msg(MsgInfo
*info
)
938 gboolean result
= FALSE
;
940 cm_return_val_if_fail(info
!= NULL
, FALSE
);
942 src
= folder_item_fetch_msg(info
->folder
, info
->msgnum
);
943 dest
= mh_get_new_msg_filename(info
->folder
);
944 num
= info
->folder
->last_num
+ 1;
946 if (move_file(src
, dest
, FALSE
) == 0) {
947 msgcache_remove_msg(info
->folder
->cache
, info
->msgnum
);
949 msgcache_add_msg(info
->folder
->cache
, info
);
959 static FolderItem
*mh_create_folder(Folder
*folder
, FolderItem
*parent
,
962 gchar
*path
, *real_name
;
964 FolderItem
*new_item
;
965 gchar
*mh_sequences_filename
;
966 FILE *mh_sequences_file
;
968 cm_return_val_if_fail(folder
!= NULL
, NULL
);
969 cm_return_val_if_fail(parent
!= NULL
, NULL
);
970 cm_return_val_if_fail(name
!= NULL
, NULL
);
972 path
= folder_item_get_path(parent
);
973 if (!is_dir_exist(path
))
974 if (make_dir_hier(path
) != 0)
977 real_name
= mh_filename_from_utf8(name
);
978 fullpath
= g_strconcat(path
, G_DIR_SEPARATOR_S
, real_name
, NULL
);
982 if (to_number(name
) > 0) {
983 MsgInfo
*info
= folder_item_get_msginfo(parent
, to_number(name
));
985 gboolean ok
= mh_renumber_msg(info
);
986 procmsg_msginfo_free(&info
);
994 if (make_dir(fullpath
) < 0) {
1002 path
= g_strconcat(parent
->path
, G_DIR_SEPARATOR_S
, name
,
1005 path
= g_strdup(name
);
1006 new_item
= folder_item_new(folder
, name
, path
);
1007 folder_item_append(parent
, new_item
);
1011 path
= folder_item_get_path(new_item
);
1012 mh_sequences_filename
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1013 ".mh_sequences", NULL
);
1014 if ((mh_sequences_file
= claws_fopen(mh_sequences_filename
, "a+b")) != NULL
) {
1015 claws_fclose(mh_sequences_file
);
1017 g_free(mh_sequences_filename
);
1023 static gint
mh_rename_folder(Folder
*folder
, FolderItem
*item
,
1029 gchar
*newpath
, *utf8newpath
;
1032 cm_return_val_if_fail(folder
!= NULL
, -1);
1033 cm_return_val_if_fail(item
!= NULL
, -1);
1034 cm_return_val_if_fail(item
->path
!= NULL
, -1);
1035 cm_return_val_if_fail(name
!= NULL
, -1);
1037 oldpath
= folder_item_get_path(item
);
1038 if (!is_dir_exist(oldpath
))
1039 make_dir_hier(oldpath
);
1041 dirname
= g_path_get_dirname(oldpath
);
1042 real_name
= mh_filename_from_utf8(name
);
1043 newpath
= g_strconcat(dirname
, G_DIR_SEPARATOR_S
, real_name
, NULL
);
1046 if (g_rename(oldpath
, newpath
) < 0) {
1047 FILE_OP_ERROR(oldpath
, "rename");
1056 if (strchr(item
->path
, G_DIR_SEPARATOR
) != NULL
) {
1057 dirname
= g_path_get_dirname(item
->path
);
1058 utf8newpath
= g_strconcat(dirname
, G_DIR_SEPARATOR_S
,
1062 utf8newpath
= g_strdup(name
);
1065 item
->name
= g_strdup(name
);
1067 paths
[0] = g_strdup(item
->path
);
1068 paths
[1] = utf8newpath
;
1069 g_node_traverse(item
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
1070 mh_rename_folder_func
, paths
);
1077 static gint
mh_remove_folder(Folder
*folder
, FolderItem
*item
)
1082 cm_return_val_if_fail(folder
!= NULL
, -1);
1083 cm_return_val_if_fail(item
!= NULL
, -1);
1084 cm_return_val_if_fail(item
->path
!= NULL
, -1);
1086 path
= folder_item_get_path(item
);
1087 if ((ret
= remove_dir_recursive(path
)) < 0) {
1088 g_warning("can't remove directory '%s'", path
);
1094 folder_item_remove(item
);
1098 static MsgInfo
*mh_parse_msg(const gchar
*file
, FolderItem
*item
)
1103 cm_return_val_if_fail(item
!= NULL
, NULL
);
1104 cm_return_val_if_fail(file
!= NULL
, NULL
);
1106 flags
.perm_flags
= MSG_NEW
|MSG_UNREAD
;
1107 flags
.tmp_flags
= 0;
1109 if (folder_has_parent_of_type(item
, F_QUEUE
)) {
1110 MSG_SET_TMP_FLAGS(flags
, MSG_QUEUED
);
1111 } else if (folder_has_parent_of_type(item
, F_DRAFT
)) {
1112 MSG_SET_TMP_FLAGS(flags
, MSG_DRAFT
);
1115 msginfo
= procheader_parse_file(file
, flags
, FALSE
, FALSE
);
1116 if (!msginfo
) return NULL
;
1118 msginfo
->msgnum
= atoi(file
);
1119 msginfo
->folder
= item
;
1124 static gboolean
mh_remove_missing_folder_items_func(GNode
*node
, gpointer data
)
1129 cm_return_val_if_fail(node
->data
!= NULL
, FALSE
);
1131 if (G_NODE_IS_ROOT(node
))
1134 item
= FOLDER_ITEM(node
->data
);
1136 path
= folder_item_get_path(item
);
1137 if (!is_dir_exist(path
)) {
1138 debug_print("folder '%s' not found. removing...\n", path
?path
:"(null)");
1139 folder_item_remove(item
);
1146 static void mh_remove_missing_folder_items(Folder
*folder
)
1148 cm_return_if_fail(folder
!= NULL
);
1150 debug_print("searching missing folders...\n");
1152 g_node_traverse(folder
->node
, G_POST_ORDER
, G_TRAVERSE_ALL
, -1,
1153 mh_remove_missing_folder_items_func
, folder
);
1156 static void mh_scan_tree_recursive(FolderItem
*item
)
1160 const gchar
*dir_name
;
1161 gchar
*entry
, *utf8entry
, *utf8name
, *path
;
1163 GError
*error
= NULL
;
1165 cm_return_if_fail(item
!= NULL
);
1166 cm_return_if_fail(item
->folder
!= NULL
);
1168 folder
= item
->folder
;
1170 path
= folder_item_get_path(item
);
1171 debug_print("mh_scan_tree_recursive() opening '%s'\n", path
);
1172 dir
= g_dir_open(path
, 0, &error
);
1174 g_warning("failed to open directory '%s': %s (%d)",
1175 path
, error
->message
, error
->code
);
1176 g_error_free(error
);
1181 debug_print("scanning %s ...\n",
1182 item
->path
? item
->path
1183 : LOCAL_FOLDER(item
->folder
)->rootpath
);
1184 if (folder
->ui_func
)
1185 folder
->ui_func(folder
, item
, folder
->ui_func_data
);
1187 while ((dir_name
= g_dir_read_name(dir
)) != NULL
) {
1188 if (dir_name
[0] == '.') continue;
1190 entry
= g_strconcat(path
, G_DIR_SEPARATOR_S
, dir_name
, NULL
);
1192 utf8name
= mh_filename_to_utf8(dir_name
);
1194 utf8entry
= g_strconcat(item
->path
, G_DIR_SEPARATOR_S
,
1197 utf8entry
= g_strdup(utf8name
);
1199 if (g_file_test(entry
, G_FILE_TEST_IS_DIR
)) {
1200 FolderItem
*new_item
= NULL
;
1204 for (node
= node
->children
; node
!= NULL
; node
= node
->next
) {
1205 FolderItem
*cur_item
= FOLDER_ITEM(node
->data
);
1206 gchar
*curpath
= folder_item_get_path(cur_item
);
1207 if (!g_strcmp0(curpath
, entry
)) {
1208 new_item
= cur_item
;
1215 debug_print("new folder '%s' found.\n", entry
);
1216 new_item
= folder_item_new(folder
, utf8name
, utf8entry
);
1217 folder_item_append(item
, new_item
);
1221 if (!folder
->inbox
&&
1222 !strcmp(dir_name
, INBOX_DIR
)) {
1223 new_item
->stype
= F_INBOX
;
1224 folder
->inbox
= new_item
;
1225 } else if (!folder
->outbox
&&
1226 !strcmp(dir_name
, OUTBOX_DIR
)) {
1227 new_item
->stype
= F_OUTBOX
;
1228 folder
->outbox
= new_item
;
1229 } else if (!folder
->draft
&&
1230 !strcmp(dir_name
, DRAFT_DIR
)) {
1231 new_item
->stype
= F_DRAFT
;
1232 folder
->draft
= new_item
;
1233 } else if (!folder
->queue
&&
1234 !strcmp(dir_name
, QUEUE_DIR
)) {
1235 new_item
->stype
= F_QUEUE
;
1236 folder
->queue
= new_item
;
1237 } else if (!folder
->trash
&&
1238 !strcmp(dir_name
, TRASH_DIR
)) {
1239 new_item
->stype
= F_TRASH
;
1240 folder
->trash
= new_item
;
1244 mh_scan_tree_recursive(new_item
);
1245 } else if (to_number(dir_name
) > 0) n_msg
++;
1255 mh_set_mtime(folder
, item
);
1258 static gboolean
mh_rename_folder_func(GNode
*node
, gpointer data
)
1260 FolderItem
*item
= node
->data
;
1261 gchar
**paths
= data
;
1262 const gchar
*oldpath
= paths
[0];
1263 const gchar
*newpath
= paths
[1];
1265 gchar
*new_itempath
;
1268 oldpathlen
= strlen(oldpath
);
1269 if (strncmp(oldpath
, item
->path
, oldpathlen
) != 0) {
1270 g_warning("path doesn't match: %s, %s", oldpath
, item
->path
);
1274 base
= item
->path
+ oldpathlen
;
1275 while (*base
== G_DIR_SEPARATOR
) base
++;
1277 new_itempath
= g_strdup(newpath
);
1279 new_itempath
= g_strconcat(newpath
, G_DIR_SEPARATOR_S
, base
,
1282 item
->path
= new_itempath
;
1287 static gchar
*mh_filename_from_utf8(const gchar
*path
)
1289 gchar
*real_path
= g_filename_from_utf8(path
, -1, NULL
, NULL
, NULL
);
1292 g_warning("mh_filename_from_utf8: failed to convert character set");
1293 real_path
= g_strdup(path
);
1299 static gchar
*mh_filename_to_utf8(const gchar
*path
)
1301 gchar
*utf8path
= g_filename_to_utf8(path
, -1, NULL
, NULL
, NULL
);
1303 g_warning("mh_filename_to_utf8: failed to convert character set");
1304 utf8path
= g_strdup(path
);
1310 static gint
sort_cache_list_by_msgnum(gconstpointer a
, gconstpointer b
)
1312 MsgInfo
*msginfo_a
= (MsgInfo
*) a
;
1313 MsgInfo
*msginfo_b
= (MsgInfo
*) b
;
1315 return (msginfo_a
->msgnum
- msginfo_b
->msgnum
);
1318 static gchar
*get_unseen_seq_name(void)
1320 static gchar
*seq_name
= NULL
;
1322 gchar buf
[BUFFSIZE
];
1324 gchar
*profile_path
= g_strconcat(
1325 get_home_dir(), G_DIR_SEPARATOR_S
,
1326 ".mh_profile", NULL
);
1327 FILE *fp
= claws_fopen(profile_path
, "r");
1329 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
) {
1330 if (!strncmp(buf
, "Unseen-Sequence:", strlen("Unseen-Sequence:"))) {
1331 gchar
*seq_tmp
= buf
+strlen("Unseen-Sequence:");
1332 while (*seq_tmp
== ' ')
1334 seq_name
= g_strdup(seq_tmp
);
1335 seq_name
= strretchomp(seq_name
);
1342 seq_name
= g_strdup("unseen");
1343 tmp
= g_strdup_printf("%s:", seq_name
);
1350 static void mh_write_sequences(FolderItem
*item
, gboolean remove_unseen
)
1352 gchar
*mh_sequences_old
, *mh_sequences_new
;
1353 FILE *mh_sequences_old_fp
, *mh_sequences_new_fp
;
1354 gchar buf
[BUFFSIZE
];
1356 gboolean err
= FALSE
;
1362 path
= folder_item_get_path(item
);
1364 mh_sequences_old
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1365 ".mh_sequences", NULL
);
1366 mh_sequences_new
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1367 ".mh_sequences.new", NULL
);
1368 if ((mh_sequences_new_fp
= claws_fopen(mh_sequences_new
, "w+b")) != NULL
) {
1369 GSList
*msglist
= folder_item_get_msg_list(item
);
1371 MsgInfo
*info
= NULL
;
1372 gint start
= -1, end
= -1;
1373 gchar
*sequence
= g_strdup("");
1375 msglist
= g_slist_sort(msglist
, sort_cache_list_by_msgnum
);
1378 /* write the unseen sequence if we don't have to scrap it */
1379 if (!remove_unseen
) do {
1380 info
= (MsgInfo
*)(cur
? cur
->data
:NULL
);
1381 if (info
&& (MSG_IS_UNREAD(info
->flags
) || MSG_IS_NEW(info
->flags
))) {
1383 start
= end
= info
->msgnum
;
1387 if (start
> 0 && end
> 0) {
1391 snprintf(tmp
, 31, " %d-%d", start
, end
);
1393 snprintf(tmp
, 31, " %d", start
);
1395 tmp_len
= strlen(tmp
);
1396 sequence
= g_realloc(sequence
, seq_len
+tmp_len
+1);
1397 strcpy(sequence
+seq_len
, tmp
);
1403 cur
= cur
? cur
->next
:NULL
;
1404 } while (cur
|| (start
> 0 && end
> 0));
1405 if (sequence
&& *sequence
) {
1406 if (fprintf(mh_sequences_new_fp
, "%s%s\n",
1407 get_unseen_seq_name(), sequence
) < 0)
1410 debug_print("wrote unseen sequence: '%s%s'\n",
1411 get_unseen_seq_name(), sequence
);
1413 /* rewrite the rest of the file */
1414 if ((mh_sequences_old_fp
= claws_fopen(mh_sequences_old
, "r+b")) != NULL
) {
1415 while (claws_fgets(buf
, sizeof(buf
), mh_sequences_old_fp
) != NULL
) {
1416 if (strncmp(buf
, get_unseen_seq_name(), strlen(get_unseen_seq_name())))
1417 if (fprintf(mh_sequences_new_fp
, "%s", buf
) < 0) {
1422 claws_fclose(mh_sequences_old_fp
);
1425 if (claws_safe_fclose(mh_sequences_new_fp
) == EOF
)
1429 if (g_rename(mh_sequences_new
, mh_sequences_old
) < 0)
1430 FILE_OP_ERROR(mh_sequences_new
, "rename");
1433 procmsg_msg_list_free(msglist
);
1435 g_free(mh_sequences_old
);
1436 g_free(mh_sequences_new
);
1442 static int mh_item_close(Folder
*folder
, FolderItem
*item
)
1444 time_t last_mtime
= (time_t)0;
1445 gboolean need_scan
= mh_scan_required(item
->folder
, item
);
1446 last_mtime
= item
->mtime
;
1448 mh_write_sequences(item
, FALSE
);
1450 if (item
->mtime
== last_mtime
&& !need_scan
) {
1451 mh_set_mtime(folder
, item
);
1457 static void mh_set_mtime(Folder
*folder
, FolderItem
*item
)
1460 gchar
*path
= folder_item_get_path(item
);
1462 cm_return_if_fail(path
!= NULL
);
1464 if (g_stat(path
, &s
) < 0) {
1465 FILE_OP_ERROR(path
, "stat");
1470 item
->mtime
= s
.st_mtime
;
1471 debug_print("MH: forced mtime of %s to %"G_GSIZE_FORMAT
"\n", item
->name
?item
->name
:"(null)", item
->mtime
);