talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

mh.c (30853B)


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