evolution-3.6.4/widgets/misc/e-attachment-store.c

No issues found

   1 /*
   2  * e-attachment-store.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  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  19  *
  20  */
  21 
  22 #ifdef HAVE_CONFIG_H
  23 #include <config.h>
  24 #endif
  25 
  26 #include "e-attachment-store.h"
  27 
  28 #include <errno.h>
  29 #include <glib/gi18n.h>
  30 
  31 #include "e-util/e-util.h"
  32 #include "e-util/e-mktemp.h"
  33 
  34 #define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \
  35 	(G_TYPE_INSTANCE_GET_PRIVATE \
  36 	((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate))
  37 
  38 struct _EAttachmentStorePrivate {
  39 	GHashTable *attachment_index;
  40 
  41 	guint ignore_row_changed : 1;
  42 };
  43 
  44 enum {
  45 	PROP_0,
  46 	PROP_NUM_ATTACHMENTS,
  47 	PROP_NUM_LOADING,
  48 	PROP_TOTAL_SIZE
  49 };
  50 
  51 G_DEFINE_TYPE (
  52 	EAttachmentStore,
  53 	e_attachment_store,
  54 	GTK_TYPE_LIST_STORE)
  55 
  56 static void
  57 attachment_store_get_property (GObject *object,
  58                                guint property_id,
  59                                GValue *value,
  60                                GParamSpec *pspec)
  61 {
  62 	switch (property_id) {
  63 		case PROP_NUM_ATTACHMENTS:
  64 			g_value_set_uint (
  65 				value,
  66 				e_attachment_store_get_num_attachments (
  67 				E_ATTACHMENT_STORE (object)));
  68 			return;
  69 
  70 		case PROP_NUM_LOADING:
  71 			g_value_set_uint (
  72 				value,
  73 				e_attachment_store_get_num_loading (
  74 				E_ATTACHMENT_STORE (object)));
  75 			return;
  76 
  77 		case PROP_TOTAL_SIZE:
  78 			g_value_set_uint64 (
  79 				value,
  80 				e_attachment_store_get_total_size (
  81 				E_ATTACHMENT_STORE (object)));
  82 			return;
  83 	}
  84 
  85 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
  86 }
  87 
  88 static void
  89 attachment_store_dispose (GObject *object)
  90 {
  91 	e_attachment_store_remove_all (E_ATTACHMENT_STORE (object));
  92 
  93 	/* Chain up to parent's dispose() method. */
  94 	G_OBJECT_CLASS (e_attachment_store_parent_class)->dispose (object);
  95 }
  96 
  97 static void
  98 attachment_store_finalize (GObject *object)
  99 {
 100 	EAttachmentStorePrivate *priv;
 101 
 102 	priv = E_ATTACHMENT_STORE_GET_PRIVATE (object);
 103 
 104 	g_hash_table_destroy (priv->attachment_index);
 105 
 106 	/* Chain up to parent's finalize() method. */
 107 	G_OBJECT_CLASS (e_attachment_store_parent_class)->finalize (object);
 108 }
 109 
 110 static void
 111 e_attachment_store_class_init (EAttachmentStoreClass *class)
 112 {
 113 	GObjectClass *object_class;
 114 
 115 	g_type_class_add_private (class, sizeof (EAttachmentStorePrivate));
 116 
 117 	object_class = G_OBJECT_CLASS (class);
 118 	object_class->get_property = attachment_store_get_property;
 119 	object_class->dispose = attachment_store_dispose;
 120 	object_class->finalize = attachment_store_finalize;
 121 
 122 	g_object_class_install_property (
 123 		object_class,
 124 		PROP_NUM_ATTACHMENTS,
 125 		g_param_spec_uint (
 126 			"num-attachments",
 127 			"Num Attachments",
 128 			NULL,
 129 			0,
 130 			G_MAXUINT,
 131 			0,
 132 			G_PARAM_READABLE));
 133 
 134 	g_object_class_install_property (
 135 		object_class,
 136 		PROP_NUM_LOADING,
 137 		g_param_spec_uint (
 138 			"num-loading",
 139 			"Num Loading",
 140 			NULL,
 141 			0,
 142 			G_MAXUINT,
 143 			0,
 144 			G_PARAM_READABLE));
 145 
 146 	g_object_class_install_property (
 147 		object_class,
 148 		PROP_TOTAL_SIZE,
 149 		g_param_spec_uint64 (
 150 			"total-size",
 151 			"Total Size",
 152 			NULL,
 153 			0,
 154 			G_MAXUINT64,
 155 			0,
 156 			G_PARAM_READABLE));
 157 }
 158 
 159 static void
 160 e_attachment_store_init (EAttachmentStore *store)
 161 {
 162 	GType types[E_ATTACHMENT_STORE_NUM_COLUMNS];
 163 	GHashTable *attachment_index;
 164 	gint column = 0;
 165 
 166 	attachment_index = g_hash_table_new_full (
 167 		g_direct_hash, g_direct_equal,
 168 		(GDestroyNotify) g_object_unref,
 169 		(GDestroyNotify) gtk_tree_row_reference_free);
 170 
 171 	store->priv = E_ATTACHMENT_STORE_GET_PRIVATE (store);
 172 	store->priv->attachment_index = attachment_index;
 173 
 174 	types[column++] = E_TYPE_ATTACHMENT;	/* COLUMN_ATTACHMENT */
 175 	types[column++] = G_TYPE_STRING;	/* COLUMN_CAPTION */
 176 	types[column++] = G_TYPE_STRING;	/* COLUMN_CONTENT_TYPE */
 177 	types[column++] = G_TYPE_STRING;	/* COLUMN_DESCRIPTION */
 178 	types[column++] = G_TYPE_ICON;		/* COLUMN_ICON */
 179 	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_LOADING */
 180 	types[column++] = G_TYPE_INT;		/* COLUMN_PERCENT */
 181 	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_SAVING */
 182 	types[column++] = G_TYPE_UINT64;	/* COLUMN_SIZE */
 183 
 184 	g_assert (column == E_ATTACHMENT_STORE_NUM_COLUMNS);
 185 
 186 	gtk_list_store_set_column_types (
 187 		GTK_LIST_STORE (store), G_N_ELEMENTS (types), types);
 188 }
 189 
 190 GtkTreeModel *
 191 e_attachment_store_new (void)
 192 {
 193 	return g_object_new (E_TYPE_ATTACHMENT_STORE, NULL);
 194 }
 195 
 196 void
 197 e_attachment_store_add_attachment (EAttachmentStore *store,
 198                                    EAttachment *attachment)
 199 {
 200 	GtkTreeRowReference *reference;
 201 	GtkTreeModel *model;
 202 	GtkTreePath *path;
 203 	GtkTreeIter iter;
 204 
 205 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
 206 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
 207 
 208 	gtk_list_store_append (GTK_LIST_STORE (store), &iter);
 209 
 210 	gtk_list_store_set (
 211 		GTK_LIST_STORE (store), &iter,
 212 		E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, attachment, -1);
 213 
 214 	model = GTK_TREE_MODEL (store);
 215 	path = gtk_tree_model_get_path (model, &iter);
 216 	reference = gtk_tree_row_reference_new (model, path);
 217 	gtk_tree_path_free (path);
 218 
 219 	g_hash_table_insert (
 220 		store->priv->attachment_index,
 221 		g_object_ref (attachment), reference);
 222 
 223 	/* This lets the attachment tell us when to update. */
 224 	e_attachment_set_reference (attachment, reference);
 225 
 226 	g_object_freeze_notify (G_OBJECT (store));
 227 	g_object_notify (G_OBJECT (store), "num-attachments");
 228 	g_object_notify (G_OBJECT (store), "total-size");
 229 	g_object_thaw_notify (G_OBJECT (store));
 230 }
 231 
 232 gboolean
 233 e_attachment_store_remove_attachment (EAttachmentStore *store,
 234                                       EAttachment *attachment)
 235 {
 236 	GtkTreeRowReference *reference;
 237 	GHashTable *hash_table;
 238 	GtkTreeModel *model;
 239 	GtkTreePath *path;
 240 	GtkTreeIter iter;
 241 
 242 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
 243 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
 244 
 245 	hash_table = store->priv->attachment_index;
 246 	reference = g_hash_table_lookup (hash_table, attachment);
 247 
 248 	if (reference == NULL)
 249 		return FALSE;
 250 
 251 	if (!gtk_tree_row_reference_valid (reference)) {
 252 		g_hash_table_remove (hash_table, attachment);
 253 		return FALSE;
 254 	}
 255 
 256 	e_attachment_cancel (attachment);
 257 	e_attachment_set_reference (attachment, NULL);
 258 
 259 	model = gtk_tree_row_reference_get_model (reference);
 260 	path = gtk_tree_row_reference_get_path (reference);
 261 	gtk_tree_model_get_iter (model, &iter, path);
 262 	gtk_tree_path_free (path);
 263 
 264 	gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
 265 	g_hash_table_remove (hash_table, attachment);
 266 
 267 	g_object_freeze_notify (G_OBJECT (store));
 268 	g_object_notify (G_OBJECT (store), "num-attachments");
 269 	g_object_notify (G_OBJECT (store), "total-size");
 270 	g_object_thaw_notify (G_OBJECT (store));
 271 
 272 	return TRUE;
 273 }
 274 
 275 void
 276 e_attachment_store_remove_all (EAttachmentStore *store)
 277 {
 278 	GList *list, *iter;
 279 
 280 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
 281 
 282 	if (!g_hash_table_size (store->priv->attachment_index))
 283 		return;
 284 
 285 	g_object_freeze_notify (G_OBJECT (store));
 286 
 287 	list = e_attachment_store_get_attachments (store);
 288 	for (iter = list; iter; iter = iter->next) {
 289 		EAttachment *attachment = iter->data;
 290 
 291 		e_attachment_cancel (attachment);
 292 		g_hash_table_remove (store->priv->attachment_index, iter->data);
 293 	}
 294 
 295 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
 296 	g_list_free (list);
 297 
 298 	gtk_list_store_clear (GTK_LIST_STORE (store));
 299 
 300 	g_object_notify (G_OBJECT (store), "num-attachments");
 301 	g_object_notify (G_OBJECT (store), "total-size");
 302 	g_object_thaw_notify (G_OBJECT (store));
 303 }
 304 
 305 void
 306 e_attachment_store_add_to_multipart (EAttachmentStore *store,
 307                                      CamelMultipart *multipart,
 308                                      const gchar *default_charset)
 309 {
 310 	GList *list, *iter;
 311 
 312 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
 313 	g_return_if_fail (CAMEL_MULTIPART (multipart));
 314 
 315 	list = e_attachment_store_get_attachments (store);
 316 
 317 	for (iter = list; iter != NULL; iter = iter->next) {
 318 		EAttachment *attachment = iter->data;
 319 
 320 		/* Skip the attachment if it's still loading. */
 321 		if (!e_attachment_get_loading (attachment))
 322 			e_attachment_add_to_multipart (
 323 				attachment, multipart, default_charset);
 324 	}
 325 
 326 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
 327 	g_list_free (list);
 328 }
 329 
 330 GList *
 331 e_attachment_store_get_attachments (EAttachmentStore *store)
 332 {
 333 	GList *list = NULL;
 334 	GtkTreeModel *model;
 335 	GtkTreeIter iter;
 336 	gboolean valid;
 337 
 338 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
 339 
 340 	model = GTK_TREE_MODEL (store);
 341 	valid = gtk_tree_model_get_iter_first (model, &iter);
 342 
 343 	while (valid) {
 344 		EAttachment *attachment;
 345 		gint column_id;
 346 
 347 		column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
 348 		gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
 349 
 350 		list = g_list_prepend (list, attachment);
 351 
 352 		valid = gtk_tree_model_iter_next (model, &iter);
 353 	}
 354 
 355 	return g_list_reverse (list);
 356 }
 357 
 358 guint
 359 e_attachment_store_get_num_attachments (EAttachmentStore *store)
 360 {
 361 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
 362 
 363 	return g_hash_table_size (store->priv->attachment_index);
 364 }
 365 
 366 guint
 367 e_attachment_store_get_num_loading (EAttachmentStore *store)
 368 {
 369 	GList *list, *iter;
 370 	guint num_loading = 0;
 371 
 372 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
 373 
 374 	list = e_attachment_store_get_attachments (store);
 375 
 376 	for (iter = list; iter != NULL; iter = iter->next) {
 377 		EAttachment *attachment = iter->data;
 378 
 379 		if (e_attachment_get_loading (attachment))
 380 			num_loading++;
 381 	}
 382 
 383 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
 384 	g_list_free (list);
 385 
 386 	return num_loading;
 387 }
 388 
 389 goffset
 390 e_attachment_store_get_total_size (EAttachmentStore *store)
 391 {
 392 	GList *list, *iter;
 393 	goffset total_size = 0;
 394 
 395 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
 396 
 397 	list = e_attachment_store_get_attachments (store);
 398 
 399 	for (iter = list; iter != NULL; iter = iter->next) {
 400 		EAttachment *attachment = iter->data;
 401 		GFileInfo *file_info;
 402 
 403 		file_info = e_attachment_get_file_info (attachment);
 404 		if (file_info != NULL)
 405 			total_size += g_file_info_get_size (file_info);
 406 	}
 407 
 408 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
 409 	g_list_free (list);
 410 
 411 	return total_size;
 412 }
 413 
 414 void
 415 e_attachment_store_run_load_dialog (EAttachmentStore *store,
 416                                     GtkWindow *parent)
 417 {
 418 	GtkFileChooser *file_chooser;
 419 	GtkWidget *dialog;
 420 	GtkWidget *option;
 421 	GSList *files, *iter;
 422 	const gchar *disposition;
 423 	gboolean active;
 424 	gint response;
 425 
 426 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
 427 	g_return_if_fail (GTK_IS_WINDOW (parent));
 428 
 429 	dialog = gtk_file_chooser_dialog_new (
 430 		_("Add Attachment"), parent,
 431 		GTK_FILE_CHOOSER_ACTION_OPEN,
 432 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 433 		_("A_ttach"), GTK_RESPONSE_OK, NULL);
 434 
 435 	file_chooser = GTK_FILE_CHOOSER (dialog);
 436 	gtk_file_chooser_set_local_only (file_chooser, FALSE);
 437 	gtk_file_chooser_set_select_multiple (file_chooser, TRUE);
 438 	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
 439 	gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
 440 
 441 	option = gtk_check_button_new_with_mnemonic (
 442 		_("_Suggest automatic display of attachment"));
 443 	gtk_file_chooser_set_extra_widget (file_chooser, option);
 444 	gtk_widget_show (option);
 445 
 446 	response = gtk_dialog_run (GTK_DIALOG (dialog));
 447 
 448 	if (response != GTK_RESPONSE_OK)
 449 		goto exit;
 450 
 451 	files = gtk_file_chooser_get_files (file_chooser);
 452 	active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (option));
 453 	disposition = active ? "inline" : "attachment";
 454 
 455 	for (iter = files; iter != NULL; iter = g_slist_next (iter)) {
 456 		EAttachment *attachment;
 457 		GFile *file = iter->data;
 458 
 459 		attachment = e_attachment_new ();
 460 		e_attachment_set_file (attachment, file);
 461 		e_attachment_set_disposition (attachment, disposition);
 462 		e_attachment_store_add_attachment (store, attachment);
 463 		e_attachment_load_async (
 464 			attachment, (GAsyncReadyCallback)
 465 			e_attachment_load_handle_error, parent);
 466 		g_object_unref (attachment);
 467 	}
 468 
 469 	g_slist_foreach (files, (GFunc) g_object_unref, NULL);
 470 	g_slist_free (files);
 471 
 472 exit:
 473 	gtk_widget_destroy (dialog);
 474 }
 475 
 476 GFile *
 477 e_attachment_store_run_save_dialog (EAttachmentStore *store,
 478                                     GList *attachment_list,
 479                                     GtkWindow *parent)
 480 {
 481 	GtkFileChooser *file_chooser;
 482 	GtkFileChooserAction action;
 483 	GtkWidget *dialog;
 484 	GFile *destination;
 485 	const gchar *title;
 486 	gint response;
 487 	guint length;
 488 
 489 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
 490 
 491 	length = g_list_length (attachment_list);
 492 
 493 	if (length == 0)
 494 		return NULL;
 495 
 496 	title = ngettext ("Save Attachment", "Save Attachments", length);
 497 
 498 	if (length == 1)
 499 		action = GTK_FILE_CHOOSER_ACTION_SAVE;
 500 	else
 501 		action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
 502 
 503 	dialog = gtk_file_chooser_dialog_new (
 504 		title, parent, action,
 505 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 506 		GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL);
 507 
 508 	file_chooser = GTK_FILE_CHOOSER (dialog);
 509 	gtk_file_chooser_set_local_only (file_chooser, FALSE);
 510 	gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE);
 511 	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
 512 	gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
 513 
 514 	if (action == GTK_FILE_CHOOSER_ACTION_SAVE) {
 515 		EAttachment *attachment;
 516 		GFileInfo *file_info;
 517 		const gchar *name = NULL;
 518 
 519 		attachment = attachment_list->data;
 520 		file_info = e_attachment_get_file_info (attachment);
 521 		if (file_info != NULL)
 522 			name = g_file_info_get_display_name (file_info);
 523 		if (name == NULL)
 524 			/* Translators: Default attachment filename. */
 525 			name = _("attachment.dat");
 526 		gtk_file_chooser_set_current_name (file_chooser, name);
 527 	}
 528 
 529 	response = gtk_dialog_run (GTK_DIALOG (dialog));
 530 
 531 	if (response == GTK_RESPONSE_OK)
 532 		destination = gtk_file_chooser_get_file (file_chooser);
 533 	else
 534 		destination = NULL;
 535 
 536 	gtk_widget_destroy (dialog);
 537 
 538 	return destination;
 539 }
 540 
 541 /******************** e_attachment_store_get_uris_async() ********************/
 542 
 543 typedef struct _UriContext UriContext;
 544 
 545 struct _UriContext {
 546 	GSimpleAsyncResult *simple;
 547 	GList *attachment_list;
 548 	GError *error;
 549 	gchar **uris;
 550 	gint index;
 551 };
 552 
 553 static UriContext *
 554 attachment_store_uri_context_new (EAttachmentStore *store,
 555                                   GList *attachment_list,
 556                                   GAsyncReadyCallback callback,
 557                                   gpointer user_data)
 558 {
 559 	UriContext *uri_context;
 560 	GSimpleAsyncResult *simple;
 561 	guint length;
 562 	gchar **uris;
 563 
 564 	simple = g_simple_async_result_new (
 565 		G_OBJECT (store), callback, user_data,
 566 		e_attachment_store_get_uris_async);
 567 
 568 	/* Add one for NULL terminator. */
 569 	length = g_list_length (attachment_list) + 1;
 570 	uris = g_malloc0 (sizeof (gchar *) * length);
 571 
 572 	uri_context = g_slice_new0 (UriContext);
 573 	uri_context->simple = simple;
 574 	uri_context->attachment_list = g_list_copy (attachment_list);
 575 	uri_context->uris = uris;
 576 
 577 	g_list_foreach (
 578 		uri_context->attachment_list,
 579 		(GFunc) g_object_ref, NULL);
 580 
 581 	return uri_context;
 582 }
 583 
 584 static void
 585 attachment_store_uri_context_free (UriContext *uri_context)
 586 {
 587 	g_object_unref (uri_context->simple);
 588 
 589 	/* The attachment list should be empty now. */
 590 	g_warn_if_fail (uri_context->attachment_list == NULL);
 591 
 592 	/* So should the error. */
 593 	g_warn_if_fail (uri_context->error == NULL);
 594 
 595 	g_strfreev (uri_context->uris);
 596 
 597 	g_slice_free (UriContext, uri_context);
 598 }
 599 
 600 static void
 601 attachment_store_get_uris_save_cb (EAttachment *attachment,
 602                                    GAsyncResult *result,
 603                                    UriContext *uri_context)
 604 {
 605 	GSimpleAsyncResult *simple;
 606 	GFile *file;
 607 	gchar **uris;
 608 	gchar *uri;
 609 	GError *error = NULL;
 610 
 611 	file = e_attachment_save_finish (attachment, result, &error);
 612 
 613 	/* Remove the attachment from the list. */
 614 	uri_context->attachment_list = g_list_remove (
 615 		uri_context->attachment_list, attachment);
 616 	g_object_unref (attachment);
 617 
 618 	if (file != NULL) {
 619 		uri = g_file_get_uri (file);
 620 		uri_context->uris[uri_context->index++] = uri;
 621 		g_object_unref (file);
 622 
 623 	} else if (error != NULL) {
 624 		/* If this is the first error, cancel the other jobs. */
 625 		if (uri_context->error == NULL) {
 626 			g_propagate_error (&uri_context->error, error);
 627 			g_list_foreach (
 628 				uri_context->attachment_list,
 629 				(GFunc) e_attachment_cancel, NULL);
 630 			error = NULL;
 631 
 632 		/* Otherwise, we can only report back one error.  So if
 633 		 * this is something other than cancellation, dump it to
 634 		 * the terminal. */
 635 		} else if (!g_error_matches (
 636 			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 637 			g_warning ("%s", error->message);
 638 	}
 639 
 640 	if (error != NULL)
 641 		g_error_free (error);
 642 
 643 	/* If there's still jobs running, let them finish. */
 644 	if (uri_context->attachment_list != NULL)
 645 		return;
 646 
 647 	/* Steal the URI list. */
 648 	uris = uri_context->uris;
 649 	uri_context->uris = NULL;
 650 
 651 	/* And the error. */
 652 	error = uri_context->error;
 653 	uri_context->error = NULL;
 654 
 655 	simple = uri_context->simple;
 656 
 657 	if (error == NULL)
 658 		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
 659 	else
 660 		g_simple_async_result_take_error (simple, error);
 661 
 662 	g_simple_async_result_complete (simple);
 663 
 664 	attachment_store_uri_context_free (uri_context);
 665 }
 666 
 667 void
 668 e_attachment_store_get_uris_async (EAttachmentStore *store,
 669                                    GList *attachment_list,
 670                                    GAsyncReadyCallback callback,
 671                                    gpointer user_data)
 672 {
 673 	GFile *temp_directory;
 674 	UriContext *uri_context;
 675 	GList *iter, *trash = NULL;
 676 	gchar *template;
 677 	gchar *path;
 678 
 679 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
 680 
 681 	uri_context = attachment_store_uri_context_new (
 682 		store, attachment_list, callback, user_data);
 683 
 684 	/* Grab the copied attachment list. */
 685 	attachment_list = uri_context->attachment_list;
 686 
 687 	/* First scan the list for attachments with a GFile. */
 688 	for (iter = attachment_list; iter != NULL; iter = iter->next) {
 689 		EAttachment *attachment = iter->data;
 690 		GFile *file;
 691 		gchar *uri;
 692 
 693 		file = e_attachment_get_file (attachment);
 694 		if (file == NULL)
 695 			continue;
 696 
 697 		uri = g_file_get_uri (file);
 698 		uri_context->uris[uri_context->index++] = uri;
 699 
 700 		/* Mark the list node for deletion. */
 701 		trash = g_list_prepend (trash, iter);
 702 		g_object_unref (attachment);
 703 	}
 704 
 705 	/* Expunge the list. */
 706 	for (iter = trash; iter != NULL; iter = iter->next) {
 707 		GList *link = iter->data;
 708 		attachment_list = g_list_delete_link (attachment_list, link);
 709 	}
 710 	g_list_free (trash);
 711 
 712 	uri_context->attachment_list = attachment_list;
 713 
 714 	/* If we got them all then we're done. */
 715 	if (attachment_list == NULL) {
 716 		GSimpleAsyncResult *simple;
 717 		gchar **uris;
 718 
 719 		/* Steal the URI list. */
 720 		uris = uri_context->uris;
 721 		uri_context->uris = NULL;
 722 
 723 		simple = uri_context->simple;
 724 		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
 725 		g_simple_async_result_complete (simple);
 726 
 727 		attachment_store_uri_context_free (uri_context);
 728 		return;
 729 	}
 730 
 731 	/* Any remaining attachments in the list should have MIME parts
 732 	 * only, so we need to save them all to a temporary directory.
 733 	 * We use a directory so the files can retain their basenames.
 734 	 * XXX This could trigger a blocking temp directory cleanup. */
 735 	template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
 736 	path = e_mkdtemp (template);
 737 	g_free (template);
 738 
 739 	/* XXX Let's hope errno got set properly. */
 740 	if (path == NULL) {
 741 		GSimpleAsyncResult *simple;
 742 
 743 		simple = uri_context->simple;
 744 		g_simple_async_result_set_error (
 745 			simple, G_FILE_ERROR,
 746 			g_file_error_from_errno (errno),
 747 			"%s", g_strerror (errno));
 748 		g_simple_async_result_complete (simple);
 749 
 750 		attachment_store_uri_context_free (uri_context);
 751 		return;
 752 	}
 753 
 754 	temp_directory = g_file_new_for_path (path);
 755 
 756 	for (iter = attachment_list; iter != NULL; iter = iter->next)
 757 		e_attachment_save_async (
 758 			E_ATTACHMENT (iter->data),
 759 			temp_directory, (GAsyncReadyCallback)
 760 			attachment_store_get_uris_save_cb,
 761 			uri_context);
 762 
 763 	g_object_unref (temp_directory);
 764 	g_free (path);
 765 }
 766 
 767 gchar **
 768 e_attachment_store_get_uris_finish (EAttachmentStore *store,
 769                                     GAsyncResult *result,
 770                                     GError **error)
 771 {
 772 	GSimpleAsyncResult *simple;
 773 	gchar **uris;
 774 
 775 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
 776 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
 777 
 778 	simple = G_SIMPLE_ASYNC_RESULT (result);
 779 	uris = g_simple_async_result_get_op_res_gpointer (simple);
 780 	g_simple_async_result_propagate_error (simple, error);
 781 
 782 	return uris;
 783 }
 784 
 785 /********************** e_attachment_store_load_async() **********************/
 786 
 787 typedef struct _LoadContext LoadContext;
 788 
 789 struct _LoadContext {
 790 	GSimpleAsyncResult *simple;
 791 	GList *attachment_list;
 792 	GError *error;
 793 };
 794 
 795 static LoadContext *
 796 attachment_store_load_context_new (EAttachmentStore *store,
 797                                    GList *attachment_list,
 798                                    GAsyncReadyCallback callback,
 799                                    gpointer user_data)
 800 {
 801 	LoadContext *load_context;
 802 	GSimpleAsyncResult *simple;
 803 
 804 	simple = g_simple_async_result_new (
 805 		G_OBJECT (store), callback, user_data,
 806 		e_attachment_store_load_async);
 807 
 808 	load_context = g_slice_new0 (LoadContext);
 809 	load_context->simple = simple;
 810 	load_context->attachment_list = g_list_copy (attachment_list);
 811 
 812 	g_list_foreach (
 813 		load_context->attachment_list,
 814 		(GFunc) g_object_ref, NULL);
 815 
 816 	return load_context;
 817 }
 818 
 819 static void
 820 attachment_store_load_context_free (LoadContext *load_context)
 821 {
 822 	g_object_unref (load_context->simple);
 823 
 824 	/* The attachment list should be empty now. */
 825 	g_warn_if_fail (load_context->attachment_list == NULL);
 826 
 827 	/* So should the error. */
 828 	g_warn_if_fail (load_context->error == NULL);
 829 
 830 	g_slice_free (LoadContext, load_context);
 831 }
 832 
 833 static void
 834 attachment_store_load_ready_cb (EAttachment *attachment,
 835                                 GAsyncResult *result,
 836                                 LoadContext *load_context)
 837 {
 838 	GSimpleAsyncResult *simple;
 839 	GError *error = NULL;
 840 
 841 	e_attachment_load_finish (attachment, result, &error);
 842 
 843 	/* Remove the attachment from the list. */
 844 	load_context->attachment_list = g_list_remove (
 845 		load_context->attachment_list, attachment);
 846 	g_object_unref (attachment);
 847 
 848 	if (error != NULL) {
 849 		/* If this is the first error, cancel the other jobs. */
 850 		if (load_context->error == NULL) {
 851 			g_propagate_error (&load_context->error, error);
 852 			g_list_foreach (
 853 				load_context->attachment_list,
 854 				(GFunc) e_attachment_cancel, NULL);
 855 			error = NULL;
 856 
 857 		/* Otherwise, we can only report back one error.  So if
 858 		 * this is something other than cancellation, dump it to
 859 		 * the terminal. */
 860 		} else if (!g_error_matches (
 861 			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 862 			g_warning ("%s", error->message);
 863 	}
 864 
 865 	if (error != NULL)
 866 		g_error_free (error);
 867 
 868 	/* If there's still jobs running, let them finish. */
 869 	if (load_context->attachment_list != NULL)
 870 		return;
 871 
 872 	/* Steal the error. */
 873 	error = load_context->error;
 874 	load_context->error = NULL;
 875 
 876 	simple = load_context->simple;
 877 
 878 	if (error == NULL)
 879 		g_simple_async_result_set_op_res_gboolean (simple, TRUE);
 880 	else
 881 		g_simple_async_result_take_error (simple, error);
 882 
 883 	g_simple_async_result_complete (simple);
 884 
 885 	attachment_store_load_context_free (load_context);
 886 }
 887 
 888 void
 889 e_attachment_store_load_async (EAttachmentStore *store,
 890                                GList *attachment_list,
 891                                GAsyncReadyCallback callback,
 892                                gpointer user_data)
 893 {
 894 	LoadContext *load_context;
 895 	GList *iter;
 896 
 897 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
 898 
 899 	load_context = attachment_store_load_context_new (
 900 		store, attachment_list, callback, user_data);
 901 
 902 	if (attachment_list == NULL) {
 903 		GSimpleAsyncResult *simple;
 904 
 905 		simple = load_context->simple;
 906 		g_simple_async_result_set_op_res_gboolean (simple, TRUE);
 907 		g_simple_async_result_complete (simple);
 908 
 909 		attachment_store_load_context_free (load_context);
 910 		return;
 911 	}
 912 
 913 	for (iter = attachment_list; iter != NULL; iter = iter->next) {
 914 		EAttachment *attachment = E_ATTACHMENT (iter->data);
 915 
 916 		e_attachment_store_add_attachment (store, attachment);
 917 
 918 		e_attachment_load_async (
 919 			attachment, (GAsyncReadyCallback)
 920 			attachment_store_load_ready_cb,
 921 			load_context);
 922 	}
 923 }
 924 
 925 gboolean
 926 e_attachment_store_load_finish (EAttachmentStore *store,
 927                                 GAsyncResult *result,
 928                                 GError **error)
 929 {
 930 	GSimpleAsyncResult *simple;
 931 	gboolean success;
 932 
 933 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
 934 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
 935 
 936 	simple = G_SIMPLE_ASYNC_RESULT (result);
 937 	success = g_simple_async_result_get_op_res_gboolean (simple);
 938 	g_simple_async_result_propagate_error (simple, error);
 939 
 940 	return success;
 941 }
 942 
 943 /********************** e_attachment_store_save_async() **********************/
 944 
 945 typedef struct _SaveContext SaveContext;
 946 
 947 struct _SaveContext {
 948 	GSimpleAsyncResult *simple;
 949 	GFile *destination;
 950 	gchar *filename_prefix;
 951 	GFile *fresh_directory;
 952 	GFile *trash_directory;
 953 	GList *attachment_list;
 954 	GError *error;
 955 	gchar **uris;
 956 	gint index;
 957 };
 958 
 959 static SaveContext *
 960 attachment_store_save_context_new (EAttachmentStore *store,
 961                                    GFile *destination,
 962                                    const gchar *filename_prefix,
 963                                    GAsyncReadyCallback callback,
 964                                    gpointer user_data)
 965 {
 966 	SaveContext *save_context;
 967 	GSimpleAsyncResult *simple;
 968 	GList *attachment_list;
 969 	guint length;
 970 	gchar **uris;
 971 
 972 	simple = g_simple_async_result_new (
 973 		G_OBJECT (store), callback, user_data,
 974 		e_attachment_store_save_async);
 975 
 976 	attachment_list = e_attachment_store_get_attachments (store);
 977 
 978 	/* Add one for NULL terminator. */
 979 	length = g_list_length (attachment_list) + 1;
 980 	uris = g_malloc0 (sizeof (gchar *) * length);
 981 
 982 	save_context = g_slice_new0 (SaveContext);
 983 	save_context->simple = simple;
 984 	save_context->destination = g_object_ref (destination);
 985 	save_context->filename_prefix = g_strdup (filename_prefix);
 986 	save_context->attachment_list = attachment_list;
 987 	save_context->uris = uris;
 988 
 989 	return save_context;
 990 }
 991 
 992 static void
 993 attachment_store_save_context_free (SaveContext *save_context)
 994 {
 995 	g_object_unref (save_context->simple);
 996 
 997 	/* The attachment list should be empty now. */
 998 	g_warn_if_fail (save_context->attachment_list == NULL);
 999 
1000 	/* So should the error. */
1001 	g_warn_if_fail (save_context->error == NULL);
1002 
1003 	if (save_context->destination) {
1004 		g_object_unref (save_context->destination);
1005 		save_context->destination = NULL;
1006 	}
1007 
1008 	g_free (save_context->filename_prefix);
1009 	save_context->filename_prefix = NULL;
1010 
1011 	if (save_context->fresh_directory) {
1012 		g_object_unref (save_context->fresh_directory);
1013 		save_context->fresh_directory = NULL;
1014 	}
1015 
1016 	if (save_context->trash_directory) {
1017 		g_object_unref (save_context->trash_directory);
1018 		save_context->trash_directory = NULL;
1019 	}
1020 
1021 	g_strfreev (save_context->uris);
1022 
1023 	g_slice_free (SaveContext, save_context);
1024 }
1025 
1026 static void
1027 attachment_store_move_file (SaveContext *save_context,
1028                             GFile *source,
1029                             GFile *destination,
1030                             GError **error)
1031 {
1032 	gchar *tmpl;
1033 	gchar *path;
1034 
1035 	g_return_if_fail (save_context != NULL);
1036 	g_return_if_fail (source != NULL);
1037 	g_return_if_fail (destination != NULL);
1038 	g_return_if_fail (error != NULL);
1039 
1040 	/* Attachments are all saved to a temporary directory.
1041 	 * Now we need to move the existing destination directory
1042 	 * out of the way (if it exists).  Instead of testing for
1043 	 * existence we'll just attempt the move and ignore any
1044 	 * G_IO_ERROR_NOT_FOUND errors. */
1045 
1046 	/* First, however, we need another temporary directory to
1047 	 * move the existing destination directory to.  Note we're
1048 	 * not actually creating the directory yet, just picking a
1049 	 * name for it.  The usual raciness with this approach
1050 	 * applies here (read up on mktemp(3)), but worst case is
1051 	 * we get a spurious G_IO_ERROR_WOULD_MERGE error and the
1052 	 * user has to try saving attachments again. */
1053 	tmpl = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
1054 	path = e_mktemp (tmpl);
1055 	g_free (tmpl);
1056 
1057 	save_context->trash_directory = g_file_new_for_path (path);
1058 	g_free (path);
1059 
1060 	/* XXX No asynchronous move operation in GIO? */
1061 	g_file_move (
1062 		destination,
1063 		save_context->trash_directory,
1064 		G_FILE_COPY_NONE, NULL, NULL, NULL, error);
1065 
1066 	if (*error != NULL && !g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
1067 		return;
1068 
1069 	g_clear_error (error);
1070 
1071 	/* Now we can move the file from the temporary directory
1072 	 * to the user-specified destination. */
1073 	g_file_move (
1074 		source,
1075 		destination,
1076 		G_FILE_COPY_NONE, NULL, NULL, NULL, error);
1077 }
1078 
1079 static void
1080 attachment_store_save_cb (EAttachment *attachment,
1081                           GAsyncResult *result,
1082                           SaveContext *save_context)
1083 {
1084 	GSimpleAsyncResult *simple;
1085 	GFile *file;
1086 	gchar **uris;
1087 	GError *error = NULL;
1088 
1089 	file = e_attachment_save_finish (attachment, result, &error);
1090 
1091 	/* Remove the attachment from the list. */
1092 	save_context->attachment_list = g_list_remove (
1093 		save_context->attachment_list, attachment);
1094 	g_object_unref (attachment);
1095 
1096 	if (file != NULL) {
1097 		/* Assemble the file's final URI from its basename. */
1098 		gchar *basename;
1099 		gchar *uri;
1100 		GFile *source = NULL, *destination = NULL;
1101 
1102 		basename = g_file_get_basename (file);
1103 		g_object_unref (file);
1104 
1105 		source = g_file_get_child (save_context->fresh_directory, basename);
1106 
1107 		if (save_context->filename_prefix && *save_context->filename_prefix) {
1108 			gchar *tmp = basename;
1109 
1110 			basename = g_strconcat (save_context->filename_prefix, basename, NULL);
1111 			g_free (tmp);
1112 		}
1113 
1114 		file = save_context->destination;
1115 		destination = g_file_get_child (file, basename);
1116 		uri = g_file_get_uri (destination);
1117 
1118 		/* move them file-by-file */
1119 		attachment_store_move_file (save_context, source, destination, &error);
1120 
1121 		if (!error)
1122 			save_context->uris[save_context->index++] = uri;
1123 
1124 		g_object_unref (source);
1125 		g_object_unref (destination);
1126 	}
1127 
1128 	if (error != NULL) {
1129 		/* If this is the first error, cancel the other jobs. */
1130 		if (save_context->error == NULL) {
1131 			g_propagate_error (&save_context->error, error);
1132 			g_list_foreach (
1133 				save_context->attachment_list,
1134 				(GFunc) e_attachment_cancel, NULL);
1135 			error = NULL;
1136 
1137 		/* Otherwise, we can only report back one error.  So if
1138 		 * this is something other than cancellation, dump it to
1139 		 * the terminal. */
1140 		} else if (!g_error_matches (
1141 			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1142 			g_warning ("%s", error->message);
1143 	}
1144 
1145 	g_clear_error (&error);
1146 
1147 	/* If there's still jobs running, let them finish. */
1148 	if (save_context->attachment_list != NULL)
1149 		return;
1150 
1151 	/* If an error occurred while saving, we're done. */
1152 	if (save_context->error != NULL) {
1153 
1154 		/* Steal the error. */
1155 		error = save_context->error;
1156 		save_context->error = NULL;
1157 
1158 		simple = save_context->simple;
1159 		g_simple_async_result_take_error (simple, error);
1160 		g_simple_async_result_complete (simple);
1161 
1162 		attachment_store_save_context_free (save_context);
1163 		return;
1164 	}
1165 
1166 	if (error != NULL) {
1167 		simple = save_context->simple;
1168 		g_simple_async_result_take_error (simple, error);
1169 		g_simple_async_result_complete (simple);
1170 
1171 		attachment_store_save_context_free (save_context);
1172 		return;
1173 	}
1174 
1175 	/* clean-up left directory */
1176 	g_file_delete (save_context->fresh_directory, NULL, NULL);
1177 
1178 	/* And the URI list. */
1179 	uris = save_context->uris;
1180 	save_context->uris = NULL;
1181 
1182 	simple = save_context->simple;
1183 	g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
1184 	g_simple_async_result_complete (simple);
1185 
1186 	attachment_store_save_context_free (save_context);
1187 }
1188 /*
1189  * @filename_prefix: prefix to use for a file name; can be %NULL for none
1190  **/
1191 void
1192 e_attachment_store_save_async (EAttachmentStore *store,
1193                                GFile *destination,
1194                                const gchar *filename_prefix,
1195                                GAsyncReadyCallback callback,
1196                                gpointer user_data)
1197 {
1198 	SaveContext *save_context;
1199 	GList *attachment_list, *iter;
1200 	GFile *temp_directory;
1201 	gchar *template;
1202 	gchar *path;
1203 
1204 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
1205 	g_return_if_fail (G_IS_FILE (destination));
1206 
1207 	save_context = attachment_store_save_context_new (
1208 		store, destination, filename_prefix, callback, user_data);
1209 
1210 	attachment_list = save_context->attachment_list;
1211 
1212 	/* Deal with an empty attachment store.  The caller will get
1213 	 * an empty NULL-terminated list as opposed to NULL, to help
1214 	 * distinguish it from an error. */
1215 	if (attachment_list == NULL) {
1216 		GSimpleAsyncResult *simple;
1217 		gchar **uris;
1218 
1219 		/* Steal the URI list. */
1220 		uris = save_context->uris;
1221 		save_context->uris = NULL;
1222 
1223 		simple = save_context->simple;
1224 		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
1225 		g_simple_async_result_complete (simple);
1226 
1227 		attachment_store_save_context_free (save_context);
1228 		return;
1229 	}
1230 
1231 	/* Save all attachments to a temporary directory, which we'll
1232 	 * then move to its proper location.  We use a directory so
1233 	 * files can retain their basenames.
1234 	 * XXX This could trigger a blocking temp directory cleanup. */
1235 	template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
1236 	path = e_mkdtemp (template);
1237 	g_free (template);
1238 
1239 	/* XXX Let's hope errno got set properly. */
1240 	if (path == NULL) {
1241 		GSimpleAsyncResult *simple;
1242 
1243 		simple = save_context->simple;
1244 		g_simple_async_result_set_error (
1245 			simple, G_FILE_ERROR,
1246 			g_file_error_from_errno (errno),
1247 			"%s", g_strerror (errno));
1248 		g_simple_async_result_complete (simple);
1249 
1250 		attachment_store_save_context_free (save_context);
1251 		return;
1252 	}
1253 
1254 	temp_directory = g_file_new_for_path (path);
1255 	save_context->fresh_directory = temp_directory;
1256 	g_free (path);
1257 
1258 	for (iter = attachment_list; iter != NULL; iter = iter->next)
1259 		e_attachment_save_async (
1260 			E_ATTACHMENT (iter->data),
1261 			temp_directory, (GAsyncReadyCallback)
1262 			attachment_store_save_cb, save_context);
1263 }
1264 
1265 gchar **
1266 e_attachment_store_save_finish (EAttachmentStore *store,
1267                                 GAsyncResult *result,
1268                                 GError **error)
1269 {
1270 	GSimpleAsyncResult *simple;
1271 	gchar **uris;
1272 
1273 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
1274 	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
1275 
1276 	simple = G_SIMPLE_ASYNC_RESULT (result);
1277 	uris = g_simple_async_result_get_op_res_gpointer (simple);
1278 	g_simple_async_result_propagate_error (simple, error);
1279 
1280 	return uris;
1281 }