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"
48 /* Define possible missing constants for Windows. */
61 static void mh_folder_init (Folder
*folder
,
65 static Folder
*mh_folder_new (const gchar
*name
,
67 static void mh_folder_destroy (Folder
*folder
);
68 static gchar
*mh_fetch_msg (Folder
*folder
,
71 static MsgInfo
*mh_get_msginfo (Folder
*folder
,
74 static gint
mh_add_msg (Folder
*folder
,
78 static gint
mh_add_msgs (Folder
*folder
,
81 GHashTable
*relation
);
82 static gint
mh_copy_msg (Folder
*folder
,
85 static gint
mh_copy_msgs (Folder
*folder
,
88 GHashTable
*relation
);
89 static gint
mh_remove_msg (Folder
*folder
,
92 static gint
mh_remove_msgs (Folder
*folder
,
95 GHashTable
*relation
);
96 static gint
mh_remove_all_msg (Folder
*folder
,
98 static gboolean
mh_is_msg_changed (Folder
*folder
,
102 static gint
mh_get_num_list (Folder
*folder
,
105 gboolean
*old_uids_valid
);
106 static gint
mh_scan_tree (Folder
*folder
);
108 static gint
mh_create_tree (Folder
*folder
);
109 static FolderItem
*mh_create_folder (Folder
*folder
,
112 static gint
mh_rename_folder (Folder
*folder
,
115 static gint
mh_remove_folder (Folder
*folder
,
118 static gchar
*mh_get_new_msg_filename (FolderItem
*dest
);
120 static MsgInfo
*mh_parse_msg (const gchar
*file
,
122 static void mh_remove_missing_folder_items (Folder
*folder
);
123 static gchar
*mh_filename_from_utf8 (const gchar
*path
);
124 static gchar
*mh_filename_to_utf8 (const gchar
*path
);
125 static void mh_scan_tree_recursive (FolderItem
*item
);
127 static gboolean
mh_rename_folder_func (GNode
*node
,
129 static gchar
*mh_item_get_path (Folder
*folder
,
132 static gboolean
mh_scan_required (Folder
*folder
,
134 static void mh_set_mtime (Folder
*folder
,
136 static int mh_item_close (Folder
*folder
,
139 static gint
mh_get_flags (Folder
*folder
, FolderItem
*item
,
140 MsgInfoList
*msginfo_list
, GHashTable
*msgflags
);
142 static void mh_write_sequences (FolderItem
*item
, gboolean remove_unseen
);
144 static FolderClass mh_class
;
146 FolderClass
*mh_get_class(void)
148 if (mh_class
.idstr
== NULL
) {
149 mh_class
.type
= F_MH
;
150 mh_class
.idstr
= "mh";
151 mh_class
.uistr
= "MH";
152 mh_class
.supports_server_search
= FALSE
;
154 /* Folder functions */
155 mh_class
.new_folder
= mh_folder_new
;
156 mh_class
.destroy_folder
= mh_folder_destroy
;
157 mh_class
.set_xml
= folder_local_set_xml
;
158 mh_class
.get_xml
= folder_local_get_xml
;
159 mh_class
.scan_tree
= mh_scan_tree
;
160 mh_class
.create_tree
= mh_create_tree
;
162 /* FolderItem functions */
163 mh_class
.item_get_path
= mh_item_get_path
;
164 mh_class
.create_folder
= mh_create_folder
;
165 mh_class
.rename_folder
= mh_rename_folder
;
166 mh_class
.remove_folder
= mh_remove_folder
;
167 mh_class
.get_num_list
= mh_get_num_list
;
168 mh_class
.scan_required
= mh_scan_required
;
169 mh_class
.set_mtime
= mh_set_mtime
;
170 mh_class
.close
= mh_item_close
;
171 mh_class
.get_flags
= NULL
; /*mh_get_flags */;
173 /* Message functions */
174 mh_class
.get_msginfo
= mh_get_msginfo
;
175 mh_class
.fetch_msg
= mh_fetch_msg
;
176 mh_class
.add_msg
= mh_add_msg
;
177 mh_class
.add_msgs
= mh_add_msgs
;
178 mh_class
.copy_msg
= mh_copy_msg
;
179 mh_class
.copy_msgs
= mh_copy_msgs
;
180 mh_class
.search_msgs
= folder_item_search_msgs_local
;
181 mh_class
.remove_msg
= mh_remove_msg
;
182 mh_class
.remove_msgs
= mh_remove_msgs
;
183 mh_class
.remove_all_msg
= mh_remove_all_msg
;
184 mh_class
.is_msg_changed
= mh_is_msg_changed
;
190 static Folder
*mh_folder_new(const gchar
*name
, const gchar
*path
)
194 folder
= (Folder
*)g_new0(MHFolder
, 1);
195 folder
->klass
= &mh_class
;
196 mh_folder_init(folder
, name
, path
);
201 static void mh_folder_destroy(Folder
*folder
)
203 folder_local_folder_destroy(LOCAL_FOLDER(folder
));
206 static void mh_folder_init(Folder
*folder
, const gchar
*name
, const gchar
*path
)
208 folder_local_folder_init(folder
, name
, path
);
212 gboolean
mh_scan_required(Folder
*folder
, FolderItem
*item
)
217 path
= folder_item_get_path(item
);
218 cm_return_val_if_fail(path
!= NULL
, FALSE
);
220 if (g_stat(path
, &s
) < 0) {
221 FILE_OP_ERROR(path
, "stat");
226 if ((s
.st_mtime
> item
->mtime
) &&
227 (s
.st_mtime
- 3600 != item
->mtime
)) {
228 debug_print("MH scan required, folder updated: %s (%ld > %ld)\n",
230 (long int) s
.st_mtime
,
231 (long int) item
->mtime
);
236 debug_print("MH scan not required: %s (%ld <= %ld)\n",
238 (long int) s
.st_mtime
,
239 (long int) item
->mtime
);
244 static void mh_get_last_num(Folder
*folder
, FolderItem
*item
)
246 gchar
*path
, *fullpath
;
249 GError
*error
= NULL
;
253 cm_return_if_fail(item
!= NULL
);
255 debug_print("mh_get_last_num(): Scanning %s ...\n", item
->path
?item
->path
:"(null)");
257 path
= folder_item_get_path(item
);
258 cm_return_if_fail(path
!= NULL
);
260 if ((dp
= g_dir_open(path
, 0, &error
)) == NULL
) {
261 g_warning("Couldn't open directory '%s': %s (%d)",
262 path
, error
->message
, error
->code
);
268 while ((d
= g_dir_read_name(dp
)) != NULL
) {
269 fullpath
= g_strconcat(path
, G_DIR_SEPARATOR_S
, d
, NULL
);
270 if ((num
= to_number(d
)) > 0 &&
271 g_file_test(fullpath
, G_FILE_TEST_IS_REGULAR
)) {
283 debug_print("Last number in dir %s = %d\n", item
->path
?item
->path
:"(null)", max
);
284 item
->last_num
= max
;
287 gint
mh_get_num_list(Folder
*folder
, FolderItem
*item
, GSList
**list
, gboolean
*old_uids_valid
)
293 GError
*error
= NULL
;
294 gint num
, nummsgs
= 0;
296 cm_return_val_if_fail(item
!= NULL
, -1);
298 debug_print("mh_get_num_list(): Scanning %s ...\n", item
->path
?item
->path
:"(null)");
300 *old_uids_valid
= TRUE
;
302 path
= folder_item_get_path(item
);
303 cm_return_val_if_fail(path
!= NULL
, -1);
305 if ((dp
= g_dir_open(path
, 0, &error
)) == NULL
) {
306 g_message("Couldn't open current directory: %s (%d).\n",
307 error
->message
, error
->code
);
314 while ((d
= g_dir_read_name(dp
)) != NULL
) {
315 if ((num
= to_number(d
)) > 0) {
316 *list
= g_slist_prepend(*list
, GINT_TO_POINTER(num
));
322 mh_set_mtime(folder
, item
);
326 static gchar
*mh_fetch_msg(Folder
*folder
, FolderItem
*item
, gint num
)
331 cm_return_val_if_fail(item
!= NULL
, NULL
);
332 cm_return_val_if_fail(num
> 0, NULL
);
334 path
= folder_item_get_path(item
);
335 file
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(num
), NULL
);
337 if (!is_file_exist(file
)) {
346 static MsgInfo
*mh_get_msginfo(Folder
*folder
, FolderItem
*item
, gint num
)
351 cm_return_val_if_fail(item
!= NULL
, NULL
);
355 file
= mh_fetch_msg(folder
, item
, num
);
356 if (!file
) return NULL
;
358 msginfo
= mh_parse_msg(file
, item
);
360 msginfo
->msgnum
= num
;
367 static gchar
*mh_get_new_msg_filename(FolderItem
*dest
)
372 destpath
= folder_item_get_path(dest
);
373 cm_return_val_if_fail(destpath
!= NULL
, NULL
);
375 if (dest
->last_num
< 0) {
376 mh_get_last_num(dest
->folder
, dest
);
377 if (dest
->last_num
< 0) return NULL
;
380 if (!is_dir_exist(destpath
))
381 make_dir_hier(destpath
);
384 destfile
= g_strdup_printf("%s%c%d", destpath
, G_DIR_SEPARATOR
,
386 if (is_file_entry_exist(destfile
)) {
398 static gint
mh_add_msg(Folder
*folder
, FolderItem
*dest
, const gchar
*file
, MsgFlags
*flags
)
402 MsgFileInfo fileinfo
;
404 cm_return_val_if_fail(file
!= NULL
, -1);
406 fileinfo
.msginfo
= NULL
;
407 fileinfo
.file
= (gchar
*)file
;
408 fileinfo
.flags
= flags
;
409 file_list
.data
= &fileinfo
;
410 file_list
.next
= NULL
;
412 ret
= mh_add_msgs(folder
, dest
, &file_list
, NULL
);
416 static gint
mh_add_msgs(Folder
*folder
, FolderItem
*dest
, GSList
*file_list
,
417 GHashTable
*relation
)
421 MsgFileInfo
*fileinfo
;
423 cm_return_val_if_fail(dest
!= NULL
, -1);
424 cm_return_val_if_fail(file_list
!= NULL
, -1);
426 if (dest
->last_num
< 0) {
427 mh_get_last_num(folder
, dest
);
428 if (dest
->last_num
< 0) return -1;
431 for (cur
= file_list
; cur
!= NULL
; cur
= cur
->next
) {
432 fileinfo
= (MsgFileInfo
*)cur
->data
;
434 destfile
= mh_get_new_msg_filename(dest
);
435 if (destfile
== NULL
) return -1;
438 if (link(fileinfo
->file
, destfile
) < 0) {
440 if (copy_file(fileinfo
->file
, destfile
, TRUE
) < 0) {
441 g_warning("can't copy message %s to %s",
442 fileinfo
->file
, destfile
);
450 if (relation
!= NULL
)
451 g_hash_table_insert(relation
, fileinfo
, GINT_TO_POINTER(dest
->last_num
+ 1));
455 mh_write_sequences(dest
, TRUE
);
456 return dest
->last_num
;
459 static gint
mh_copy_msg(Folder
*folder
, FolderItem
*dest
, MsgInfo
*msginfo
)
463 cm_return_val_if_fail(msginfo
!= NULL
, -1);
465 msglist
.data
= msginfo
;
468 return mh_copy_msgs(folder
, dest
, &msglist
, NULL
);
471 static gint
mh_copy_msgs(Folder
*folder
, FolderItem
*dest
, MsgInfoList
*msglist
,
472 GHashTable
*relation
)
474 gboolean dest_need_scan
= FALSE
;
475 gboolean src_need_scan
= FALSE
;
476 FolderItem
*src
= NULL
;
479 FolderItemPrefs
*prefs
;
480 MsgInfo
*msginfo
= NULL
;
481 MsgInfoList
*cur
= NULL
;
482 gint curnum
= 0, total
= 0;
483 gchar
*srcpath
= NULL
;
484 gboolean full_fetch
= FALSE
;
485 time_t last_dest_mtime
= (time_t)0;
486 time_t last_src_mtime
= (time_t)0;
488 cm_return_val_if_fail(dest
!= NULL
, -1);
489 cm_return_val_if_fail(msglist
!= NULL
, -1);
491 msginfo
= (MsgInfo
*)msglist
->data
;
493 cm_return_val_if_fail(msginfo
!= NULL
, -1);
495 if (msginfo
->folder
== dest
) {
496 g_warning("the src folder is identical to the dest.");
500 if (msginfo
->folder
->folder
!= dest
->folder
)
503 if (FOLDER_TYPE(msginfo
->folder
->folder
) == F_MH
) {
504 src
= msginfo
->folder
;
507 if (dest
->last_num
< 0) {
508 mh_get_last_num(folder
, dest
);
509 if (dest
->last_num
< 0) return -1;
514 srcpath
= folder_item_get_path(msginfo
->folder
);
516 dest_need_scan
= mh_scan_required(dest
->folder
, dest
);
517 last_dest_mtime
= dest
->mtime
;
520 src_need_scan
= mh_scan_required(src
->folder
, src
);
521 last_src_mtime
= src
->mtime
;
524 total
= g_slist_length(msglist
);
526 if (MSG_IS_MOVE(msginfo
->flags
))
527 statusbar_print_all(_("Moving messages..."));
529 statusbar_print_all(_("Copying messages..."));
531 for (cur
= msglist
; cur
; cur
= cur
->next
) {
532 msginfo
= (MsgInfo
*)cur
->data
;
534 goto err_reset_status
;
537 srcfile
= g_strconcat(srcpath
,
539 itos(msginfo
->msgnum
), NULL
);
541 srcfile
= procmsg_get_message_file(msginfo
);
544 goto err_reset_status
;
546 destfile
= mh_get_new_msg_filename(dest
);
549 goto err_reset_status
;
553 statusbar_progress_all(curnum
, total
, 100);
554 if (curnum
% 100 == 0)
559 debug_print("Copying message %s%c%d to %s ...\n",
560 msginfo
->folder
->path
, G_DIR_SEPARATOR
,
561 msginfo
->msgnum
, dest
->path
);
564 if (MSG_IS_MOVE(msginfo
->flags
)) {
565 msginfo
->flags
.tmp_flags
&= ~MSG_MOVE_DONE
;
566 if (move_file(srcfile
, destfile
, TRUE
) < 0) {
567 FILE_OP_ERROR(srcfile
, "move");
568 if (copy_file(srcfile
, destfile
, TRUE
) < 0) {
569 FILE_OP_ERROR(srcfile
, "copy");
572 goto err_reset_status
;
575 /* say unlinking's not necessary */
576 msginfo
->flags
.tmp_flags
|= MSG_MOVE_DONE
;
578 } else if (copy_file(srcfile
, destfile
, TRUE
) < 0) {
579 FILE_OP_ERROR(srcfile
, "copy");
582 goto err_reset_status
;
584 if (prefs
&& prefs
->enable_folder_chmod
&& prefs
->folder_chmod
) {
585 if (chmod(destfile
, prefs
->folder_chmod
) < 0)
586 FILE_OP_ERROR(destfile
, "chmod");
589 if (g_hash_table_lookup(relation
, msginfo
) != NULL
)
590 g_warning("already in: %p", msginfo
);
592 g_hash_table_insert(relation
, msginfo
, GINT_TO_POINTER(dest
->last_num
+1));
600 mh_write_sequences(dest
, TRUE
);
602 if (dest
->mtime
== last_dest_mtime
&& !dest_need_scan
) {
603 mh_set_mtime(folder
, dest
);
606 if (src
&& src
->mtime
== last_src_mtime
&& !src_need_scan
) {
607 mh_set_mtime(folder
, src
);
611 statusbar_progress_all(0,0,0);
614 return dest
->last_num
;
617 mh_write_sequences(dest
, TRUE
);
619 statusbar_progress_all(0,0,0);
626 static gint
mh_remove_msg(Folder
*folder
, FolderItem
*item
, gint num
)
628 gboolean need_scan
= FALSE
;
629 time_t last_mtime
= (time_t)0;
632 cm_return_val_if_fail(item
!= NULL
, -1);
634 file
= mh_fetch_msg(folder
, item
, num
);
635 cm_return_val_if_fail(file
!= NULL
, -1);
637 need_scan
= mh_scan_required(folder
, item
);
638 last_mtime
= item
->mtime
;
640 if (claws_unlink(file
) < 0) {
641 FILE_OP_ERROR(file
, "unlink");
646 if (item
->mtime
== last_mtime
&& !need_scan
) {
647 mh_set_mtime(folder
, item
);
653 static gint
mh_remove_msgs(Folder
*folder
, FolderItem
*item
,
654 MsgInfoList
*msglist
, GHashTable
*relation
)
656 gboolean need_scan
= FALSE
;
658 time_t last_mtime
= (time_t)0;
660 gint total
= 0, curnum
= 0;
662 cm_return_val_if_fail(item
!= NULL
, -1);
664 path
= folder_item_get_path(item
);
666 need_scan
= mh_scan_required(folder
, item
);
667 last_mtime
= item
->mtime
;
669 total
= g_slist_length(msglist
);
671 statusbar_print_all(_("Deleting messages..."));
674 for (cur
= msglist
; cur
; cur
= cur
->next
) {
675 MsgInfo
*msginfo
= (MsgInfo
*)cur
->data
;
678 if (MSG_IS_MOVE(msginfo
->flags
) && MSG_IS_MOVE_DONE(msginfo
->flags
)) {
679 msginfo
->flags
.tmp_flags
&= ~MSG_MOVE_DONE
;
683 statusbar_progress_all(curnum
, total
, 100);
684 if (curnum
% 100 == 0)
689 file
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(msginfo
->msgnum
), NULL
);
693 if (claws_unlink(file
) < 0) {
702 statusbar_progress_all(0,0,0);
705 if (item
->mtime
== last_mtime
&& !need_scan
) {
706 mh_set_mtime(folder
, item
);
713 static gint
mh_remove_all_msg(Folder
*folder
, FolderItem
*item
)
718 cm_return_val_if_fail(item
!= NULL
, -1);
720 path
= folder_item_get_path(item
);
721 cm_return_val_if_fail(path
!= NULL
, -1);
722 val
= remove_all_numbered_files(path
);
725 mh_write_sequences(item
, TRUE
);
730 static gboolean
mh_is_msg_changed(Folder
*folder
, FolderItem
*item
,
737 GError
*error
= NULL
;
745 parent_path
= folder_item_get_path(item
);
746 path
= g_strdup_printf("%s%c%d", parent_path
,
747 G_DIR_SEPARATOR
, msginfo
->msgnum
);
751 f
= g_file_new_for_path(path
);
753 fi
= g_file_query_info(f
, "standard::size,time::modified",
754 G_FILE_QUERY_INFO_NONE
, NULL
, &error
);
756 g_warning(error
->message
);
762 g_file_info_get_modification_time(fi
, &tv
);
763 if (msginfo
->size
!= g_file_info_get_size(fi
) || (
764 (msginfo
->mtime
- tv
.tv_sec
!= 0) &&
765 abs(msginfo
->mtime
- tv
.tv_sec
) != 3600)) {
771 r
= g_stat(path
, &s
);
774 msginfo
->size
!= s
.st_size
|| (
775 (msginfo
->mtime
- s
.st_mtime
!= 0) &&
776 (msginfo
->mtime
- s
.st_mtime
!= 3600) &&
777 (msginfo
->mtime
- s
.st_mtime
!= -3600))) {
785 static gint
mh_scan_tree(Folder
*folder
)
789 cm_return_val_if_fail(folder
!= NULL
, -1);
792 item
= folder_item_new(folder
, folder
->name
, NULL
);
793 item
->folder
= folder
;
794 folder
->node
= item
->node
= g_node_new(item
);
796 item
= FOLDER_ITEM(folder
->node
->data
);
798 mh_create_tree(folder
);
799 mh_remove_missing_folder_items(folder
);
800 mh_scan_tree_recursive(item
);
805 #define MAKE_DIR_IF_NOT_EXIST(dir) \
807 if (!is_dir_exist(dir)) { \
808 if (is_file_exist(dir)) { \
809 g_warning("File '%s' already exists. " \
810 "Can't create folder.", dir); \
813 if (make_dir_hier(dir) < 0) \
815 debug_print("Created dir '%s'\n", dir); \
819 static gint
mh_create_tree(Folder
*folder
)
821 gchar
*rootpath
, *f
, *path
;
823 cm_return_val_if_fail(folder
!= NULL
, -1);
825 rootpath
= LOCAL_FOLDER(folder
)->rootpath
;
827 if (*rootpath
== '/') {
828 #elif defined G_OS_WIN32
829 if (g_ascii_isalpha(*rootpath
) && !strncmp(rootpath
+ 1, ":\\", 2)) {
831 /* Folder path is absolute. */
832 rootpath
= g_strdup(rootpath
);
834 /* Folder path is relative, using mail base dir. */
835 rootpath
= g_strconcat(get_mail_base_dir(), G_DIR_SEPARATOR_S
,
839 MAKE_DIR_IF_NOT_EXIST(rootpath
);
841 /* Create special directories as needed */
842 if (folder
->inbox
!= NULL
&&
843 folder
->inbox
->path
!= NULL
)
844 f
= folder
->inbox
->path
;
847 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
848 MAKE_DIR_IF_NOT_EXIST(path
);
851 if (folder
->outbox
!= NULL
&&
852 folder
->outbox
->path
!= NULL
)
853 f
= folder
->outbox
->path
;
856 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
857 MAKE_DIR_IF_NOT_EXIST(path
);
860 if (folder
->draft
!= NULL
&&
861 folder
->draft
->path
!= NULL
)
862 f
= folder
->draft
->path
;
865 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
866 MAKE_DIR_IF_NOT_EXIST(path
);
869 if (folder
->queue
!= NULL
&&
870 folder
->queue
->path
!= NULL
)
871 f
= folder
->queue
->path
;
874 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
875 MAKE_DIR_IF_NOT_EXIST(path
);
878 if (folder
->trash
!= NULL
&&
879 folder
->trash
->path
!= NULL
)
880 f
= folder
->trash
->path
;
883 path
= g_strconcat(rootpath
, G_DIR_SEPARATOR_S
, f
, NULL
);
884 MAKE_DIR_IF_NOT_EXIST(path
);
891 #undef MAKE_DIR_IF_NOT_EXIST
893 static gchar
*mh_item_get_path(Folder
*folder
, FolderItem
*item
)
895 gchar
*folder_path
, *path
;
897 cm_return_val_if_fail(folder
!= NULL
, NULL
);
898 cm_return_val_if_fail(item
!= NULL
, NULL
);
900 folder_path
= g_strdup(LOCAL_FOLDER(folder
)->rootpath
);
901 cm_return_val_if_fail(folder_path
!= NULL
, NULL
);
903 /* FIXME: [W32] The code below does not correctly merge
904 relative filenames; there should be a function to handle
906 if ( !is_relative_filename (folder_path
) ) {
908 path
= g_strconcat(folder_path
, G_DIR_SEPARATOR_S
,
911 path
= g_strdup(folder_path
);
914 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
915 folder_path
, G_DIR_SEPARATOR_S
,
918 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
922 real_path
= mh_filename_from_utf8(path
);
923 if (!is_dir_exist(real_path
) && is_dir_exist(path
)) {
924 /* mmh, older version did put utf8 filenames instead of
925 * the correct encoding */
926 if (g_rename(path
, real_path
) == 0)
927 folder_item_scan(item
);
934 static gboolean
mh_renumber_msg(MsgInfo
*info
)
937 gboolean result
= FALSE
;
939 cm_return_val_if_fail(info
!= NULL
, FALSE
);
941 src
= folder_item_fetch_msg(info
->folder
, info
->msgnum
);
942 dest
= mh_get_new_msg_filename(info
->folder
);
943 num
= info
->folder
->last_num
+ 1;
945 if (move_file(src
, dest
, FALSE
) == 0) {
946 msgcache_remove_msg(info
->folder
->cache
, info
->msgnum
);
948 msgcache_add_msg(info
->folder
->cache
, info
);
958 static FolderItem
*mh_create_folder(Folder
*folder
, FolderItem
*parent
,
961 gchar
*path
, *real_name
;
963 FolderItem
*new_item
;
964 gchar
*mh_sequences_filename
;
965 FILE *mh_sequences_file
;
967 cm_return_val_if_fail(folder
!= NULL
, NULL
);
968 cm_return_val_if_fail(parent
!= NULL
, NULL
);
969 cm_return_val_if_fail(name
!= NULL
, NULL
);
971 path
= folder_item_get_path(parent
);
972 if (!is_dir_exist(path
))
973 if (make_dir_hier(path
) != 0)
976 real_name
= mh_filename_from_utf8(name
);
977 fullpath
= g_strconcat(path
, G_DIR_SEPARATOR_S
, real_name
, NULL
);
981 if (to_number(name
) > 0) {
982 MsgInfo
*info
= folder_item_get_msginfo(parent
, to_number(name
));
984 gboolean ok
= mh_renumber_msg(info
);
985 procmsg_msginfo_free(&info
);
993 if (make_dir(fullpath
) < 0) {
1001 path
= g_strconcat(parent
->path
, G_DIR_SEPARATOR_S
, name
,
1004 path
= g_strdup(name
);
1005 new_item
= folder_item_new(folder
, name
, path
);
1006 folder_item_append(parent
, new_item
);
1010 path
= folder_item_get_path(new_item
);
1011 mh_sequences_filename
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1012 ".mh_sequences", NULL
);
1013 if ((mh_sequences_file
= g_fopen(mh_sequences_filename
, "a+b")) != NULL
) {
1014 fclose(mh_sequences_file
);
1016 g_free(mh_sequences_filename
);
1022 static gint
mh_rename_folder(Folder
*folder
, FolderItem
*item
,
1028 gchar
*newpath
, *utf8newpath
;
1031 cm_return_val_if_fail(folder
!= NULL
, -1);
1032 cm_return_val_if_fail(item
!= NULL
, -1);
1033 cm_return_val_if_fail(item
->path
!= NULL
, -1);
1034 cm_return_val_if_fail(name
!= NULL
, -1);
1036 oldpath
= folder_item_get_path(item
);
1037 if (!is_dir_exist(oldpath
))
1038 make_dir_hier(oldpath
);
1040 dirname
= g_path_get_dirname(oldpath
);
1041 real_name
= mh_filename_from_utf8(name
);
1042 newpath
= g_strconcat(dirname
, G_DIR_SEPARATOR_S
, real_name
, NULL
);
1045 if (g_rename(oldpath
, newpath
) < 0) {
1046 FILE_OP_ERROR(oldpath
, "rename");
1055 if (strchr(item
->path
, G_DIR_SEPARATOR
) != NULL
) {
1056 dirname
= g_path_get_dirname(item
->path
);
1057 utf8newpath
= g_strconcat(dirname
, G_DIR_SEPARATOR_S
,
1061 utf8newpath
= g_strdup(name
);
1064 item
->name
= g_strdup(name
);
1066 paths
[0] = g_strdup(item
->path
);
1067 paths
[1] = utf8newpath
;
1068 g_node_traverse(item
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
1069 mh_rename_folder_func
, paths
);
1076 static gint
mh_remove_folder(Folder
*folder
, FolderItem
*item
)
1081 cm_return_val_if_fail(folder
!= NULL
, -1);
1082 cm_return_val_if_fail(item
!= NULL
, -1);
1083 cm_return_val_if_fail(item
->path
!= NULL
, -1);
1085 path
= folder_item_get_path(item
);
1086 if ((ret
= remove_dir_recursive(path
)) < 0) {
1087 g_warning("can't remove directory '%s'", path
);
1093 folder_item_remove(item
);
1097 static MsgInfo
*mh_parse_msg(const gchar
*file
, FolderItem
*item
)
1102 cm_return_val_if_fail(item
!= NULL
, NULL
);
1103 cm_return_val_if_fail(file
!= NULL
, NULL
);
1105 flags
.perm_flags
= MSG_NEW
|MSG_UNREAD
;
1106 flags
.tmp_flags
= 0;
1108 if (folder_has_parent_of_type(item
, F_QUEUE
)) {
1109 MSG_SET_TMP_FLAGS(flags
, MSG_QUEUED
);
1110 } else if (folder_has_parent_of_type(item
, F_DRAFT
)) {
1111 MSG_SET_TMP_FLAGS(flags
, MSG_DRAFT
);
1114 msginfo
= procheader_parse_file(file
, flags
, FALSE
, FALSE
);
1115 if (!msginfo
) return NULL
;
1117 msginfo
->msgnum
= atoi(file
);
1118 msginfo
->folder
= item
;
1123 static gboolean
mh_remove_missing_folder_items_func(GNode
*node
, gpointer data
)
1128 cm_return_val_if_fail(node
->data
!= NULL
, FALSE
);
1130 if (G_NODE_IS_ROOT(node
))
1133 item
= FOLDER_ITEM(node
->data
);
1135 path
= folder_item_get_path(item
);
1136 if (!is_dir_exist(path
)) {
1137 debug_print("folder '%s' not found. removing...\n", path
?path
:"(null)");
1138 folder_item_remove(item
);
1145 static void mh_remove_missing_folder_items(Folder
*folder
)
1147 cm_return_if_fail(folder
!= NULL
);
1149 debug_print("searching missing folders...\n");
1151 g_node_traverse(folder
->node
, G_POST_ORDER
, G_TRAVERSE_ALL
, -1,
1152 mh_remove_missing_folder_items_func
, folder
);
1155 static void mh_scan_tree_recursive(FolderItem
*item
)
1159 const gchar
*dir_name
;
1160 gchar
*entry
, *utf8entry
, *utf8name
, *path
;
1162 GError
*error
= NULL
;
1164 cm_return_if_fail(item
!= NULL
);
1165 cm_return_if_fail(item
->folder
!= NULL
);
1167 folder
= item
->folder
;
1169 path
= folder_item_get_path(item
);
1170 debug_print("mh_scan_tree_recursive() opening '%s'\n", path
);
1171 dir
= g_dir_open(path
, 0, &error
);
1173 g_warning("failed to open directory '%s': %s (%d)",
1174 path
, error
->message
, error
->code
);
1175 g_error_free(error
);
1180 debug_print("scanning %s ...\n",
1181 item
->path
? item
->path
1182 : LOCAL_FOLDER(item
->folder
)->rootpath
);
1183 if (folder
->ui_func
)
1184 folder
->ui_func(folder
, item
, folder
->ui_func_data
);
1186 while ((dir_name
= g_dir_read_name(dir
)) != NULL
) {
1187 if (dir_name
[0] == '.') continue;
1189 entry
= g_strconcat(path
, G_DIR_SEPARATOR_S
, dir_name
, NULL
);
1191 utf8name
= mh_filename_to_utf8(dir_name
);
1193 utf8entry
= g_strconcat(item
->path
, G_DIR_SEPARATOR_S
,
1196 utf8entry
= g_strdup(utf8name
);
1198 if (g_file_test(entry
, G_FILE_TEST_IS_DIR
)) {
1199 FolderItem
*new_item
= NULL
;
1203 for (node
= node
->children
; node
!= NULL
; node
= node
->next
) {
1204 FolderItem
*cur_item
= FOLDER_ITEM(node
->data
);
1205 gchar
*curpath
= folder_item_get_path(cur_item
);
1206 if (!strcmp2(curpath
, entry
)) {
1207 new_item
= cur_item
;
1214 debug_print("new folder '%s' found.\n", entry
);
1215 new_item
= folder_item_new(folder
, utf8name
, utf8entry
);
1216 folder_item_append(item
, new_item
);
1220 if (!folder
->inbox
&&
1221 !strcmp(dir_name
, INBOX_DIR
)) {
1222 new_item
->stype
= F_INBOX
;
1223 folder
->inbox
= new_item
;
1224 } else if (!folder
->outbox
&&
1225 !strcmp(dir_name
, OUTBOX_DIR
)) {
1226 new_item
->stype
= F_OUTBOX
;
1227 folder
->outbox
= new_item
;
1228 } else if (!folder
->draft
&&
1229 !strcmp(dir_name
, DRAFT_DIR
)) {
1230 new_item
->stype
= F_DRAFT
;
1231 folder
->draft
= new_item
;
1232 } else if (!folder
->queue
&&
1233 !strcmp(dir_name
, QUEUE_DIR
)) {
1234 new_item
->stype
= F_QUEUE
;
1235 folder
->queue
= new_item
;
1236 } else if (!folder
->trash
&&
1237 !strcmp(dir_name
, TRASH_DIR
)) {
1238 new_item
->stype
= F_TRASH
;
1239 folder
->trash
= new_item
;
1243 mh_scan_tree_recursive(new_item
);
1244 } else if (to_number(dir_name
) > 0) n_msg
++;
1254 mh_set_mtime(folder
, item
);
1257 static gboolean
mh_rename_folder_func(GNode
*node
, gpointer data
)
1259 FolderItem
*item
= node
->data
;
1260 gchar
**paths
= data
;
1261 const gchar
*oldpath
= paths
[0];
1262 const gchar
*newpath
= paths
[1];
1264 gchar
*new_itempath
;
1267 oldpathlen
= strlen(oldpath
);
1268 if (strncmp(oldpath
, item
->path
, oldpathlen
) != 0) {
1269 g_warning("path doesn't match: %s, %s", oldpath
, item
->path
);
1273 base
= item
->path
+ oldpathlen
;
1274 while (*base
== G_DIR_SEPARATOR
) base
++;
1276 new_itempath
= g_strdup(newpath
);
1278 new_itempath
= g_strconcat(newpath
, G_DIR_SEPARATOR_S
, base
,
1281 item
->path
= new_itempath
;
1286 static gchar
*mh_filename_from_utf8(const gchar
*path
)
1288 gchar
*real_path
= g_filename_from_utf8(path
, -1, NULL
, NULL
, NULL
);
1291 g_warning("mh_filename_from_utf8: failed to convert character set");
1292 real_path
= g_strdup(path
);
1298 static gchar
*mh_filename_to_utf8(const gchar
*path
)
1300 gchar
*utf8path
= g_filename_to_utf8(path
, -1, NULL
, NULL
, NULL
);
1302 g_warning("mh_filename_to_utf8: failed to convert character set");
1303 utf8path
= g_strdup(path
);
1309 static gint
sort_cache_list_by_msgnum(gconstpointer a
, gconstpointer b
)
1311 MsgInfo
*msginfo_a
= (MsgInfo
*) a
;
1312 MsgInfo
*msginfo_b
= (MsgInfo
*) b
;
1314 return (msginfo_a
->msgnum
- msginfo_b
->msgnum
);
1317 static gchar
*get_unseen_seq_name(void)
1319 static gchar
*seq_name
= NULL
;
1321 gchar buf
[BUFFSIZE
];
1323 gchar
*profile_path
= g_strconcat(
1324 get_home_dir(), G_DIR_SEPARATOR_S
,
1325 ".mh_profile", NULL
);
1326 FILE *fp
= g_fopen(profile_path
, "r");
1328 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
1329 if (!strncmp(buf
, "Unseen-Sequence:", strlen("Unseen-Sequence:"))) {
1330 gchar
*seq_tmp
= buf
+strlen("Unseen-Sequence:");
1331 while (*seq_tmp
== ' ')
1333 seq_name
= g_strdup(seq_tmp
);
1334 seq_name
= strretchomp(seq_name
);
1341 seq_name
= g_strdup("unseen");
1342 tmp
= g_strdup_printf("%s:", seq_name
);
1349 static void mh_write_sequences(FolderItem
*item
, gboolean remove_unseen
)
1351 gchar
*mh_sequences_old
, *mh_sequences_new
;
1352 FILE *mh_sequences_old_fp
, *mh_sequences_new_fp
;
1353 gchar buf
[BUFFSIZE
];
1355 gboolean err
= FALSE
;
1361 path
= folder_item_get_path(item
);
1363 mh_sequences_old
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1364 ".mh_sequences", NULL
);
1365 mh_sequences_new
= g_strconcat(path
, G_DIR_SEPARATOR_S
,
1366 ".mh_sequences.new", NULL
);
1367 if ((mh_sequences_new_fp
= g_fopen(mh_sequences_new
, "w+b")) != NULL
) {
1368 GSList
*msglist
= folder_item_get_msg_list(item
);
1370 MsgInfo
*info
= NULL
;
1371 gint start
= -1, end
= -1;
1372 gchar
*sequence
= g_strdup("");
1374 msglist
= g_slist_sort(msglist
, sort_cache_list_by_msgnum
);
1377 /* write the unseen sequence if we don't have to scrap it */
1378 if (!remove_unseen
) do {
1379 info
= (MsgInfo
*)(cur
? cur
->data
:NULL
);
1380 if (info
&& (MSG_IS_UNREAD(info
->flags
) || MSG_IS_NEW(info
->flags
))) {
1382 start
= end
= info
->msgnum
;
1386 if (start
> 0 && end
> 0) {
1390 snprintf(tmp
, 31, " %d-%d", start
, end
);
1392 snprintf(tmp
, 31, " %d", start
);
1394 tmp_len
= strlen(tmp
);
1395 sequence
= g_realloc(sequence
, seq_len
+tmp_len
+1);
1396 strcpy(sequence
+seq_len
, tmp
);
1402 cur
= cur
? cur
->next
:NULL
;
1403 } while (cur
|| (start
> 0 && end
> 0));
1404 if (sequence
&& *sequence
) {
1405 if (fprintf(mh_sequences_new_fp
, "%s%s\n",
1406 get_unseen_seq_name(), sequence
) < 0)
1409 debug_print("wrote unseen sequence: '%s%s'\n",
1410 get_unseen_seq_name(), sequence
);
1412 /* rewrite the rest of the file */
1413 if ((mh_sequences_old_fp
= g_fopen(mh_sequences_old
, "r+b")) != NULL
) {
1414 while (fgets(buf
, sizeof(buf
), mh_sequences_old_fp
) != NULL
) {
1415 if (strncmp(buf
, get_unseen_seq_name(), strlen(get_unseen_seq_name())))
1416 if (fprintf(mh_sequences_new_fp
, "%s", buf
) < 0) {
1421 fclose(mh_sequences_old_fp
);
1424 fflush(mh_sequences_new_fp
);
1426 fsync(fileno(mh_sequences_new_fp
));
1428 if (fclose(mh_sequences_new_fp
) == EOF
)
1432 if (g_rename(mh_sequences_new
, mh_sequences_old
) < 0)
1433 FILE_OP_ERROR(mh_sequences_new
, "rename");
1436 procmsg_msg_list_free(msglist
);
1438 g_free(mh_sequences_old
);
1439 g_free(mh_sequences_new
);
1445 static int mh_item_close(Folder
*folder
, FolderItem
*item
)
1447 time_t last_mtime
= (time_t)0;
1448 gboolean need_scan
= mh_scan_required(item
->folder
, item
);
1449 last_mtime
= item
->mtime
;
1451 mh_write_sequences(item
, FALSE
);
1453 if (item
->mtime
== last_mtime
&& !need_scan
) {
1454 mh_set_mtime(folder
, item
);
1460 static void mh_set_mtime(Folder
*folder
, FolderItem
*item
)
1463 gchar
*path
= folder_item_get_path(item
);
1465 cm_return_if_fail(path
!= NULL
);
1467 if (g_stat(path
, &s
) < 0) {
1468 FILE_OP_ERROR(path
, "stat");
1473 item
->mtime
= s
.st_mtime
;
1474 debug_print("MH: forced mtime of %s to %ld\n", item
->name
?item
->name
:"(null)", item
->mtime
);