evolution-3.6.4/libemail-engine/e-mail-folder-utils.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found e-mail-folder-utils.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
clang-analyzer no-output-found e-mail-folder-utils.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
   1 /*
   2  * e-mail-folder-utils.c
   3  *
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) version 3.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16  *
  17  */
  18 
  19 #ifdef HAVE_CONFIG_H
  20 #include <config.h>
  21 #endif
  22 
  23 #include "e-mail-folder-utils.h"
  24 
  25 #include <glib/gi18n-lib.h>
  26 
  27 #include <libedataserver/libedataserver.h>
  28 
  29 #include <libemail-engine/e-mail-session.h>
  30 #include <libemail-engine/mail-tools.h>
  31 
  32 #include "e-mail-utils.h"
  33 
  34 /* X-Mailer header value */
  35 #define X_MAILER ("Evolution " VERSION SUB_VERSION " " VERSION_COMMENT)
  36 
  37 typedef struct _AsyncContext AsyncContext;
  38 
  39 struct _AsyncContext {
  40 	CamelMimeMessage *message;
  41 	CamelMessageInfo *info;
  42 	CamelMimePart *part;
  43 	GHashTable *hash_table;
  44 	GPtrArray *ptr_array;
  45 	GFile *destination;
  46 	gchar *fwd_subject;
  47 	gchar *message_uid;
  48 };
  49 
  50 static void
  51 async_context_free (AsyncContext *context)
  52 {
  53 	if (context->message != NULL)
  54 		g_object_unref (context->message);
  55 
  56 	if (context->info != NULL)
  57 		camel_message_info_free (context->info);
  58 
  59 	if (context->part != NULL)
  60 		g_object_unref (context->part);
  61 
  62 	if (context->hash_table != NULL)
  63 		g_hash_table_unref (context->hash_table);
  64 
  65 	if (context->ptr_array != NULL)
  66 		g_ptr_array_unref (context->ptr_array);
  67 
  68 	if (context->destination != NULL)
  69 		g_object_unref (context->destination);
  70 
  71 	g_free (context->fwd_subject);
  72 	g_free (context->message_uid);
  73 
  74 	g_slice_free (AsyncContext, context);
  75 }
  76 
  77 static void
  78 mail_folder_append_message_thread (GSimpleAsyncResult *simple,
  79                                    GObject *object,
  80                                    GCancellable *cancellable)
  81 {
  82 	AsyncContext *context;
  83 	GError *error = NULL;
  84 
  85 	context = g_simple_async_result_get_op_res_gpointer (simple);
  86 
  87 	e_mail_folder_append_message_sync (
  88 		CAMEL_FOLDER (object), context->message,
  89 		context->info, &context->message_uid,
  90 		cancellable, &error);
  91 
  92 	if (error != NULL)
  93 		g_simple_async_result_take_error (simple, error);
  94 }
  95 
  96 gboolean
  97 e_mail_folder_append_message_sync (CamelFolder *folder,
  98                                    CamelMimeMessage *message,
  99                                    CamelMessageInfo *info,
 100                                    gchar **appended_uid,
 101                                    GCancellable *cancellable,
 102                                    GError **error)
 103 {
 104 	CamelMedium *medium;
 105 	gboolean success;
 106 
 107 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
 108 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
 109 
 110 	medium = CAMEL_MEDIUM (message);
 111 
 112 	camel_operation_push_message (
 113 		cancellable,
 114 		_("Saving message to folder '%s'"),
 115 		camel_folder_get_display_name (folder));
 116 
 117 	if (camel_medium_get_header (medium, "X-Mailer") == NULL)
 118 		camel_medium_set_header (medium, "X-Mailer", X_MAILER);
 119 
 120 	camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
 121 
 122 	success = camel_folder_append_message_sync (
 123 		folder, message, info, appended_uid, cancellable, error);
 124 
 125 	camel_operation_pop_message (cancellable);
 126 
 127 	return success;
 128 }
 129 
 130 void
 131 e_mail_folder_append_message (CamelFolder *folder,
 132                               CamelMimeMessage *message,
 133                               CamelMessageInfo *info,
 134                               gint io_priority,
 135                               GCancellable *cancellable,
 136                               GAsyncReadyCallback callback,
 137                               gpointer user_data)
 138 {
 139 	GSimpleAsyncResult *simple;
 140 	AsyncContext *context;
 141 
 142 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
 143 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
 144 
 145 	context = g_slice_new0 (AsyncContext);
 146 	context->message = g_object_ref (message);
 147 
 148 	if (info != NULL)
 149 		context->info = camel_message_info_ref (info);
 150 
 151 	simple = g_simple_async_result_new (
 152 		G_OBJECT (folder), callback, user_data,
 153 		e_mail_folder_append_message);
 154 
 155 	g_simple_async_result_set_check_cancellable (simple, cancellable);
 156 
 157 	g_simple_async_result_set_op_res_gpointer (
 158 		simple, context, (GDestroyNotify) async_context_free);
 159 
 160 	g_simple_async_result_run_in_thread (
 161 		simple, mail_folder_append_message_thread,
 162 		io_priority, cancellable);
 163 
 164 	g_object_unref (simple);
 165 }
 166 
 167 gboolean
 168 e_mail_folder_append_message_finish (CamelFolder *folder,
 169                                      GAsyncResult *result,
 170                                      gchar **appended_uid,
 171                                      GError **error)
 172 {
 173 	GSimpleAsyncResult *simple;
 174 	AsyncContext *context;
 175 
 176 	g_return_val_if_fail (
 177 		g_simple_async_result_is_valid (
 178 		result, G_OBJECT (folder),
 179 		e_mail_folder_append_message), FALSE);
 180 
 181 	simple = G_SIMPLE_ASYNC_RESULT (result);
 182 	context = g_simple_async_result_get_op_res_gpointer (simple);
 183 
 184 	if (appended_uid != NULL) {
 185 		*appended_uid = context->message_uid;
 186 		context->message_uid = NULL;
 187 	}
 188 
 189 	/* Assume success unless a GError is set. */
 190 	return !g_simple_async_result_propagate_error (simple, error);
 191 }
 192 
 193 static void
 194 mail_folder_expunge_thread (GSimpleAsyncResult *simple,
 195                             GObject *object,
 196                             GCancellable *cancellable)
 197 {
 198 	GError *error = NULL;
 199 
 200 	e_mail_folder_expunge_sync (
 201 		CAMEL_FOLDER (object), cancellable, &error);
 202 
 203 	if (error != NULL)
 204 		g_simple_async_result_take_error (simple, error);
 205 }
 206 
 207 static gboolean
 208 mail_folder_expunge_pop3_stores (CamelFolder *folder,
 209                                  GCancellable *cancellable,
 210                                  GError **error)
 211 {
 212 	GHashTable *expunging_uids;
 213 	CamelStore *parent_store;
 214 	CamelService *service;
 215 	CamelSession *session;
 216 	ESourceRegistry *registry;
 217 	GPtrArray *uids;
 218 	GList *list, *link;
 219 	const gchar *extension_name;
 220 	gboolean success = TRUE;
 221 	guint ii;
 222 
 223 	parent_store = camel_folder_get_parent_store (folder);
 224 
 225 	service = CAMEL_SERVICE (parent_store);
 226 	session = camel_service_get_session (service);
 227 	registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
 228 
 229 	uids = camel_folder_get_uids (folder);
 230 
 231 	if (uids == NULL)
 232 		return TRUE;
 233 
 234 	expunging_uids = g_hash_table_new_full (
 235 		(GHashFunc) g_str_hash,
 236 		(GEqualFunc) g_str_equal,
 237 		(GDestroyNotify) g_free,
 238 		(GDestroyNotify) g_free);
 239 
 240 	for (ii = 0; ii < uids->len; ii++) {
 241 		CamelMessageInfo *info;
 242 		CamelMessageFlags flags = 0;
 243 		CamelMimeMessage *message;
 244 		const gchar *pop3_uid;
 245 		const gchar *source_uid;
 246 
 247 		info = camel_folder_get_message_info (
 248 			folder, uids->pdata[ii]);
 249 
 250 		if (info != NULL) {
 251 			flags = camel_message_info_flags (info);
 252 			camel_folder_free_message_info (folder, info);
 253 		}
 254 
 255 		/* Only interested in deleted messages. */
 256 		if ((flags & CAMEL_MESSAGE_DELETED) == 0)
 257 			continue;
 258 
 259 		/* because the UID in the local store doesn't
 260 		 * match with the UID in the pop3 store */
 261 		message = camel_folder_get_message_sync (
 262 			folder, uids->pdata[ii], cancellable, NULL);
 263 
 264 		if (message == NULL)
 265 			continue;
 266 
 267 		pop3_uid = camel_medium_get_header (
 268 			CAMEL_MEDIUM (message), "X-Evolution-POP3-UID");
 269 		source_uid = camel_mime_message_get_source (message);
 270 
 271 		if (pop3_uid != NULL)
 272 			g_hash_table_insert (
 273 				expunging_uids,
 274 				g_strstrip (g_strdup (pop3_uid)),
 275 				g_strstrip (g_strdup (source_uid)));
 276 
 277 		g_object_unref (message);
 278 	}
 279 
 280 	camel_folder_free_uids (folder, uids);
 281 	uids = NULL;
 282 
 283 	if (g_hash_table_size (expunging_uids) == 0) {
 284 		g_hash_table_destroy (expunging_uids);
 285 		return TRUE;
 286 	}
 287 
 288 	extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
 289 	list = e_source_registry_list_sources (registry, extension_name);
 290 
 291 	for (link = list; link != NULL; link = g_list_next (link)) {
 292 		ESource *source = E_SOURCE (link->data);
 293 		ESourceBackend *extension;
 294 		CamelFolder *folder;
 295 		CamelService *service;
 296 		CamelSettings *settings;
 297 		const gchar *backend_name;
 298 		const gchar *service_uid;
 299 		const gchar *source_uid;
 300 		gboolean any_found = FALSE;
 301 		gboolean delete_expunged = FALSE;
 302 		gboolean keep_on_server = FALSE;
 303 
 304 		source_uid = e_source_get_uid (source);
 305 
 306 		extension = e_source_get_extension (source, extension_name);
 307 		backend_name = e_source_backend_get_backend_name (extension);
 308 
 309 		if (!em_utils_is_source_enabled_with_parents (registry, source) ||
 310 		    g_strcmp0 (backend_name, "pop") != 0)
 311 			continue;
 312 
 313 		service = camel_session_ref_service (
 314 			CAMEL_SESSION (session), source_uid);
 315 
 316 		service_uid = camel_service_get_uid (service);
 317 		settings = camel_service_ref_settings (service);
 318 
 319 		g_object_get (
 320 			settings,
 321 			"delete-expunged", &delete_expunged,
 322 			"keep-on-server", &keep_on_server,
 323 			NULL);
 324 
 325 		g_object_unref (settings);
 326 
 327 		if (!keep_on_server || !delete_expunged) {
 328 			g_object_unref (service);
 329 			continue;
 330 		}
 331 
 332 		folder = camel_store_get_inbox_folder_sync (
 333 			CAMEL_STORE (service), cancellable, error);
 334 
 335 		/* Abort the loop on error. */
 336 		if (folder == NULL) {
 337 			g_object_unref (service);
 338 			success = FALSE;
 339 			break;
 340 		}
 341 
 342 		uids = camel_folder_get_uids (folder);
 343 
 344 		if (uids == NULL) {
 345 			g_object_unref (service);
 346 			g_object_unref (folder);
 347 			continue;
 348 		}
 349 
 350 		for (ii = 0; ii < uids->len; ii++) {
 351 			const gchar *source_uid;
 352 
 353 			source_uid = g_hash_table_lookup (
 354 				expunging_uids, uids->pdata[ii]);
 355 			if (g_strcmp0 (source_uid, service_uid) == 0) {
 356 				any_found = TRUE;
 357 				camel_folder_delete_message (
 358 					folder, uids->pdata[ii]);
 359 			}
 360 		}
 361 
 362 		camel_folder_free_uids (folder, uids);
 363 
 364 		if (any_found)
 365 			success = camel_folder_synchronize_sync (
 366 				folder, TRUE, cancellable, error);
 367 
 368 		g_object_unref (folder);
 369 		g_object_unref (service);
 370 
 371 		/* Abort the loop on error. */
 372 		if (!success)
 373 			break;
 374 	}
 375 
 376 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
 377 
 378 	g_hash_table_destroy (expunging_uids);
 379 
 380 	return success;
 381 }
 382 
 383 gboolean
 384 e_mail_folder_expunge_sync (CamelFolder *folder,
 385                             GCancellable *cancellable,
 386                             GError **error)
 387 {
 388 	CamelFolder *local_inbox;
 389 	CamelStore *parent_store;
 390 	CamelService *service;
 391 	CamelSession *session;
 392 	gboolean is_local_inbox;
 393 	gboolean is_local_trash;
 394 	gboolean store_is_local;
 395 	gboolean success = TRUE;
 396 	const gchar *uid;
 397 
 398 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
 399 
 400 	parent_store = camel_folder_get_parent_store (folder);
 401 
 402 	service = CAMEL_SERVICE (parent_store);
 403 	session = camel_service_get_session (service);
 404 
 405 	uid = camel_service_get_uid (service);
 406 	store_is_local = (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0);
 407 
 408 	local_inbox = e_mail_session_get_local_folder (
 409 		E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_INBOX);
 410 	is_local_inbox = (folder == local_inbox);
 411 	is_local_trash = FALSE;
 412 
 413 	if (store_is_local && !is_local_inbox) {
 414 		CamelFolder *local_trash;
 415 
 416 		local_trash = camel_store_get_trash_folder_sync (
 417 			parent_store, cancellable, error);
 418 
 419 		if (local_trash != NULL) {
 420 			is_local_trash = (folder == local_trash);
 421 			g_object_unref (local_trash);
 422 		} else {
 423 			return FALSE;
 424 		}
 425 	}
 426 
 427 	/* Expunge all POP3 accounts when expunging
 428 	 * the local Inbox or Trash folder. */
 429 	if (is_local_inbox || is_local_trash)
 430 		success = mail_folder_expunge_pop3_stores (
 431 			folder, cancellable, error);
 432 
 433 	if (success)
 434 		success = camel_folder_expunge_sync (
 435 			folder, cancellable, error);
 436 
 437 	return success;
 438 }
 439 
 440 void
 441 e_mail_folder_expunge (CamelFolder *folder,
 442                        gint io_priority,
 443                        GCancellable *cancellable,
 444                        GAsyncReadyCallback callback,
 445                        gpointer user_data)
 446 {
 447 	GSimpleAsyncResult *simple;
 448 
 449 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
 450 
 451 	simple = g_simple_async_result_new (
 452 		G_OBJECT (folder), callback,
 453 		user_data, e_mail_folder_expunge);
 454 
 455 	g_simple_async_result_set_check_cancellable (simple, cancellable);
 456 
 457 	g_simple_async_result_run_in_thread (
 458 		simple, mail_folder_expunge_thread,
 459 		io_priority, cancellable);
 460 
 461 	g_object_unref (simple);
 462 }
 463 
 464 gboolean
 465 e_mail_folder_expunge_finish (CamelFolder *folder,
 466                               GAsyncResult *result,
 467                               GError **error)
 468 {
 469 	GSimpleAsyncResult *simple;
 470 
 471 	g_return_val_if_fail (
 472 		g_simple_async_result_is_valid (
 473 		result, G_OBJECT (folder),
 474 		e_mail_folder_expunge), FALSE);
 475 
 476 	simple = G_SIMPLE_ASYNC_RESULT (result);
 477 
 478 	/* Assume success unless a GError is set. */
 479 	return !g_simple_async_result_propagate_error (simple, error);
 480 }
 481 
 482 static void
 483 mail_folder_build_attachment_thread (GSimpleAsyncResult *simple,
 484                                      GObject *object,
 485                                      GCancellable *cancellable)
 486 {
 487 	AsyncContext *context;
 488 	GError *error = NULL;
 489 
 490 	context = g_simple_async_result_get_op_res_gpointer (simple);
 491 
 492 	context->part = e_mail_folder_build_attachment_sync (
 493 		CAMEL_FOLDER (object), context->ptr_array,
 494 		&context->fwd_subject, cancellable, &error);
 495 
 496 	if (error != NULL)
 497 		g_simple_async_result_take_error (simple, error);
 498 }
 499 
 500 CamelMimePart *
 501 e_mail_folder_build_attachment_sync (CamelFolder *folder,
 502                                      GPtrArray *message_uids,
 503                                      gchar **fwd_subject,
 504                                      GCancellable *cancellable,
 505                                      GError **error)
 506 {
 507 	GHashTable *hash_table;
 508 	CamelMimeMessage *message;
 509 	CamelMimePart *part;
 510 	const gchar *uid;
 511 
 512 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
 513 	g_return_val_if_fail (message_uids != NULL, NULL);
 514 
 515 	/* Need at least one message UID to make an attachment. */
 516 	g_return_val_if_fail (message_uids->len > 0, NULL);
 517 
 518 	hash_table = e_mail_folder_get_multiple_messages_sync (
 519 		folder, message_uids, cancellable, error);
 520 
 521 	if (hash_table == NULL)
 522 		return NULL;
 523 
 524 	/* Create the forward subject from the first message. */
 525 
 526 	uid = g_ptr_array_index (message_uids, 0);
 527 	g_return_val_if_fail (uid != NULL, NULL);
 528 
 529 	message = g_hash_table_lookup (hash_table, uid);
 530 	g_return_val_if_fail (message != NULL, NULL);
 531 
 532 	if (fwd_subject != NULL)
 533 		*fwd_subject = mail_tool_generate_forward_subject (message);
 534 
 535 	if (message_uids->len == 1) {
 536 		part = mail_tool_make_message_attachment (message);
 537 
 538 	} else {
 539 		CamelMultipart *multipart;
 540 		guint ii;
 541 
 542 		multipart = camel_multipart_new ();
 543 		camel_data_wrapper_set_mime_type (
 544 			CAMEL_DATA_WRAPPER (multipart), "multipart/digest");
 545 		camel_multipart_set_boundary (multipart, NULL);
 546 
 547 		for (ii = 0; ii < message_uids->len; ii++) {
 548 			uid = g_ptr_array_index (message_uids, ii);
 549 			g_return_val_if_fail (uid != NULL, NULL);
 550 
 551 			message = g_hash_table_lookup (hash_table, uid);
 552 			g_return_val_if_fail (message != NULL, NULL);
 553 
 554 			part = mail_tool_make_message_attachment (message);
 555 			camel_multipart_add_part (multipart, part);
 556 			g_object_unref (part);
 557 		}
 558 
 559 		part = camel_mime_part_new ();
 560 
 561 		camel_medium_set_content (
 562 			CAMEL_MEDIUM (part),
 563 			CAMEL_DATA_WRAPPER (multipart));
 564 
 565 		camel_mime_part_set_description (
 566 			part, _("Forwarded messages"));
 567 
 568 		g_object_unref (multipart);
 569 	}
 570 
 571 	g_hash_table_unref (hash_table);
 572 
 573 	return part;
 574 }
 575 
 576 void
 577 e_mail_folder_build_attachment (CamelFolder *folder,
 578                                 GPtrArray *message_uids,
 579                                 gint io_priority,
 580                                 GCancellable *cancellable,
 581                                 GAsyncReadyCallback callback,
 582                                 gpointer user_data)
 583 {
 584 	GSimpleAsyncResult *simple;
 585 	AsyncContext *context;
 586 
 587 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
 588 	g_return_if_fail (message_uids != NULL);
 589 
 590 	/* Need at least one message UID to make an attachment. */
 591 	g_return_if_fail (message_uids->len > 0);
 592 
 593 	context = g_slice_new0 (AsyncContext);
 594 	context->ptr_array = g_ptr_array_ref (message_uids);
 595 
 596 	simple = g_simple_async_result_new (
 597 		G_OBJECT (folder), callback, user_data,
 598 		e_mail_folder_build_attachment);
 599 
 600 	g_simple_async_result_set_check_cancellable (simple, cancellable);
 601 
 602 	g_simple_async_result_set_op_res_gpointer (
 603 		simple, context, (GDestroyNotify) async_context_free);
 604 
 605 	g_simple_async_result_run_in_thread (
 606 		simple, mail_folder_build_attachment_thread,
 607 		io_priority, cancellable);
 608 
 609 	g_object_unref (simple);
 610 }
 611 
 612 CamelMimePart *
 613 e_mail_folder_build_attachment_finish (CamelFolder *folder,
 614                                        GAsyncResult *result,
 615                                        gchar **fwd_subject,
 616                                        GError **error)
 617 {
 618 	GSimpleAsyncResult *simple;
 619 	AsyncContext *context;
 620 
 621 	g_return_val_if_fail (
 622 		g_simple_async_result_is_valid (
 623 		result, G_OBJECT (folder),
 624 		e_mail_folder_build_attachment), NULL);
 625 
 626 	simple = G_SIMPLE_ASYNC_RESULT (result);
 627 	context = g_simple_async_result_get_op_res_gpointer (simple);
 628 
 629 	if (g_simple_async_result_propagate_error (simple, error))
 630 		return NULL;
 631 
 632 	if (fwd_subject != NULL) {
 633 		*fwd_subject = context->fwd_subject;
 634 		context->fwd_subject = NULL;
 635 	}
 636 
 637 	g_return_val_if_fail (CAMEL_IS_MIME_PART (context->part), NULL);
 638 
 639 	return g_object_ref (context->part);
 640 }
 641 
 642 static void
 643 mail_folder_find_duplicate_messages_thread (GSimpleAsyncResult *simple,
 644                                             GObject *object,
 645                                             GCancellable *cancellable)
 646 {
 647 	AsyncContext *context;
 648 	GError *error = NULL;
 649 
 650 	context = g_simple_async_result_get_op_res_gpointer (simple);
 651 
 652 	context->hash_table = e_mail_folder_find_duplicate_messages_sync (
 653 		CAMEL_FOLDER (object), context->ptr_array,
 654 		cancellable, &error);
 655 
 656 	if (error != NULL)
 657 		g_simple_async_result_take_error (simple, error);
 658 }
 659 
 660 static GHashTable *
 661 emfu_get_messages_hash_sync (CamelFolder *folder,
 662                              GPtrArray *message_uids,
 663                              GCancellable *cancellable,
 664                              GError **error)
 665 {
 666 	GHashTable *hash_table;
 667 	CamelMimeMessage *message;
 668 	guint ii;
 669 
 670 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
 671 	g_return_val_if_fail (message_uids != NULL, NULL);
 672 
 673 	camel_operation_push_message (
 674 		cancellable,
 675 		ngettext (
 676 			"Retrieving %d message",
 677 			"Retrieving %d messages",
 678 			message_uids->len),
 679 		message_uids->len);
 680 
 681 	hash_table = g_hash_table_new_full (
 682 		(GHashFunc) g_str_hash,
 683 		(GEqualFunc) g_str_equal,
 684 		(GDestroyNotify) g_free,
 685 		(GDestroyNotify) g_free);
 686 
 687 	/* This is an all or nothing operation.  Destroy the
 688 	 * hash table if we fail to retrieve any message. */
 689 
 690 	for (ii = 0; ii < message_uids->len; ii++) {
 691 		const gchar *uid;
 692 		gint percent;
 693 
 694 		uid = g_ptr_array_index (message_uids, ii);
 695 		percent = ((ii + 1) * 100) / message_uids->len;
 696 
 697 		message = camel_folder_get_message_sync (
 698 			folder, uid, cancellable, error);
 699 
 700 		camel_operation_progress (cancellable, percent);
 701 
 702 		if (CAMEL_IS_MIME_MESSAGE (message)) {
 703 			CamelDataWrapper *content;
 704 			gchar *digest = NULL;
 705 
 706 			/* Generate a digest string from the message's content. */
 707 			content = camel_medium_get_content (CAMEL_MEDIUM (message));
 708 
 709 			if (content != NULL) {
 710 				CamelStream *stream;
 711 				GByteArray *buffer;
 712 				gssize n_bytes;
 713 
 714 				stream = camel_stream_mem_new ();
 715 
 716 				n_bytes = camel_data_wrapper_decode_to_stream_sync (
 717 					content, stream, cancellable, error);
 718 
 719 				if (n_bytes >= 0) {
 720 					/* The CamelStreamMem owns the buffer. */
 721 					buffer = camel_stream_mem_get_byte_array (
 722 						CAMEL_STREAM_MEM (stream));
 723 					g_return_val_if_fail (buffer != NULL, NULL);
 724 
 725 					digest = g_compute_checksum_for_data (
 726 						G_CHECKSUM_SHA256, buffer->data, buffer->len);
 727 				}
 728 
 729 				g_object_unref (stream);
 730 			}
 731 
 732 			g_hash_table_insert (
 733 				hash_table, g_strdup (uid), digest);
 734 			g_object_unref (message);
 735 		} else {
 736 			g_hash_table_destroy (hash_table);
 737 			hash_table = NULL;
 738 			break;
 739 		}
 740 	}
 741 
 742 	camel_operation_pop_message (cancellable);
 743 
 744 	return hash_table;
 745 }
 746 
 747 GHashTable *
 748 e_mail_folder_find_duplicate_messages_sync (CamelFolder *folder,
 749                                             GPtrArray *message_uids,
 750                                             GCancellable *cancellable,
 751                                             GError **error)
 752 {
 753 	GQueue trash = G_QUEUE_INIT;
 754 	GHashTable *hash_table;
 755 	GHashTable *unique_ids;
 756 	GHashTableIter iter;
 757 	gpointer key, value;
 758 
 759 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
 760 	g_return_val_if_fail (message_uids != NULL, NULL);
 761 
 762 	/* hash_table = { MessageUID : digest-as-string } */
 763 	hash_table = emfu_get_messages_hash_sync (
 764 		folder, message_uids, cancellable, error);
 765 
 766 	if (hash_table == NULL)
 767 		return NULL;
 768 
 769 	camel_operation_push_message (
 770 		cancellable, _("Scanning messages for duplicates"));
 771 
 772 	unique_ids = g_hash_table_new_full (
 773 		(GHashFunc) g_int64_hash,
 774 		(GEqualFunc) g_int64_equal,
 775 		(GDestroyNotify) g_free,
 776 		(GDestroyNotify) g_free);
 777 
 778 	g_hash_table_iter_init (&iter, hash_table);
 779 
 780 	while (g_hash_table_iter_next (&iter, &key, &value)) {
 781 		const CamelSummaryMessageID *message_id;
 782 		CamelMessageFlags flags;
 783 		CamelMessageInfo *info;
 784 		gboolean duplicate;
 785 		const gchar *digest;
 786 
 787 		info = camel_folder_get_message_info (folder, key);
 788 		message_id = camel_message_info_message_id (info);
 789 		flags = camel_message_info_flags (info);
 790 
 791 		/* Skip messages marked for deletion. */
 792 		if (flags & CAMEL_MESSAGE_DELETED) {
 793 			g_queue_push_tail (&trash, key);
 794 			camel_message_info_free (info);
 795 			continue;
 796 		}
 797 
 798 		digest = value;
 799 
 800 		if (digest == NULL) {
 801 			g_queue_push_tail (&trash, key);
 802 			camel_message_info_free (info);
 803 			continue;
 804 		}
 805 
 806 		/* Determine if the message a duplicate. */
 807 
 808 		value = g_hash_table_lookup (unique_ids, &message_id->id.id);
 809 		duplicate = (value != NULL) && g_str_equal (digest, value);
 810 
 811 		if (!duplicate) {
 812 			gint64 *v_int64;
 813 
 814 			/* XXX Might be better to create a GArray
 815 			 *     of 64-bit integers and have the hash
 816 			 *     table keys point to array elements. */
 817 			v_int64 = g_new0 (gint64, 1);
 818 			*v_int64 = (gint64) message_id->id.id;
 819 
 820 			g_hash_table_insert (unique_ids, v_int64, g_strdup (digest));
 821 			g_queue_push_tail (&trash, key);
 822 		}
 823 
 824 		camel_message_info_free (info);
 825 	}
 826 
 827 	/* Delete all non-duplicate messages from the hash table. */
 828 	while ((key = g_queue_pop_head (&trash)) != NULL)
 829 		g_hash_table_remove (hash_table, key);
 830 
 831 	camel_operation_pop_message (cancellable);
 832 
 833 	g_hash_table_destroy (unique_ids);
 834 
 835 	return hash_table;
 836 }
 837 
 838 void
 839 e_mail_folder_find_duplicate_messages (CamelFolder *folder,
 840                                        GPtrArray *message_uids,
 841                                        gint io_priority,
 842                                        GCancellable *cancellable,
 843                                        GAsyncReadyCallback callback,
 844                                        gpointer user_data)
 845 {
 846 	GSimpleAsyncResult *simple;
 847 	AsyncContext *context;
 848 
 849 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
 850 	g_return_if_fail (message_uids != NULL);
 851 
 852 	context = g_slice_new0 (AsyncContext);
 853 	context->ptr_array = g_ptr_array_ref (message_uids);
 854 
 855 	simple = g_simple_async_result_new (
 856 		G_OBJECT (folder), callback, user_data,
 857 		e_mail_folder_find_duplicate_messages);
 858 
 859 	g_simple_async_result_set_check_cancellable (simple, cancellable);
 860 
 861 	g_simple_async_result_set_op_res_gpointer (
 862 		simple, context, (GDestroyNotify) async_context_free);
 863 
 864 	g_simple_async_result_run_in_thread (
 865 		simple, mail_folder_find_duplicate_messages_thread,
 866 		io_priority, cancellable);
 867 
 868 	g_object_unref (simple);
 869 }
 870 
 871 GHashTable *
 872 e_mail_folder_find_duplicate_messages_finish (CamelFolder *folder,
 873                                               GAsyncResult *result,
 874                                               GError **error)
 875 {
 876 	GSimpleAsyncResult *simple;
 877 	AsyncContext *context;
 878 
 879 	g_return_val_if_fail (
 880 		g_simple_async_result_is_valid (
 881 		result, G_OBJECT (folder),
 882 		e_mail_folder_find_duplicate_messages), NULL);
 883 
 884 	simple = G_SIMPLE_ASYNC_RESULT (result);
 885 	context = g_simple_async_result_get_op_res_gpointer (simple);
 886 
 887 	if (g_simple_async_result_propagate_error (simple, error))
 888 		return NULL;
 889 
 890 	return g_hash_table_ref (context->hash_table);
 891 }
 892 
 893 static void
 894 mail_folder_get_multiple_messages_thread (GSimpleAsyncResult *simple,
 895                                           GObject *object,
 896                                           GCancellable *cancellable)
 897 {
 898 	AsyncContext *context;
 899 	GError *error = NULL;
 900 
 901 	context = g_simple_async_result_get_op_res_gpointer (simple);
 902 
 903 	context->hash_table = e_mail_folder_get_multiple_messages_sync (
 904 		CAMEL_FOLDER (object), context->ptr_array,
 905 		cancellable, &error);
 906 
 907 	if (error != NULL)
 908 		g_simple_async_result_take_error (simple, error);
 909 }
 910 
 911 GHashTable *
 912 e_mail_folder_get_multiple_messages_sync (CamelFolder *folder,
 913                                           GPtrArray *message_uids,
 914                                           GCancellable *cancellable,
 915                                           GError **error)
 916 {
 917 	GHashTable *hash_table;
 918 	CamelMimeMessage *message;
 919 	guint ii;
 920 
 921 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
 922 	g_return_val_if_fail (message_uids != NULL, NULL);
 923 
 924 	camel_operation_push_message (
 925 		cancellable,
 926 		ngettext (
 927 			"Retrieving %d message",
 928 			"Retrieving %d messages",
 929 			message_uids->len),
 930 		message_uids->len);
 931 
 932 	hash_table = g_hash_table_new_full (
 933 		(GHashFunc) g_str_hash,
 934 		(GEqualFunc) g_str_equal,
 935 		(GDestroyNotify) g_free,
 936 		(GDestroyNotify) g_object_unref);
 937 
 938 	/* This is an all or nothing operation.  Destroy the
 939 	 * hash table if we fail to retrieve any message. */
 940 
 941 	for (ii = 0; ii < message_uids->len; ii++) {
 942 		const gchar *uid;
 943 		gint percent;
 944 
 945 		uid = g_ptr_array_index (message_uids, ii);
 946 		percent = ((ii + 1) * 100) / message_uids->len;
 947 
 948 		message = camel_folder_get_message_sync (
 949 			folder, uid, cancellable, error);
 950 
 951 		camel_operation_progress (cancellable, percent);
 952 
 953 		if (CAMEL_IS_MIME_MESSAGE (message)) {
 954 			g_hash_table_insert (
 955 				hash_table, g_strdup (uid), message);
 956 		} else {
 957 			g_hash_table_destroy (hash_table);
 958 			hash_table = NULL;
 959 			break;
 960 		}
 961 	}
 962 
 963 	camel_operation_pop_message (cancellable);
 964 
 965 	return hash_table;
 966 }
 967 
 968 void
 969 e_mail_folder_get_multiple_messages (CamelFolder *folder,
 970                                      GPtrArray *message_uids,
 971                                      gint io_priority,
 972                                      GCancellable *cancellable,
 973                                      GAsyncReadyCallback callback,
 974                                      gpointer user_data)
 975 {
 976 	GSimpleAsyncResult *simple;
 977 	AsyncContext *context;
 978 
 979 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
 980 	g_return_if_fail (message_uids != NULL);
 981 
 982 	context = g_slice_new0 (AsyncContext);
 983 	context->ptr_array = g_ptr_array_ref (message_uids);
 984 
 985 	simple = g_simple_async_result_new (
 986 		G_OBJECT (folder), callback, user_data,
 987 		e_mail_folder_get_multiple_messages);
 988 
 989 	g_simple_async_result_set_check_cancellable (simple, cancellable);
 990 
 991 	g_simple_async_result_set_op_res_gpointer (
 992 		simple, context, (GDestroyNotify) async_context_free);
 993 
 994 	g_simple_async_result_run_in_thread (
 995 		simple, mail_folder_get_multiple_messages_thread,
 996 		io_priority, cancellable);
 997 
 998 	g_object_unref (simple);
 999 }
1000 
1001 GHashTable *
1002 e_mail_folder_get_multiple_messages_finish (CamelFolder *folder,
1003                                             GAsyncResult *result,
1004                                             GError **error)
1005 {
1006 	GSimpleAsyncResult *simple;
1007 	AsyncContext *context;
1008 
1009 	g_return_val_if_fail (
1010 		g_simple_async_result_is_valid (
1011 		result, G_OBJECT (folder),
1012 		e_mail_folder_get_multiple_messages), NULL);
1013 
1014 	simple = G_SIMPLE_ASYNC_RESULT (result);
1015 	context = g_simple_async_result_get_op_res_gpointer (simple);
1016 
1017 	if (g_simple_async_result_propagate_error (simple, error))
1018 		return NULL;
1019 
1020 	return g_hash_table_ref (context->hash_table);
1021 }
1022 
1023 static void
1024 mail_folder_remove_thread (GSimpleAsyncResult *simple,
1025                            GObject *object,
1026                            GCancellable *cancellable)
1027 {
1028 	GError *error = NULL;
1029 
1030 	e_mail_folder_remove_sync (
1031 		CAMEL_FOLDER (object), cancellable, &error);
1032 
1033 	if (error != NULL)
1034 		g_simple_async_result_take_error (simple, error);
1035 }
1036 
1037 static gboolean
1038 mail_folder_remove_recursive (CamelStore *store,
1039                               CamelFolderInfo *folder_info,
1040                               GCancellable *cancellable,
1041                               GError **error)
1042 {
1043 	gboolean success = TRUE;
1044 
1045 	while (folder_info != NULL) {
1046 		CamelFolder *folder;
1047 
1048 		if (folder_info->child != NULL) {
1049 			success = mail_folder_remove_recursive (
1050 				store, folder_info->child, cancellable, error);
1051 			if (!success)
1052 				break;
1053 		}
1054 
1055 		folder = camel_store_get_folder_sync (
1056 			store, folder_info->full_name, 0, cancellable, error);
1057 		if (folder == NULL) {
1058 			success = FALSE;
1059 			break;
1060 		}
1061 
1062 		if (!CAMEL_IS_VEE_FOLDER (folder)) {
1063 			GPtrArray *uids;
1064 			guint ii;
1065 
1066 			/* Delete every message in this folder,
1067 			 * then expunge it. */
1068 
1069 			camel_folder_freeze (folder);
1070 
1071 			uids = camel_folder_get_uids (folder);
1072 
1073 			for (ii = 0; ii < uids->len; ii++)
1074 				camel_folder_delete_message (
1075 					folder, uids->pdata[ii]);
1076 
1077 			camel_folder_free_uids (folder, uids);
1078 
1079 			success = camel_folder_synchronize_sync (
1080 				folder, TRUE, cancellable, error);
1081 
1082 			camel_folder_thaw (folder);
1083 		}
1084 
1085 		g_object_unref (folder);
1086 
1087 		if (!success)
1088 			break;
1089 
1090 		/* If the store supports subscriptions,
1091 		 * then unsubscribe from this folder. */
1092 		if (CAMEL_IS_SUBSCRIBABLE (store)) {
1093 			success = camel_subscribable_unsubscribe_folder_sync (
1094 				CAMEL_SUBSCRIBABLE (store),
1095 				folder_info->full_name,
1096 				cancellable, error);
1097 			if (!success)
1098 				break;
1099 		}
1100 
1101 		success = camel_store_delete_folder_sync (
1102 			store, folder_info->full_name, cancellable, error);
1103 		if (!success)
1104 			break;
1105 
1106 		folder_info = folder_info->next;
1107 	}
1108 
1109 	return success;
1110 }
1111 
1112 static void
1113 follow_cancel_cb (GCancellable *cancellable,
1114                   GCancellable *transparent_cancellable)
1115 {
1116 	g_cancellable_cancel (transparent_cancellable);
1117 }
1118 
1119 gboolean
1120 e_mail_folder_remove_sync (CamelFolder *folder,
1121                            GCancellable *cancellable,
1122                            GError **error)
1123 {
1124 	CamelFolderInfo *folder_info;
1125 	CamelFolderInfo *to_remove;
1126 	CamelFolderInfo *next = NULL;
1127 	CamelStore *parent_store;
1128 	const gchar *display_name;
1129 	const gchar *full_name;
1130 	const gchar *message;
1131 	gboolean success = TRUE;
1132 	GCancellable *transparent_cancellable = NULL;
1133 	gulong cbid = 0;
1134 
1135 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1136 
1137 	full_name = camel_folder_get_full_name (folder);
1138 	parent_store = camel_folder_get_parent_store (folder);
1139 
1140 	folder_info = camel_store_get_folder_info_sync (
1141 		parent_store, full_name,
1142 		CAMEL_STORE_FOLDER_INFO_FAST |
1143 		CAMEL_STORE_FOLDER_INFO_RECURSIVE |
1144 		CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
1145 		cancellable, error);
1146 
1147 	if (folder_info == NULL)
1148 		return FALSE;
1149 
1150 	to_remove = folder_info;
1151 
1152 	/* For cases when the top-level folder_info contains siblings,
1153 	 * such as when full_name contains a wildcard letter, compare
1154 	 * the folder name against folder_info->full_name to avoid
1155 	 * removing more folders than requested. */
1156 	if (folder_info->next != NULL) {
1157 		while (to_remove != NULL) {
1158 			if (g_strcmp0 (to_remove->full_name, full_name) == 0)
1159 				break;
1160 			to_remove = to_remove->next;
1161 		}
1162 
1163 		/* XXX Should we set a GError and return FALSE here? */
1164 		if (to_remove == NULL) {
1165 			g_warning (
1166 				"%s: Failed to find folder '%s'",
1167 				G_STRFUNC, full_name);
1168 			camel_store_free_folder_info (
1169 				parent_store, folder_info);
1170 			return TRUE;
1171 		}
1172 
1173 		/* Prevent iterating over siblings. */
1174 		next = to_remove->next;
1175 		to_remove->next = NULL;
1176 	}
1177 
1178 	message = _("Removing folder '%s'");
1179 	display_name = camel_folder_get_display_name (folder);
1180 	camel_operation_push_message (cancellable, message, display_name);
1181 
1182 	if (cancellable) {
1183 		transparent_cancellable = g_cancellable_new ();
1184 		cbid = g_cancellable_connect (
1185 			cancellable, G_CALLBACK (follow_cancel_cb),
1186 			transparent_cancellable, NULL);
1187 	}
1188 
1189 	success = mail_folder_remove_recursive (
1190 		parent_store, to_remove, transparent_cancellable, error);
1191 
1192 	if (transparent_cancellable) {
1193 		g_cancellable_disconnect (cancellable, cbid);
1194 		g_object_unref (transparent_cancellable);
1195 	}
1196 
1197 	camel_operation_pop_message (cancellable);
1198 
1199 	/* Restore the folder_info tree to its original
1200 	 * state so we don't leak folder_info nodes. */
1201 	to_remove->next = next;
1202 
1203 	camel_store_free_folder_info (parent_store, folder_info);
1204 
1205 	return success;
1206 }
1207 
1208 void
1209 e_mail_folder_remove (CamelFolder *folder,
1210                       gint io_priority,
1211                       GCancellable *cancellable,
1212                       GAsyncReadyCallback callback,
1213                       gpointer user_data)
1214 {
1215 	GSimpleAsyncResult *simple;
1216 
1217 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
1218 
1219 	simple = g_simple_async_result_new (
1220 		G_OBJECT (folder), callback,
1221 		user_data, e_mail_folder_remove);
1222 
1223 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1224 
1225 	g_simple_async_result_run_in_thread (
1226 		simple, mail_folder_remove_thread,
1227 		io_priority, cancellable);
1228 
1229 	g_object_unref (simple);
1230 }
1231 
1232 gboolean
1233 e_mail_folder_remove_finish (CamelFolder *folder,
1234                              GAsyncResult *result,
1235                              GError **error)
1236 {
1237 	GSimpleAsyncResult *simple;
1238 
1239 	g_return_val_if_fail (
1240 		g_simple_async_result_is_valid (
1241 		result, G_OBJECT (folder),
1242 		e_mail_folder_remove), FALSE);
1243 
1244 	simple = G_SIMPLE_ASYNC_RESULT (result);
1245 
1246 	/* Assume success unless a GError is set. */
1247 	return !g_simple_async_result_propagate_error (simple, error);
1248 }
1249 
1250 static void
1251 mail_folder_remove_attachments_thread (GSimpleAsyncResult *simple,
1252                                        GObject *object,
1253                                        GCancellable *cancellable)
1254 {
1255 	AsyncContext *context;
1256 	GError *error = NULL;
1257 
1258 	context = g_simple_async_result_get_op_res_gpointer (simple);
1259 
1260 	e_mail_folder_remove_attachments_sync (
1261 		CAMEL_FOLDER (object), context->ptr_array,
1262 		cancellable, &error);
1263 
1264 	if (error != NULL)
1265 		g_simple_async_result_take_error (simple, error);
1266 }
1267 
1268 /* Helper for e_mail_folder_remove_attachments_sync() */
1269 static gboolean
1270 mail_folder_strip_message (CamelFolder *folder,
1271                            CamelMimeMessage *message,
1272                            const gchar *message_uid,
1273                            GCancellable *cancellable,
1274                            GError **error)
1275 {
1276 	CamelDataWrapper *content;
1277 	CamelMultipart *multipart;
1278 	gboolean modified = FALSE;
1279 	gboolean success = TRUE;
1280 	guint ii, n_parts;
1281 
1282 	content = camel_medium_get_content (CAMEL_MEDIUM (message));
1283 
1284 	if (!CAMEL_IS_MULTIPART (content))
1285 		return TRUE;
1286 
1287 	multipart = CAMEL_MULTIPART (content);
1288 	n_parts = camel_multipart_get_number (multipart);
1289 
1290 	/* Replace MIME parts with "attachment" or "inline" dispositions
1291 	 * with a small "text/plain" part saying the file was removed. */
1292 	for (ii = 0; ii < n_parts; ii++) {
1293 		CamelMimePart *mime_part;
1294 		const gchar *disposition;
1295 		gboolean is_attachment;
1296 
1297 		mime_part = camel_multipart_get_part (multipart, ii);
1298 		disposition = camel_mime_part_get_disposition (mime_part);
1299 
1300 		is_attachment =
1301 			(g_strcmp0 (disposition, "attachment") == 0) ||
1302 			(g_strcmp0 (disposition, "inline") == 0);
1303 
1304 		if (is_attachment) {
1305 			const gchar *filename;
1306 			const gchar *content_type;
1307 			gchar *content;
1308 
1309 			disposition = "inline";
1310 			content_type = "text/plain";
1311 			filename = camel_mime_part_get_filename (mime_part);
1312 
1313 			if (filename != NULL && *filename != '\0')
1314 				content = g_strdup_printf (
1315 					_("File \"%s\" has been removed."),
1316 					filename);
1317 			else
1318 				content = g_strdup (
1319 					_("File has been removed."));
1320 
1321 			camel_mime_part_set_content (
1322 				mime_part, content,
1323 				strlen (content), content_type);
1324 			camel_mime_part_set_content_type (
1325 				mime_part, content_type);
1326 			camel_mime_part_set_disposition (
1327 				mime_part, disposition);
1328 
1329 			modified = TRUE;
1330 		}
1331 	}
1332 
1333 	/* Append the modified message with removed attachments to
1334 	 * the folder and mark the original message for deletion. */
1335 	if (modified) {
1336 		CamelMessageInfo *orig_info;
1337 		CamelMessageInfo *copy_info;
1338 		CamelMessageFlags flags;
1339 
1340 		orig_info =
1341 			camel_folder_get_message_info (folder, message_uid);
1342 		copy_info =
1343 			camel_message_info_new_from_header (
1344 			NULL, CAMEL_MIME_PART (message)->headers);
1345 
1346 		flags = camel_folder_get_message_flags (folder, message_uid);
1347 		camel_message_info_set_flags (copy_info, flags, flags);
1348 
1349 		success = camel_folder_append_message_sync (
1350 			folder, message, copy_info, NULL, cancellable, error);
1351 		if (success)
1352 			camel_message_info_set_flags (
1353 				orig_info,
1354 				CAMEL_MESSAGE_DELETED,
1355 				CAMEL_MESSAGE_DELETED);
1356 
1357 		camel_folder_free_message_info (folder, orig_info);
1358 		camel_message_info_free (copy_info);
1359 	}
1360 
1361 	return success;
1362 }
1363 
1364 gboolean
1365 e_mail_folder_remove_attachments_sync (CamelFolder *folder,
1366                                        GPtrArray *message_uids,
1367                                        GCancellable *cancellable,
1368                                        GError **error)
1369 {
1370 	gboolean success = TRUE;
1371 	guint ii;
1372 
1373 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1374 	g_return_val_if_fail (message_uids != NULL, FALSE);
1375 
1376 	camel_folder_freeze (folder);
1377 
1378 	camel_operation_push_message (cancellable, _("Removing attachments"));
1379 
1380 	for (ii = 0; success && ii < message_uids->len; ii++) {
1381 		CamelMimeMessage *message;
1382 		const gchar *uid;
1383 		gint percent;
1384 
1385 		uid = g_ptr_array_index (message_uids, ii);
1386 
1387 		message = camel_folder_get_message_sync (
1388 			folder, uid, cancellable, error);
1389 
1390 		if (message == NULL) {
1391 			success = FALSE;
1392 			break;
1393 		}
1394 
1395 		success = mail_folder_strip_message (
1396 			folder, message, uid, cancellable, error);
1397 
1398 		percent = ((ii + 1) * 100) / message_uids->len;
1399 		camel_operation_progress (cancellable, percent);
1400 
1401 		g_object_unref (message);
1402 	}
1403 
1404 	camel_operation_pop_message (cancellable);
1405 
1406 	if (success)
1407 		camel_folder_synchronize_sync (
1408 			folder, FALSE, cancellable, error);
1409 
1410 	camel_folder_thaw (folder);
1411 
1412 	return success;
1413 }
1414 
1415 void
1416 e_mail_folder_remove_attachments (CamelFolder *folder,
1417                                   GPtrArray *message_uids,
1418                                   gint io_priority,
1419                                   GCancellable *cancellable,
1420                                   GAsyncReadyCallback callback,
1421                                   gpointer user_data)
1422 {
1423 	GSimpleAsyncResult *simple;
1424 	AsyncContext *context;
1425 
1426 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
1427 	g_return_if_fail (message_uids != NULL);
1428 
1429 	context = g_slice_new0 (AsyncContext);
1430 	context->ptr_array = g_ptr_array_ref (message_uids);
1431 
1432 	simple = g_simple_async_result_new (
1433 		G_OBJECT (folder), callback, user_data,
1434 		e_mail_folder_remove_attachments);
1435 
1436 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1437 
1438 	g_simple_async_result_set_op_res_gpointer (
1439 		simple, context, (GDestroyNotify) async_context_free);
1440 
1441 	g_simple_async_result_run_in_thread (
1442 		simple, mail_folder_remove_attachments_thread,
1443 		io_priority, cancellable);
1444 
1445 	g_object_unref (simple);
1446 }
1447 
1448 gboolean
1449 e_mail_folder_remove_attachments_finish (CamelFolder *folder,
1450                                          GAsyncResult *result,
1451                                          GError **error)
1452 {
1453 	GSimpleAsyncResult *simple;
1454 
1455 	g_return_val_if_fail (
1456 		g_simple_async_result_is_valid (
1457 		result, G_OBJECT (folder),
1458 		e_mail_folder_remove_attachments), FALSE);
1459 
1460 	simple = G_SIMPLE_ASYNC_RESULT (result);
1461 
1462 	/* Assume success unless a GError is set. */
1463 	return !g_simple_async_result_propagate_error (simple, error);
1464 }
1465 
1466 static void
1467 mail_folder_save_messages_thread (GSimpleAsyncResult *simple,
1468                                   GObject *object,
1469                                   GCancellable *cancellable)
1470 {
1471 	AsyncContext *context;
1472 	GError *error = NULL;
1473 
1474 	context = g_simple_async_result_get_op_res_gpointer (simple);
1475 
1476 	e_mail_folder_save_messages_sync (
1477 		CAMEL_FOLDER (object), context->ptr_array,
1478 		context->destination, cancellable, &error);
1479 
1480 	if (error != NULL)
1481 		g_simple_async_result_take_error (simple, error);
1482 }
1483 
1484 /* Helper for e_mail_folder_save_messages_sync() */
1485 static void
1486 mail_folder_save_prepare_part (CamelMimePart *mime_part)
1487 {
1488 	CamelDataWrapper *content;
1489 
1490 	content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
1491 
1492 	if (content == NULL)
1493 		return;
1494 
1495 	if (CAMEL_IS_MULTIPART (content)) {
1496 		guint n_parts, ii;
1497 
1498 		n_parts = camel_multipart_get_number (
1499 			CAMEL_MULTIPART (content));
1500 		for (ii = 0; ii < n_parts; ii++) {
1501 			mime_part = camel_multipart_get_part (
1502 				CAMEL_MULTIPART (content), ii);
1503 			mail_folder_save_prepare_part (mime_part);
1504 		}
1505 
1506 	} else if (CAMEL_IS_MIME_MESSAGE (content)) {
1507 		mail_folder_save_prepare_part (CAMEL_MIME_PART (content));
1508 
1509 	} else {
1510 		CamelContentType *type;
1511 
1512 		/* Save textual parts as 8-bit, not encoded. */
1513 		type = camel_data_wrapper_get_mime_type_field (content);
1514 		if (camel_content_type_is (type, "text", "*"))
1515 			camel_mime_part_set_encoding (
1516 				mime_part, CAMEL_TRANSFER_ENCODING_8BIT);
1517 	}
1518 }
1519 
1520 gboolean
1521 e_mail_folder_save_messages_sync (CamelFolder *folder,
1522                                   GPtrArray *message_uids,
1523                                   GFile *destination,
1524                                   GCancellable *cancellable,
1525                                   GError **error)
1526 {
1527 	GFileOutputStream *file_output_stream;
1528 	CamelStream *base_stream = NULL;
1529 	GByteArray *byte_array;
1530 	gboolean success = TRUE;
1531 	guint ii;
1532 
1533 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1534 	g_return_val_if_fail (message_uids != NULL, FALSE);
1535 	g_return_val_if_fail (G_IS_FILE (destination), FALSE);
1536 
1537 	/* Need at least one message UID to save. */
1538 	g_return_val_if_fail (message_uids->len > 0, FALSE);
1539 
1540 	camel_operation_push_message (
1541 		cancellable, ngettext (
1542 			"Saving %d message",
1543 			"Saving %d messages",
1544 			message_uids->len),
1545 		message_uids->len);
1546 
1547 	file_output_stream = g_file_replace (
1548 		destination, NULL, FALSE,
1549 		G_FILE_CREATE_PRIVATE |
1550 		G_FILE_CREATE_REPLACE_DESTINATION,
1551 		cancellable, error);
1552 
1553 	if (file_output_stream == NULL) {
1554 		camel_operation_pop_message (cancellable);
1555 		return FALSE;
1556 	}
1557 
1558 	byte_array = g_byte_array_new ();
1559 
1560 	for (ii = 0; ii < message_uids->len; ii++) {
1561 		CamelMimeMessage *message;
1562 		CamelMimeFilter *filter;
1563 		CamelStream *stream;
1564 		const gchar *uid;
1565 		gchar *from_line;
1566 		gint percent;
1567 		gint retval;
1568 
1569 		if (base_stream != NULL)
1570 			g_object_unref (base_stream);
1571 
1572 		/* CamelStreamMem does NOT take ownership of the byte
1573 		 * array when set with camel_stream_mem_set_byte_array().
1574 		 * This allows us to reuse the same memory slab for each
1575 		 * message, which is slightly more efficient. */
1576 		base_stream = camel_stream_mem_new ();
1577 		camel_stream_mem_set_byte_array (
1578 			CAMEL_STREAM_MEM (base_stream), byte_array);
1579 
1580 		uid = g_ptr_array_index (message_uids, ii);
1581 
1582 		message = camel_folder_get_message_sync (
1583 			folder, uid, cancellable, error);
1584 		if (message == NULL) {
1585 			success = FALSE;
1586 			goto exit;
1587 		}
1588 
1589 		mail_folder_save_prepare_part (CAMEL_MIME_PART (message));
1590 
1591 		from_line = camel_mime_message_build_mbox_from (message);
1592 		g_return_val_if_fail (from_line != NULL, FALSE);
1593 
1594 		success = g_output_stream_write_all (
1595 			G_OUTPUT_STREAM (file_output_stream),
1596 			from_line, strlen (from_line), NULL,
1597 			cancellable, error);
1598 
1599 		g_free (from_line);
1600 
1601 		if (!success) {
1602 			g_object_unref (message);
1603 			goto exit;
1604 		}
1605 
1606 		filter = camel_mime_filter_from_new ();
1607 		stream = camel_stream_filter_new (base_stream);
1608 		camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
1609 
1610 		retval = camel_data_wrapper_write_to_stream_sync (
1611 			CAMEL_DATA_WRAPPER (message),
1612 			stream, cancellable, error);
1613 
1614 		g_object_unref (filter);
1615 		g_object_unref (stream);
1616 
1617 		if (retval == -1) {
1618 			g_object_unref (message);
1619 			goto exit;
1620 		}
1621 
1622 		g_byte_array_append (byte_array, (guint8 *) "\n", 1);
1623 
1624 		success = g_output_stream_write_all (
1625 			G_OUTPUT_STREAM (file_output_stream),
1626 			byte_array->data, byte_array->len,
1627 			NULL, cancellable, error);
1628 
1629 		if (!success) {
1630 			g_object_unref (message);
1631 			goto exit;
1632 		}
1633 
1634 		percent = ((ii + 1) * 100) / message_uids->len;
1635 		camel_operation_progress (cancellable, percent);
1636 
1637 		/* Reset the byte array for the next message. */
1638 		g_byte_array_set_size (byte_array, 0);
1639 
1640 		g_object_unref (message);
1641 	}
1642 
1643 exit:
1644 	if (base_stream != NULL)
1645 		g_object_unref (base_stream);
1646 
1647 	g_byte_array_free (byte_array, TRUE);
1648 
1649 	g_object_unref (file_output_stream);
1650 
1651 	camel_operation_pop_message (cancellable);
1652 
1653 	if (!success) {
1654 		/* Try deleting the destination file. */
1655 		g_file_delete (destination, NULL, NULL);
1656 	}
1657 
1658 	return success;
1659 }
1660 
1661 void
1662 e_mail_folder_save_messages (CamelFolder *folder,
1663                              GPtrArray *message_uids,
1664                              GFile *destination,
1665                              gint io_priority,
1666                              GCancellable *cancellable,
1667                              GAsyncReadyCallback callback,
1668                              gpointer user_data)
1669 {
1670 	GSimpleAsyncResult *simple;
1671 	AsyncContext *context;
1672 
1673 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
1674 	g_return_if_fail (message_uids != NULL);
1675 	g_return_if_fail (G_IS_FILE (destination));
1676 
1677 	/* Need at least one message UID to save. */
1678 	g_return_if_fail (message_uids->len > 0);
1679 
1680 	context = g_slice_new0 (AsyncContext);
1681 	context->ptr_array = g_ptr_array_ref (message_uids);
1682 	context->destination = g_object_ref (destination);
1683 
1684 	simple = g_simple_async_result_new (
1685 		G_OBJECT (folder), callback, user_data,
1686 		e_mail_folder_save_messages);
1687 
1688 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1689 
1690 	g_simple_async_result_set_op_res_gpointer (
1691 		simple, context, (GDestroyNotify) async_context_free);
1692 
1693 	g_simple_async_result_run_in_thread (
1694 		simple, mail_folder_save_messages_thread,
1695 		io_priority, cancellable);
1696 
1697 	g_object_unref (simple);
1698 }
1699 
1700 gboolean
1701 e_mail_folder_save_messages_finish (CamelFolder *folder,
1702                                     GAsyncResult *result,
1703                                     GError **error)
1704 {
1705 	GSimpleAsyncResult *simple;
1706 
1707 	g_return_val_if_fail (
1708 		g_simple_async_result_is_valid (
1709 		result, G_OBJECT (folder),
1710 		e_mail_folder_save_messages), FALSE);
1711 
1712 	simple = G_SIMPLE_ASYNC_RESULT (result);
1713 
1714 	/* Assume success unless a GError is set. */
1715 	return !g_simple_async_result_propagate_error (simple, error);
1716 }
1717 
1718 /**
1719  * e_mail_folder_uri_build:
1720  * @store: a #CamelStore
1721  * @folder_name: a folder name
1722  *
1723  * Builds a folder URI string from @store and @folder_name.
1724  *
1725  * Returns: a newly-allocated folder URI string
1726  **/
1727 gchar *
1728 e_mail_folder_uri_build (CamelStore *store,
1729                          const gchar *folder_name)
1730 {
1731 	const gchar *uid;
1732 	gchar *encoded_name;
1733 	gchar *encoded_uid;
1734 	gchar *uri;
1735 
1736 	g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
1737 	g_return_val_if_fail (folder_name != NULL, NULL);
1738 
1739 	/* Skip the leading slash, if present. */
1740 	if (*folder_name == '/')
1741 		folder_name++;
1742 
1743 	uid = camel_service_get_uid (CAMEL_SERVICE (store));
1744 
1745 	encoded_uid = camel_url_encode (uid, ":;@/");
1746 	encoded_name = camel_url_encode (folder_name, "#");
1747 
1748 	uri = g_strdup_printf ("folder://%s/%s", encoded_uid, encoded_name);
1749 
1750 	g_free (encoded_uid);
1751 	g_free (encoded_name);
1752 
1753 	return uri;
1754 }
1755 
1756 /**
1757  * e_mail_folder_uri_parse:
1758  * @session: a #CamelSession
1759  * @folder_uri: a folder URI
1760  * @out_store: return location for a #CamelStore, or %NULL
1761  * @out_folder_name: return location for a folder name, or %NULL
1762  * @error: return location for a #GError, or %NULL
1763  *
1764  * Parses a folder URI generated by e_mail_folder_uri_build() and
1765  * returns the corresponding #CamelStore instance in @out_store and
1766  * folder name string in @out_folder_name.  If the URI is malformed
1767  * or no corresponding store exists, the function sets @error and
1768  * returns %FALSE.
1769  *
1770  * If the function is able to parse the URI, the #CamelStore instance
1771  * set in @out_store should be unreferenced with g_object_unref() when
1772  * done with it, and the folder name string set in @out_folder_name
1773  * should be freed with g_free().
1774  *
1775  * The function also handles older style URIs, such as ones where the
1776  * #CamelStore's #CamelStore::uri string was embedded directly in the
1777  * folder URI, and account-based URIs that used an "email://" prefix.
1778  *
1779  * Returns: %TRUE if @folder_uri could be parsed, %FALSE otherwise
1780  **/
1781 gboolean
1782 e_mail_folder_uri_parse (CamelSession *session,
1783                          const gchar *folder_uri,
1784                          CamelStore **out_store,
1785                          gchar **out_folder_name,
1786                          GError **error)
1787 {
1788 	CamelURL *url;
1789 	CamelService *service = NULL;
1790 	gchar *folder_name = NULL;
1791 	gboolean success = FALSE;
1792 
1793 	g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1794 	g_return_val_if_fail (folder_uri != NULL, FALSE);
1795 
1796 	url = camel_url_new (folder_uri, error);
1797 	if (url == NULL)
1798 		return FALSE;
1799 
1800 	/* Current URI Format: 'folder://' STORE_UID '/' FOLDER_PATH */
1801 	if (g_strcmp0 (url->protocol, "folder") == 0) {
1802 
1803 		if (url->host != NULL) {
1804 			gchar *uid;
1805 
1806 			if (url->user == NULL || *url->user == '\0')
1807 				uid = g_strdup (url->host);
1808 			else
1809 				uid = g_strconcat (
1810 					url->user, "@", url->host, NULL);
1811 
1812 			service = camel_session_ref_service (session, uid);
1813 			g_free (uid);
1814 		}
1815 
1816 		if (url->path != NULL && *url->path == '/')
1817 			folder_name = camel_url_decode_path (url->path + 1);
1818 
1819 	/* This style was used to reference accounts by UID before
1820 	 * CamelServices themselves had UIDs.  Some examples are:
1821 	 *
1822 	 * Special cases:
1823 	 *
1824 	 *   'email://local@local/' FOLDER_PATH
1825 	 *   'email://vfolder@local/' FOLDER_PATH
1826 	 *
1827 	 * General case:
1828 	 *
1829 	 *   'email://' ACCOUNT_UID '/' FOLDER_PATH
1830 	 *
1831 	 * Note: ACCOUNT_UID is now equivalent to STORE_UID, and
1832 	 *       the STORE_UIDs for the special cases are 'local'
1833 	 *       and 'vfolder'.
1834 	 */
1835 	} else if (g_strcmp0 (url->protocol, "email") == 0) {
1836 		gchar *uid = NULL;
1837 
1838 		/* Handle the special cases. */
1839 		if (g_strcmp0 (url->host, "local") == 0) {
1840 			if (g_strcmp0 (url->user, "local") == 0)
1841 				uid = g_strdup ("local");
1842 			if (g_strcmp0 (url->user, "vfolder") == 0)
1843 				uid = g_strdup ("vfolder");
1844 		}
1845 
1846 		/* Handle the general case. */
1847 		if (uid == NULL && url->host != NULL) {
1848 			if (url->user == NULL)
1849 				uid = g_strdup (url->host);
1850 			else
1851 				uid = g_strdup_printf (
1852 					"%s@%s", url->user, url->host);
1853 		}
1854 
1855 		if (uid != NULL) {
1856 			service = camel_session_ref_service (session, uid);
1857 			g_free (uid);
1858 		}
1859 
1860 		if (url->path != NULL && *url->path == '/')
1861 			folder_name = camel_url_decode_path (url->path + 1);
1862 
1863 	/* CamelFolderInfo URIs used to embed the store's URI, so the
1864 	 * folder name is appended as either a path part or a fragment
1865 	 * part, depending whether the store's URI used the path part.
1866 	 * To determine which it is, you have to check the provider
1867 	 * flags for CAMEL_URL_FRAGMENT_IS_PATH. */
1868 	} else {
1869 		service = camel_session_ref_service_by_url (
1870 			session, url, CAMEL_PROVIDER_STORE);
1871 
1872 		if (CAMEL_IS_STORE (service)) {
1873 			CamelProvider *provider;
1874 
1875 			provider = camel_service_get_provider (service);
1876 
1877 			if (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)
1878 				folder_name = g_strdup (url->fragment);
1879 			else if (url->path != NULL && *url->path == '/')
1880 				folder_name = g_strdup (url->path + 1);
1881 		}
1882 	}
1883 
1884 	if (CAMEL_IS_STORE (service) && folder_name != NULL) {
1885 		if (out_store != NULL)
1886 			*out_store = g_object_ref (service);
1887 
1888 		if (out_folder_name != NULL) {
1889 			*out_folder_name = folder_name;
1890 			folder_name = NULL;
1891 		}
1892 
1893 		success = TRUE;
1894 	} else {
1895 		g_set_error (
1896 			error, CAMEL_FOLDER_ERROR,
1897 			CAMEL_FOLDER_ERROR_INVALID,
1898 			_("Invalid folder URI '%s'"),
1899 			folder_uri);
1900 	}
1901 
1902 	if (service != NULL)
1903 		g_object_unref (service);
1904 
1905 	g_free (folder_name);
1906 
1907 	camel_url_free (url);
1908 
1909 	return success;
1910 }
1911 
1912 /**
1913  * e_mail_folder_uri_equal:
1914  * @session: a #CamelSession
1915  * @folder_uri_a: a folder URI
1916  * @folder_uri_b: another folder URI
1917  *
1918  * Compares two folder URIs for equality.  If either URI is invalid,
1919  * the function returns %FALSE.
1920  *
1921  * Returns: %TRUE if the URIs are equal, %FALSE if not
1922  **/
1923 gboolean
1924 e_mail_folder_uri_equal (CamelSession *session,
1925                          const gchar *folder_uri_a,
1926                          const gchar *folder_uri_b)
1927 {
1928 	CamelStore *store_a;
1929 	CamelStore *store_b;
1930 	CamelStoreClass *class;
1931 	gchar *folder_name_a;
1932 	gchar *folder_name_b;
1933 	gboolean success_a;
1934 	gboolean success_b;
1935 	gboolean equal = FALSE;
1936 
1937 	g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1938 	g_return_val_if_fail (folder_uri_a != NULL, FALSE);
1939 	g_return_val_if_fail (folder_uri_b != NULL, FALSE);
1940 
1941 	success_a = e_mail_folder_uri_parse (
1942 		session, folder_uri_a, &store_a, &folder_name_a, NULL);
1943 
1944 	success_b = e_mail_folder_uri_parse (
1945 		session, folder_uri_b, &store_b, &folder_name_b, NULL);
1946 
1947 	if (!success_a || !success_b)
1948 		goto exit;
1949 
1950 	if (store_a != store_b)
1951 		goto exit;
1952 
1953 	/* Doesn't matter which store we use since they're the same. */
1954 	class = CAMEL_STORE_GET_CLASS (store_a);
1955 	g_return_val_if_fail (class->equal_folder_name != NULL, FALSE);
1956 
1957 	equal = class->equal_folder_name (folder_name_a, folder_name_b);
1958 
1959 exit:
1960 	if (success_a) {
1961 		g_object_unref (store_a);
1962 		g_free (folder_name_a);
1963 	}
1964 
1965 	if (success_b) {
1966 		g_object_unref (store_b);
1967 		g_free (folder_name_b);
1968 	}
1969 
1970 	return equal;
1971 }
1972 
1973 /**
1974  * e_mail_folder_uri_from_folder:
1975  * @folder: a #CamelFolder
1976  *
1977  * Convenience function for building a folder URI from a #CamelFolder.
1978  * Free the returned URI string with g_free().
1979  *
1980  * Returns: a newly-allocated folder URI string
1981  **/
1982 gchar *
1983 e_mail_folder_uri_from_folder (CamelFolder *folder)
1984 {
1985 	CamelStore *store;
1986 	const gchar *folder_name;
1987 
1988 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
1989 
1990 	store = camel_folder_get_parent_store (folder);
1991 	folder_name = camel_folder_get_full_name (folder);
1992 
1993 	return e_mail_folder_uri_build (store, folder_name);
1994 }
1995 
1996 /**
1997  * e_mail_folder_uri_to_markup:
1998  * @session: a #CamelSession
1999  * @folder_uri: a folder URI
2000  * @error: return location for a #GError, or %NULL
2001  *
2002  * Converts @folder_uri to a markup string suitable for displaying to users.
2003  * The string consists of the #CamelStore display name (in bold), followed
2004  * by the folder path.  If the URI is malformed or no corresponding store
2005  * exists, the function sets @error and returns %NULL.  Free the returned
2006  * string with g_free().
2007  *
2008  * Returns: a newly-allocated markup string, or %NULL
2009  **/
2010 gchar *
2011 e_mail_folder_uri_to_markup (CamelSession *session,
2012                              const gchar *folder_uri,
2013                              GError **error)
2014 {
2015 	CamelStore *store = NULL;
2016 	const gchar *display_name;
2017 	gchar *folder_name = NULL;
2018 	gchar *markup;
2019 	gboolean success;
2020 
2021 	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
2022 	g_return_val_if_fail (folder_uri != NULL, NULL);
2023 
2024 	success = e_mail_folder_uri_parse (
2025 		session, folder_uri, &store, &folder_name, error);
2026 
2027 	if (!success)
2028 		return NULL;
2029 
2030 	g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
2031 	g_return_val_if_fail (folder_name != NULL, NULL);
2032 
2033 	display_name = camel_service_get_display_name (CAMEL_SERVICE (store));
2034 
2035 	markup = g_markup_printf_escaped (
2036 		"<b>%s</b> : %s", display_name, folder_name);
2037 
2038 	g_object_unref (store);
2039 	g_free (folder_name);
2040 
2041 	return markup;
2042 }