evolution r37476 - in branches/kill-bonobo: calendar/gui/dialogs composer mail widgets/misc



Author: mbarnes
Date: Thu Mar 26 04:48:21 2009
New Revision: 37476
URL: http://svn.gnome.org/viewvc/evolution?rev=37476&view=rev

Log:
Saving progress again on the attachment rewrite.


Modified:
   branches/kill-bonobo/calendar/gui/dialogs/comp-editor.c
   branches/kill-bonobo/composer/e-msg-composer.c
   branches/kill-bonobo/mail/em-format-html-display.c
   branches/kill-bonobo/widgets/misc/e-attachment-dialog.c
   branches/kill-bonobo/widgets/misc/e-attachment-icon-view.c
   branches/kill-bonobo/widgets/misc/e-attachment-store.c
   branches/kill-bonobo/widgets/misc/e-attachment-store.h
   branches/kill-bonobo/widgets/misc/e-attachment-tree-view.c
   branches/kill-bonobo/widgets/misc/e-attachment-view.c
   branches/kill-bonobo/widgets/misc/e-attachment-view.h
   branches/kill-bonobo/widgets/misc/e-attachment.c
   branches/kill-bonobo/widgets/misc/e-attachment.h

Modified: branches/kill-bonobo/calendar/gui/dialogs/comp-editor.c
==============================================================================
--- branches/kill-bonobo/calendar/gui/dialogs/comp-editor.c	(original)
+++ branches/kill-bonobo/calendar/gui/dialogs/comp-editor.c	Thu Mar 26 04:48:21 2009
@@ -2261,6 +2261,9 @@
 
 		attachment = e_attachment_new_for_uri (uri);
 		e_attachment_store_add_attachment (store, attachment);
+		e_attachment_load_async (
+			attachment, (GAsyncReadyCallback)
+			e_attachment_load_handle_error, editor);
 		g_object_unref (attachment);
 	}
 }

Modified: branches/kill-bonobo/composer/e-msg-composer.c
==============================================================================
--- branches/kill-bonobo/composer/e-msg-composer.c	(original)
+++ branches/kill-bonobo/composer/e-msg-composer.c	Thu Mar 26 04:48:21 2009
@@ -1959,6 +1959,9 @@
 
 		attachment = e_attachment_new_for_uri (uri);
 		e_attachment_store_add_attachment (store, attachment);
+		e_attachment_load_async (
+			attachment, (GAsyncReadyCallback)
+			e_attachment_load_handle_error, composer);
 		g_object_unref (attachment);
 	}
 
@@ -3443,6 +3446,9 @@
 
 				attachment = e_attachment_new_for_uri (content);
 				e_attachment_store_add_attachment (store, attachment);
+				e_attachment_load_async (
+					attachment, (GAsyncReadyCallback)
+					e_attachment_load_handle_error, composer);
 				g_object_unref (attachment);
 			} else if (!g_ascii_strcasecmp (header, "from")) {
 				/* Ignore */
@@ -3679,6 +3685,9 @@
 	attachment = e_attachment_new ();
 	e_attachment_set_mime_part (attachment, mime_part);
 	e_attachment_store_add_attachment (store, attachment);
+	e_attachment_load_async (
+		attachment, (GAsyncReadyCallback)
+		e_attachment_load_handle_error, composer);
 	g_object_unref (attachment);
 }
 

Modified: branches/kill-bonobo/mail/em-format-html-display.c
==============================================================================
--- branches/kill-bonobo/mail/em-format-html-display.c	(original)
+++ branches/kill-bonobo/mail/em-format-html-display.c	Thu Mar 26 04:48:21 2009
@@ -1319,10 +1319,17 @@
 	if (efhd->priv->attachment_bar) {
 		EAttachmentView *view;
 		EAttachmentStore *store;
+		gpointer parent;
+
+		parent = gtk_widget_get_toplevel (efhd->priv->attachment_bar);
+		parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
 
 		view = E_ATTACHMENT_VIEW (efhd->priv->attachment_bar);
 		store = e_attachment_view_get_store (view);
 		e_attachment_store_add_attachment (store, info->attachment);
+		e_attachment_load_async (
+			info->attachment, (GAsyncReadyCallback)
+			e_attachment_load_handle_error, parent);
 
 #if 0  /* KILL-BONOBO */
 		file = camel_mime_part_get_filename(info->puri.part);

Modified: branches/kill-bonobo/widgets/misc/e-attachment-dialog.c
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment-dialog.c	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment-dialog.c	Thu Mar 26 04:48:21 2009
@@ -58,20 +58,24 @@
 
 	attachment = e_attachment_dialog_get_attachment (dialog);
 
-	if (E_IS_ATTACHMENT (attachment)) {
+	if (attachment != NULL) {
 		file_info = e_attachment_get_file_info (attachment);
-		content_type = e_attachment_get_content_type (attachment);
-		display_name = e_attachment_get_display_name (attachment);
 		description = e_attachment_get_description (attachment);
 		disposition = e_attachment_get_disposition (attachment);
 	} else {
 		file_info = NULL;
-		content_type = NULL;
-		display_name = NULL;
 		description = NULL;
 		disposition = NULL;
 	}
 
+	if (file_info != NULL) {
+		content_type = g_file_info_get_content_type (file_info);
+		display_name = g_file_info_get_display_name (file_info);
+	} else {
+		content_type = NULL;
+		display_name = NULL;
+	}
+
 	if (content_type != NULL) {
 		gchar *comment;
 		gchar *mime_type;

Modified: branches/kill-bonobo/widgets/misc/e-attachment-icon-view.c
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment-icon-view.c	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment-icon-view.c	Thu Mar 26 04:48:21 2009
@@ -21,6 +21,7 @@
 
 #include "e-attachment-icon-view.h"
 
+#include <glib/gi18n.h>
 #include <gdk/gdkkeysyms.h>
 
 #include "e-attachment.h"
@@ -163,6 +164,15 @@
 	return TRUE;
 }
 
+static void
+attachment_icon_view_item_activated (GtkIconView *icon_view,
+                                     GtkTreePath *path)
+{
+	EAttachmentView *view = E_ATTACHMENT_VIEW (icon_view);
+
+	e_attachment_view_open_path (view, path, NULL);
+}
+
 static EAttachmentViewPrivate *
 attachment_icon_view_get_private (EAttachmentView *view)
 {
@@ -265,6 +275,7 @@
 {
 	GObjectClass *object_class;
 	GtkWidgetClass *widget_class;
+	GtkIconViewClass *icon_view_class;
 
 	parent_class = g_type_class_peek_parent (class);
 	g_type_class_add_private (class, sizeof (EAttachmentViewPrivate));
@@ -282,6 +293,9 @@
 	widget_class->drag_data_received = attachment_icon_view_drag_data_received;
 	widget_class->popup_menu = attachment_icon_view_popup_menu;
 
+	icon_view_class = GTK_ICON_VIEW_CLASS (class);
+	icon_view_class->item_activated = attachment_icon_view_item_activated;
+
 	g_object_class_override_property (
 		object_class, PROP_EDITABLE, "editable");
 }
@@ -317,7 +331,7 @@
 
 	renderer = gtk_cell_renderer_pixbuf_new ();
 	g_object_set (renderer, "stock-size", GTK_ICON_SIZE_DIALOG, NULL);
-	gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+	gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
 
 	gtk_cell_layout_add_attribute (
 		cell_layout, renderer, "gicon",
@@ -327,11 +341,35 @@
 	g_object_set (
 		renderer, "alignment", PANGO_ALIGN_CENTER,
 		"xalign", 0.5, NULL);
-	gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+	gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
 
 	gtk_cell_layout_add_attribute (
 		cell_layout, renderer, "text",
 		E_ATTACHMENT_STORE_COLUMN_CAPTION);
+
+	renderer = gtk_cell_renderer_progress_new ();
+	g_object_set (renderer, "text", _("Loading"), NULL);
+	gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
+
+	gtk_cell_layout_add_attribute (
+		cell_layout, renderer, "value",
+		E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+	gtk_cell_layout_add_attribute (
+		cell_layout, renderer, "visible",
+		E_ATTACHMENT_STORE_COLUMN_LOADING);
+
+	renderer = gtk_cell_renderer_progress_new ();
+	g_object_set (renderer, "text", _("Saving"), NULL);
+	gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
+
+	gtk_cell_layout_add_attribute (
+		cell_layout, renderer, "value",
+		E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+	gtk_cell_layout_add_attribute (
+		cell_layout, renderer, "visible",
+		E_ATTACHMENT_STORE_COLUMN_SAVING);
 }
 
 GType

Modified: branches/kill-bonobo/widgets/misc/e-attachment-store.c
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment-store.c	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment-store.c	Thu Mar 26 04:48:21 2009
@@ -26,8 +26,6 @@
 #include "e-util/e-util.h"
 #include "e-util/gconf-bridge.h"
 
-#include "e-file-activity.h"
-
 #define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate))
@@ -35,7 +33,6 @@
 #define DEFAULT_ICON_NAME	"mail-attachment"
 
 struct _EAttachmentStorePrivate {
-	GHashTable *activity_index;
 	GHashTable *attachment_index;
 	gchar *background_filename;
 	gchar *background_options;
@@ -54,13 +51,7 @@
 	PROP_TOTAL_SIZE
 };
 
-enum {
-	NEW_ACTIVITY,
-	LAST_SIGNAL
-};
-
 static gpointer parent_class;
-static guint signals[LAST_SIGNAL];
 
 static const gchar *
 attachment_store_get_background_filename (EAttachmentStore *store)
@@ -101,183 +92,6 @@
 }
 
 static void
-attachment_store_remove_activity (EAttachmentStore *store,
-                                  EActivity *activity)
-{
-	GtkTreeRowReference *reference;
-	GHashTable *hash_table;
-
-	hash_table = store->priv->activity_index;
-	reference = g_hash_table_lookup (hash_table, activity);
-
-	if (gtk_tree_row_reference_valid (reference)) {
-		GtkTreeModel *model;
-		GtkTreePath *path;
-		GtkTreeIter iter;
-
-		model = gtk_tree_row_reference_get_model (reference);
-		path = gtk_tree_row_reference_get_path (reference);
-		gtk_tree_model_get_iter (model, &iter, path);
-		gtk_tree_path_free (path);
-
-		gtk_list_store_set (
-			GTK_LIST_STORE (store), &iter,
-			E_ATTACHMENT_STORE_COLUMN_ACTIVITY, NULL, -1);
-	}
-
-	g_hash_table_remove (hash_table, activity);
-
-	g_object_notify (G_OBJECT (store), "num-loading");
-}
-
-static void
-attachment_store_copy_ready (GFile *source,
-                             GAsyncResult *result,
-                             GtkTreeRowReference *reference)
-{
-	EAttachmentStore *store;
-	EAttachment *attachment;
-	EActivity *activity;
-	GFile *destination;
-	GtkTreeModel *model;
-	GtkTreePath *path;
-	GtkTreeIter iter;
-	gboolean valid;
-	GError *error = NULL;
-
-	model = gtk_tree_row_reference_get_model (reference);
-	path = gtk_tree_row_reference_get_path (reference);
-	valid = gtk_tree_model_get_iter (model, &iter, path);
-	gtk_tree_path_free (path);
-	g_return_if_fail (valid);
-
-	gtk_tree_model_get (
-		model, &iter,
-		E_ATTACHMENT_STORE_COLUMN_ACTIVITY, &activity,
-		E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, &attachment, -1);
-
-	gtk_tree_row_reference_free (reference);
-
-	store = E_ATTACHMENT_STORE (model);
-
-	if (!g_file_copy_finish (source, result, &error))
-		goto fail;
-
-	gtk_list_store_set (
-		GTK_LIST_STORE (store), &iter,
-		E_ATTACHMENT_STORE_COLUMN_ACTIVITY, NULL, -1);
-
-	destination = e_file_activity_get_file (E_FILE_ACTIVITY (activity));
-	e_attachment_set_file (attachment, destination);
-
-	e_activity_complete (activity);
-
-	g_object_unref (attachment);
-	g_object_unref (activity);
-
-	return;
-
-fail:
-	e_attachment_store_remove_attachment (store, attachment);
-
-	g_object_unref (attachment);
-	g_object_unref (activity);
-
-	/* XXX Do something more useful with the error. */
-	g_warning ("%s", error->message);
-	g_error_free (error);
-}
-
-static void
-attachment_store_copy_async (EAttachmentStore *store,
-                             EAttachment *attachment)
-{
-	EActivity *activity;
-	GCancellable *cancellable;
-	GtkTreeRowReference *reference;
-	GtkTreeModel *model;
-	GtkTreePath *path;
-	GtkTreeIter iter;
-	GHashTable *hash_table;
-	GFile *destination;
-	GFile *source;
-	gboolean valid;
-	gchar *filename;
-	gchar *uri;
-	gint fd;
-	GError *error = NULL;
-
-	hash_table = store->priv->attachment_index;
-	reference = g_hash_table_lookup (hash_table, attachment);
-	g_return_if_fail (reference != NULL);
-
-	fd = e_file_open_tmp (&filename, &error);
-	if (error != NULL)
-		goto fail;
-
-	source = e_attachment_get_file (attachment);
-	destination = g_file_new_for_path (filename);
-
-	g_free (filename);
-	close (fd);
-
-	model = gtk_tree_row_reference_get_model (reference);
-	path = gtk_tree_row_reference_get_path (reference);
-	valid = gtk_tree_model_get_iter (model, &iter, path);
-	gtk_tree_path_free (path);
-	g_return_if_fail (valid);
-
-	uri = g_file_get_uri (source);
-	activity = e_file_activity_newv (_("Downloading '%s'"), uri);
-	g_free (uri);
-
-	gtk_list_store_set (
-		GTK_LIST_STORE (store), &iter,
-		E_ATTACHMENT_STORE_COLUMN_ACTIVITY, activity, -1);
-
-	reference = gtk_tree_row_reference_copy (reference);
-
-	hash_table = store->priv->activity_index;
-	g_hash_table_insert (hash_table, g_object_ref (activity), reference);
-
-	g_signal_connect_swapped (
-		activity, "cancelled",
-		G_CALLBACK (attachment_store_remove_activity), store);
-
-	g_signal_connect_swapped (
-		activity, "completed",
-		G_CALLBACK (attachment_store_remove_activity), store);
-
-	reference = gtk_tree_row_reference_copy (reference);
-
-	cancellable = e_file_activity_get_cancellable (
-		E_FILE_ACTIVITY (activity));
-
-	g_file_copy_async (
-		source, destination, G_FILE_COPY_OVERWRITE,
-		G_PRIORITY_DEFAULT, cancellable, (GFileProgressCallback)
-		e_file_activity_progress, activity, (GAsyncReadyCallback)
-		attachment_store_copy_ready, reference);
-
-	e_file_activity_set_file (E_FILE_ACTIVITY (activity), destination);
-	g_signal_emit (store, signals[NEW_ACTIVITY], 0, activity);
-
-	g_object_notify (G_OBJECT (store), "num-loading");
-
-	g_object_unref (activity);
-	g_object_unref (destination);
-
-	return;
-
-fail:
-	e_attachment_store_remove_attachment (store, attachment);
-
-	/* XXX Do something more useful with the error. */
-	g_warning ("%s", error->message);
-	g_error_free (error);
-}
-
-static void
 attachment_store_set_property (GObject *object,
                                guint property_id,
                                const GValue *value,
@@ -366,7 +180,6 @@
 
 	priv = E_ATTACHMENT_STORE_GET_PRIVATE (object);
 
-	g_hash_table_remove_all (priv->activity_index);
 	g_hash_table_remove_all (priv->attachment_index);
 
 	/* Chain up to parent's dispose() method. */
@@ -380,7 +193,6 @@
 
 	priv = E_ATTACHMENT_STORE_GET_PRIVATE (object);
 
-	g_hash_table_destroy (priv->activity_index);
 	g_hash_table_destroy (priv->attachment_index);
 
 	g_free (priv->background_filename);
@@ -418,6 +230,7 @@
 {
 	EAttachmentStorePrivate *priv;
 	EAttachment *attachment;
+	GFileInfo *file_info;
 	GFile *file;
 	GIcon *icon;
 	GList *list;
@@ -429,8 +242,9 @@
 	gchar *caption;
 	gboolean loading;
 	gboolean saving;
-	guint64 size;
+	goffset size;
 	gint column_id;
+	gint percent;
 
 	priv = E_ATTACHMENT_STORE_GET_PRIVATE (model);
 
@@ -441,17 +255,24 @@
 	gtk_tree_model_get (model, iter, column_id, &attachment, -1);
 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
 
-	content_type = e_attachment_get_content_type (attachment);
-	display_name = e_attachment_get_display_name (attachment);
+	file_info = e_attachment_get_file_info (attachment);
+	if (file_info == NULL) {
+		g_object_unref (attachment);
+		return;
+	}
+
+	content_type = g_file_info_get_content_type (file_info);
+	display_name = g_file_info_get_display_name (file_info);
 	thumbnail_path = e_attachment_get_thumbnail_path (attachment);
 	loading = e_attachment_get_loading (attachment);
+	percent = e_attachment_get_percent (attachment);
 	saving = e_attachment_get_saving (attachment);
-	icon = e_attachment_get_icon (attachment);
-	size = e_attachment_get_size (attachment);
+	icon = g_file_info_get_icon (file_info);
+	size = g_file_info_get_size (file_info);
 
 	content_type = (content_type != NULL) ? content_type : "";
 	content_description = g_content_type_get_description (content_type);
-	display_size = g_format_size_for_display ((goffset) size);
+	display_size = g_format_size_for_display (size);
 
 	if (size > 0)
 		caption = g_strdup_printf (
@@ -507,6 +328,7 @@
 		E_ATTACHMENT_STORE_COLUMN_CAPTION, caption,
 		E_ATTACHMENT_STORE_COLUMN_ICON, icon,
 		E_ATTACHMENT_STORE_COLUMN_LOADING, loading,
+		E_ATTACHMENT_STORE_COLUMN_PERCENT, percent,
 		E_ATTACHMENT_STORE_COLUMN_SAVING, saving,
 		E_ATTACHMENT_STORE_COLUMN_SIZE, size,
 		-1);
@@ -613,31 +435,24 @@
 attachment_store_init (EAttachmentStore *store)
 {
 	GType types[E_ATTACHMENT_STORE_NUM_COLUMNS];
-	GHashTable *activity_index;
 	GHashTable *attachment_index;
 	gint column = 0;
 
-	activity_index = g_hash_table_new_full (
-		g_direct_hash, g_direct_equal,
-		(GDestroyNotify) g_object_unref,
-		(GDestroyNotify) gtk_tree_row_reference_free);
-
 	attachment_index = g_hash_table_new_full (
 		g_direct_hash, g_direct_equal,
 		(GDestroyNotify) g_object_unref,
 		(GDestroyNotify) gtk_tree_row_reference_free);
 
 	store->priv = E_ATTACHMENT_STORE_GET_PRIVATE (store);
-	store->priv->activity_index = activity_index;
 	store->priv->attachment_index = attachment_index;
 
-	types[column++] = E_TYPE_ACTIVITY;	/* COLUMN_ACTIVITY */
 	types[column++] = E_TYPE_ATTACHMENT;	/* COLUMN_ATTACHMENT */
 	types[column++] = G_TYPE_STRING;	/* COLUMN_CAPTION */
 	types[column++] = G_TYPE_STRING;	/* COLUMN_CONTENT_TYPE */
 	types[column++] = G_TYPE_STRING;	/* COLUMN_DISPLAY_NAME */
 	types[column++] = G_TYPE_ICON;		/* COLUMN_ICON */
 	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_LOADING */
+	types[column++] = G_TYPE_INT;		/* COLUMN_PERCENT */
 	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_SAVING */
 	types[column++] = G_TYPE_UINT64;	/* COLUMN_SIZE */
 
@@ -720,10 +535,7 @@
 	file = e_attachment_get_file (attachment);
 
 	/* This lets the attachment tell us when to update. */
-	_e_attachment_set_reference (attachment, reference);
-
-	if (file != NULL && !g_file_is_native (file))
-		attachment_store_copy_async (store, attachment);
+	e_attachment_set_reference (attachment, reference);
 
 	g_object_freeze_notify (G_OBJECT (store));
 	g_object_notify (G_OBJECT (store), "num-attachments");
@@ -737,7 +549,6 @@
 {
 	GtkTreeRowReference *reference;
 	GHashTable *hash_table;
-	EActivity *activity;
 	GtkTreeModel *model;
 	GtkTreePath *path;
 	GtkTreeIter iter;
@@ -756,21 +567,14 @@
 		return FALSE;
 	}
 
+	e_attachment_cancel (attachment);
+	e_attachment_set_reference (attachment, NULL);
+
 	model = gtk_tree_row_reference_get_model (reference);
 	path = gtk_tree_row_reference_get_path (reference);
 	gtk_tree_model_get_iter (model, &iter, path);
 	gtk_tree_path_free (path);
 
-	gtk_tree_model_get (
-		model, &iter,
-		E_ATTACHMENT_STORE_COLUMN_ACTIVITY, &activity, -1);
-
-	if (activity != NULL) {
-		/* Cancel the file transfer. */
-		e_activity_cancel (activity);
-		g_object_unref (activity);
-	}
-
 	gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
 	g_hash_table_remove (hash_table, attachment);
 
@@ -804,8 +608,10 @@
 		column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
 		gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
 
-		e_attachment_add_to_multipart (
-			attachment, multipart, default_charset);
+		/* Skip the attachment if it's still loading. */
+		if (!e_attachment_get_loading (attachment))
+			e_attachment_add_to_multipart (
+				attachment, multipart, default_charset);
 
 		g_object_unref (attachment);
 
@@ -847,17 +653,38 @@
 guint
 e_attachment_store_get_num_loading (EAttachmentStore *store)
 {
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	guint num_loading = 0;
+	gboolean valid;
+
 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
 
-	return g_hash_table_size (store->priv->activity_index);
+	model = GTK_TREE_MODEL (store);
+	valid = gtk_tree_model_get_iter_first (model, &iter);
+
+	while (valid) {
+		EAttachment *attachment;
+		gint column_id;
+
+		column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+		gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+		if (e_attachment_get_loading (attachment))
+			num_loading++;
+		g_object_unref (attachment);
+
+		valid = gtk_tree_model_iter_next (model, &iter);
+	}
+
+	return num_loading;
 }
 
-guint64
+goffset
 e_attachment_store_get_total_size (EAttachmentStore *store)
 {
 	GtkTreeModel *model;
 	GtkTreeIter iter;
-	guint64 total_size = 0;
+	goffset total_size = 0;
 	gboolean valid;
 
 	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
@@ -867,11 +694,14 @@
 
 	while (valid) {
 		EAttachment *attachment;
+		GFileInfo *file_info;
 		gint column_id;
 
 		column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
 		gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
-		total_size += e_attachment_get_size (attachment);
+		file_info = e_attachment_get_file_info (attachment);
+		if (file_info != NULL)
+			total_size += g_file_info_get_size (file_info);
 		g_object_unref (attachment);
 
 		valid = gtk_tree_model_iter_next (model, &iter);
@@ -928,6 +758,7 @@
 	gint response;
 
 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
 
 	dialog = gtk_file_chooser_dialog_new (
 		_("Add Attachment"), parent,
@@ -962,6 +793,9 @@
 		attachment = e_attachment_new ();
 		e_attachment_set_file (attachment, file);
 		e_attachment_store_add_attachment (store, attachment);
+		e_attachment_load_async (
+			attachment, (GAsyncReadyCallback)
+			e_attachment_load_handle_error, parent);
 		g_object_unref (attachment);
 	}
 
@@ -979,13 +813,14 @@
 {
 	GtkFileChooser *file_chooser;
 	GtkWidget *dialog;
-	GFile *file;
-	EActivity *activity;
+	GFile *destination;
+	GFileInfo *file_info;
 	const gchar *display_name;
 	gint response;
 
 	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
 
 	dialog = gtk_file_chooser_dialog_new (
 		_("Save Attachment"), parent,
@@ -999,7 +834,11 @@
 	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
 	gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
 
-	display_name = e_attachment_get_display_name (attachment);
+	file_info = e_attachment_get_file_info (attachment);
+	if (file_info != NULL)
+		display_name = g_file_info_get_display_name (file_info);
+	else
+		display_name = NULL;
 	if (display_name != NULL)
 		gtk_file_chooser_set_current_name (file_chooser, display_name);
 
@@ -1008,13 +847,13 @@
 	if (response != GTK_RESPONSE_OK)
 		goto exit;
 
-	file = gtk_file_chooser_get_file (file_chooser);
-	activity = e_file_activity_new (_("Saving attachment"));
+	destination = gtk_file_chooser_get_file (file_chooser);
+
 	e_attachment_save_async (
-		attachment, E_FILE_ACTIVITY (activity), file);
-	g_signal_emit (store, signals[NEW_ACTIVITY], 0, activity);
-	g_object_unref (activity);
-	g_object_unref (file);
+		attachment, destination, (GAsyncReadyCallback)
+		e_attachment_save_handle_error, parent);
+
+	g_object_unref (destination);
 
 exit:
 	gtk_widget_destroy (dialog);

Modified: branches/kill-bonobo/widgets/misc/e-attachment-store.h
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment-store.h	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment-store.h	Thu Mar 26 04:48:21 2009
@@ -60,13 +60,13 @@
 };
 
 enum {
-	E_ATTACHMENT_STORE_COLUMN_ACTIVITY,	/* E_TYPE_ACTIVITY */
 	E_ATTACHMENT_STORE_COLUMN_ATTACHMENT,	/* E_TYPE_ATTACHMENT */
 	E_ATTACHMENT_STORE_COLUMN_CAPTION,	/* G_TYPE_STRING */
 	E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, /* G_TYPE_STRING */
 	E_ATTACHMENT_STORE_COLUMN_DISPLAY_NAME,	/* G_TYPE_STRING */
 	E_ATTACHMENT_STORE_COLUMN_ICON,		/* G_TYPE_ICON */
 	E_ATTACHMENT_STORE_COLUMN_LOADING,	/* G_TYPE_BOOLEAN */
+	E_ATTACHMENT_STORE_COLUMN_PERCENT,	/* G_TYPE_INT */
 	E_ATTACHMENT_STORE_COLUMN_SAVING,	/* G_TYPE_BOOLEAN */
 	E_ATTACHMENT_STORE_COLUMN_SIZE,		/* G_TYPE_UINT64 */
 	E_ATTACHMENT_STORE_NUM_COLUMNS
@@ -93,7 +93,7 @@
 						(EAttachmentStore *store);
 guint		e_attachment_store_get_num_loading
 						(EAttachmentStore *store);
-guint64		e_attachment_store_get_total_size
+goffset		e_attachment_store_get_total_size
 						(EAttachmentStore *store);
 gint		e_attachment_store_run_file_chooser_dialog
 						(EAttachmentStore *store,

Modified: branches/kill-bonobo/widgets/misc/e-attachment-tree-view.c
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment-tree-view.c	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment-tree-view.c	Thu Mar 26 04:48:21 2009
@@ -182,6 +182,16 @@
 	return TRUE;
 }
 
+static void
+attachment_tree_view_row_activated (GtkTreeView *tree_view,
+                                    GtkTreePath *path,
+                                    GtkTreeViewColumn *column)
+{
+	EAttachmentView *view = E_ATTACHMENT_VIEW (tree_view);
+
+	e_attachment_view_open_path (view, path, NULL);
+}
+
 static EAttachmentViewPrivate *
 attachment_tree_view_get_private (EAttachmentView *view)
 {
@@ -301,6 +311,7 @@
 {
 	GObjectClass *object_class;
 	GtkWidgetClass *widget_class;
+	GtkTreeViewClass *tree_view_class;
 
 	parent_class = g_type_class_peek_parent (class);
 	g_type_class_add_private (class, sizeof (EAttachmentViewPrivate));
@@ -318,6 +329,9 @@
 	widget_class->drag_data_received = attachment_tree_view_drag_data_received;
 	widget_class->popup_menu = attachment_tree_view_popup_menu;
 
+	tree_view_class = GTK_TREE_VIEW_CLASS (class);
+	tree_view_class->row_activated = attachment_tree_view_row_activated;
+
 	g_object_class_override_property (
 		object_class, PROP_EDITABLE, "editable");
 }
@@ -353,6 +367,8 @@
 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
 
+	/* Name Column */
+
 	column = gtk_tree_view_column_new ();
 	gtk_tree_view_column_set_expand (column, TRUE);
 	gtk_tree_view_column_set_spacing (column, 3);
@@ -376,6 +392,32 @@
 		column, renderer, "text",
 		E_ATTACHMENT_STORE_COLUMN_DISPLAY_NAME);
 
+	renderer = gtk_cell_renderer_progress_new ();
+	g_object_set (renderer, "text", _("Loading"), NULL);
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "value",
+		E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "visible",
+		E_ATTACHMENT_STORE_COLUMN_LOADING);
+
+	renderer = gtk_cell_renderer_progress_new ();
+	g_object_set (renderer, "text", _("Saving"), NULL);
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "value",
+		E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "visible",
+		E_ATTACHMENT_STORE_COLUMN_SAVING);
+
+	/* Size Column */
+
 	column = gtk_tree_view_column_new ();
 	gtk_tree_view_column_set_title (column, _("Size"));
 	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
@@ -387,6 +429,8 @@
 		column, renderer, (GtkTreeCellDataFunc)
 		attachment_tree_view_render_size, NULL, NULL);
 
+	/* Type Column */
+
 	column = gtk_tree_view_column_new ();
 	gtk_tree_view_column_set_title (column, _("Type"));
 	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);

Modified: branches/kill-bonobo/widgets/misc/e-attachment-view.c
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment-view.c	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment-view.c	Thu Mar 26 04:48:21 2009
@@ -67,6 +67,7 @@
 static const gchar *ui =
 "<ui>"
 "  <popup name='context'>"
+"    <menuitem action='cancel'/>"
 "    <menuitem action='save-as'/>"
 "    <menuitem action='set-background'/>"
 "    <menuitem action='remove'/>"
@@ -100,6 +101,23 @@
 }
 
 static void
+action_cancel_cb (GtkAction *action,
+                  EAttachmentView *view)
+{
+	EAttachment *attachment;
+	GList *selected;
+
+	selected = e_attachment_view_get_selected_attachments (view);
+	g_return_if_fail (g_list_length (selected) == 1);
+	attachment = selected->data;
+
+	e_attachment_cancel (attachment);
+
+	g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+	g_list_free (selected);
+}
+
+static void
 action_drag_cancel_cb (GtkAction *action,
                        EAttachmentView *view)
 {
@@ -128,23 +146,20 @@
                    EAttachmentView *view)
 {
 	GAppInfo *app_info;
-	EActivity *activity;
-	EAttachment *attachment;
+	GtkTreePath *path;
+	GList *selected;
+
+	selected = e_attachment_view_get_selected_paths (view);
+	g_return_if_fail (g_list_length (selected) == 1);
+	path = selected->data;
 
 	app_info = g_object_get_data (G_OBJECT (action), "app-info");
 	g_return_if_fail (G_IS_APP_INFO (app_info));
 
-	attachment = g_object_get_data (G_OBJECT (action), "attachment");
-	g_return_if_fail (E_IS_ATTACHMENT (attachment));
-
-	activity = e_file_activity_newv (
-		_("Opening attachment in %s"),
-		g_app_info_get_name (app_info));
+	e_attachment_view_open_path (view, path, app_info);
 
-	e_attachment_launch_async (
-		attachment, E_FILE_ACTIVITY (activity), app_info);
-
-	g_object_unref (activity);
+	g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (selected);
 }
 
 static void
@@ -178,14 +193,21 @@
 	GtkRecentChooser *chooser;
 	EAttachmentStore *store;
 	EAttachment *attachment;
+	gpointer parent;
 	gchar *uri;
 
 	chooser = GTK_RECENT_CHOOSER (action);
 	store = e_attachment_view_get_store (view);
 
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
 	uri = gtk_recent_chooser_get_current_uri (chooser);
 	attachment = e_attachment_new_for_uri (uri);
 	e_attachment_store_add_attachment (store, attachment);
+	e_attachment_load_async (
+		attachment, (GAsyncReadyCallback)
+		e_attachment_load_handle_error, parent);
 	g_free (uri);
 }
 
@@ -200,16 +222,42 @@
 action_save_as_cb (GtkAction *action,
                    EAttachmentView *view)
 {
+	EAttachmentStore *store;
+	EAttachment *attachment;
+	GList *selected;
+	gpointer parent;
+
+	store = e_attachment_view_get_store (view);
+
+	selected = e_attachment_view_get_selected_attachments (view);
+	g_return_if_fail (g_list_length (selected) == 1);
+	attachment = selected->data;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+	e_attachment_store_run_save_dialog (store, attachment, parent);
+
+	g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+	g_list_free (selected);
 }
 
 static void
 action_set_background_cb (GtkAction *action,
                           EAttachmentView *view)
 {
+	/* FIXME */
 }
 
 static GtkActionEntry standard_entries[] = {
 
+	{ "cancel",
+	  GTK_STOCK_CANCEL,
+	  NULL,
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_cancel_cb) },
+
 	{ "drag-cancel",
 	  NULL,
 	  N_("Cancel _Drag"),
@@ -284,6 +332,7 @@
 	const gchar *data;
 	gboolean success = FALSE;
 	gboolean delete = FALSE;
+	gpointer parent;
 	gint length;
 
 	priv = e_attachment_view_get_private (view);
@@ -301,8 +350,14 @@
 	if (camel_data_wrapper_construct_from_stream (wrapper, stream) == -1)
 		goto exit;
 
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
 	attachment = e_attachment_new_for_message (message);
 	e_attachment_store_add_attachment (store, attachment);
+	e_attachment_load_async (
+		attachment, (GAsyncReadyCallback)
+		e_attachment_load_handle_error, parent);
 	g_object_unref (attachment);
 
 	success = TRUE;
@@ -324,6 +379,7 @@
 	EAttachmentViewPrivate *priv;
 	EAttachment *attachment;
 	const gchar *data;
+	gpointer parent;
 	gchar *copied_data;
 	gchar **strv;
 	gint length;
@@ -339,8 +395,14 @@
 	strv = g_strsplit (copied_data, "\n", 2);
 	g_free (copied_data);
 
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
 	attachment = e_attachment_new_for_uri (strv[0]);
 	e_attachment_store_add_attachment (store, attachment);
+	e_attachment_load_async (
+		attachment, (GAsyncReadyCallback)
+		e_attachment_load_handle_error, parent);
 	g_object_unref (attachment);
 
 	g_strfreev (strv);
@@ -355,6 +417,7 @@
                     GdkDragAction action)
 {
 	EAttachmentViewPrivate *priv;
+	gpointer parent;
 	gchar **uris;
 	gint ii;
 
@@ -362,11 +425,17 @@
 
 	uris = gtk_selection_data_get_uris (selection_data);
 
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
 	for (ii = 0; uris[ii] != NULL; ii++) {
 		EAttachment *attachment;
 
 		attachment = e_attachment_new_for_uri (uris[ii]);
 		e_attachment_store_add_attachment (store, attachment);
+		e_attachment_load_async (
+			attachment, (GAsyncReadyCallback)
+			e_attachment_load_handle_error, parent);
 		g_object_unref (attachment);
 	}
 
@@ -386,6 +455,7 @@
 	CamelMimePart *mime_part;
 	GdkAtom atom;
 	const gchar *data;
+	gpointer parent;
 	gchar *content_type;
 	gint length;
 
@@ -402,9 +472,15 @@
 	camel_mime_part_set_disposition (mime_part, "inline");
 	g_free (content_type);
 
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
 	attachment = e_attachment_new ();
 	e_attachment_set_mime_part (attachment, mime_part);
 	e_attachment_store_add_attachment (store, attachment);
+	e_attachment_load_async (
+		attachment, (GAsyncReadyCallback)
+		e_attachment_load_handle_error, parent);
 	g_object_unref (attachment);
 
 	camel_object_unref (mime_part);
@@ -635,6 +711,8 @@
 	GList *selected, *item;
 	gint column_id;
 
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
 	column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
 	selected = e_attachment_view_get_selected_paths (view);
 	store = e_attachment_view_get_store (view);
@@ -657,6 +735,39 @@
 
 	return selected;
 }
+
+void
+e_attachment_view_open_path (EAttachmentView *view,
+                             GtkTreePath *path,
+                             GAppInfo *app_info)
+{
+	EAttachmentStore *store;
+	EAttachment *attachment;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gpointer parent;
+	gint column_id;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (path != NULL);
+
+	column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+	store = e_attachment_view_get_store (view);
+	model = GTK_TREE_MODEL (store);
+
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+	e_attachment_open_async (
+		attachment, app_info, (GAsyncReadyCallback)
+		e_attachment_open_handle_error, parent);
+
+	g_object_unref (attachment);
+}
+
 void
 e_attachment_view_remove_selected (EAttachmentView *view,
                                    gboolean select_next)
@@ -1076,6 +1187,7 @@
 	GList *list, *iter;
 	guint n_selected;
 	gboolean is_image;
+	gboolean busy = FALSE;
 
 	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
 
@@ -1086,6 +1198,8 @@
 	if (n_selected == 1) {
 		attachment = g_object_ref (list->data);
 		is_image = e_attachment_is_image (attachment);
+		busy |= e_attachment_get_loading (attachment);
+		busy |= e_attachment_get_saving (attachment);
 	} else {
 		attachment = NULL;
 		is_image = FALSE;
@@ -1094,23 +1208,26 @@
 	g_list_foreach (list, (GFunc) g_object_unref, NULL);
 	g_list_free (list);
 
+	action = e_attachment_view_get_action (view, "cancel");
+	gtk_action_set_visible (action, busy);
+
 	action = e_attachment_view_get_action (view, "properties");
-	gtk_action_set_visible (action, n_selected == 1);
+	gtk_action_set_visible (action, !busy && n_selected == 1);
 
 	action = e_attachment_view_get_action (view, "remove");
-	gtk_action_set_visible (action, n_selected > 0);
+	gtk_action_set_visible (action, !busy && n_selected > 0);
 
 	action = e_attachment_view_get_action (view, "save-as");
-	gtk_action_set_visible (action, n_selected > 0);
+	gtk_action_set_visible (action, !busy && n_selected == 1);
 
 	action = e_attachment_view_get_action (view, "set-background");
-	gtk_action_set_visible (action, is_image);
+	gtk_action_set_visible (action, !busy && is_image);
 
 	/* Clear out the "openwith" action group. */
 	gtk_ui_manager_remove_ui (priv->ui_manager, priv->merge_id);
 	e_action_group_remove_all_actions (priv->openwith_actions);
 
-	if (attachment == NULL)
+	if (attachment == NULL || busy)
 		return;
 
 	list = e_attachment_list_apps (attachment);

Modified: branches/kill-bonobo/widgets/misc/e-attachment-view.h
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment-view.h	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment-view.h	Thu Mar 26 04:48:21 2009
@@ -107,6 +107,9 @@
 						 gboolean editable);
 GList *		e_attachment_view_get_selected_attachments
 						(EAttachmentView *view);
+void		e_attachment_view_open_path	(EAttachmentView *view,
+						 GtkTreePath *path,
+						 GAppInfo *app_info);
 void		e_attachment_view_remove_selected
 						(EAttachmentView *view,
 						 gboolean select_next);

Modified: branches/kill-bonobo/widgets/misc/e-attachment.c
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment.c	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment.c	Thu Mar 26 04:48:21 2009
@@ -27,6 +27,7 @@
 #include <camel/camel-data-wrapper.h>
 #include <camel/camel-mime-message.h>
 #include <camel/camel-stream-filter.h>
+#include <camel/camel-stream-mem.h>
 #include <camel/camel-stream-null.h>
 #include <camel/camel-stream-vfs.h>
 
@@ -37,6 +38,7 @@
 	((obj), E_TYPE_ATTACHMENT, EAttachmentPrivate))
 
 /* Emblems */
+#define EMBLEM_CANCELLED	"gtk-cancel"
 #define EMBLEM_LOADING		"emblem-downloads"
 #define EMBLEM_SAVING		"document-save"
 #define EMBLEM_ENCRYPT_WEAK	"security-low"
@@ -54,7 +56,10 @@
 	GFileInfo *file_info;
 	GCancellable *cancellable;
 	CamelMimePart *mime_part;
+	guint emblem_timeout_id;
 	gchar *disposition;
+	gint percent;
+
 	guint loading : 1;
 	guint saving  : 1;
 
@@ -76,44 +81,14 @@
 	PROP_FILE_INFO,
 	PROP_LOADING,
 	PROP_MIME_PART,
+	PROP_PERCENT,
+	PROP_REFERENCE,
+	PROP_SAVING,
 	PROP_SIGNED
 };
 
 static gpointer parent_class;
 
-static void
-attachment_notify_model (EAttachment *attachment)
-{
-	GtkTreeRowReference *reference;
-	GtkTreeModel *model;
-	GtkTreePath *path;
-	GtkTreeIter iter;
-
-	reference = attachment->priv->reference;
-
-	if (reference == NULL)
-		return;
-
-	/* Free the reference if it's no longer valid.
-	 * It means we've been removed from the store. */
-	if (!gtk_tree_row_reference_valid (reference)) {
-		gtk_tree_row_reference_free (reference);
-		attachment->priv->reference = NULL;
-		return;
-	}
-
-	model = gtk_tree_row_reference_get_model (reference);
-	path = gtk_tree_row_reference_get_path (reference);
-
-	gtk_tree_model_get_iter (model, &iter, path);
-	gtk_tree_model_row_changed (model, path, &iter);
-
-	/* XXX This doesn't really belong here. */
-	g_object_notify (G_OBJECT (model), "total-size");
-
-	gtk_tree_path_free (path);
-}
-
 static gchar *
 attachment_get_default_charset (void)
 {
@@ -147,18 +122,34 @@
 }
 
 static void
+attachment_notify_model (EAttachment *attachment)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	reference = e_attachment_get_reference (attachment);
+
+	if (reference == NULL)
+		return;
+
+	model = gtk_tree_row_reference_get_model (reference);
+	path = gtk_tree_row_reference_get_path (reference);
+
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_row_changed (model, path, &iter);
+
+	gtk_tree_path_free (path);
+}
+
+static void
 attachment_set_file_info (EAttachment *attachment,
                           GFileInfo *file_info)
 {
-	GCancellable *cancellable;
-
-	cancellable = attachment->priv->cancellable;
+	GtkTreeRowReference *reference;
 
-	/* Cancel any query operations in progress. */
-	if (!g_cancellable_is_cancelled (cancellable)) {
-		g_cancellable_cancel (cancellable);
-		g_cancellable_reset (cancellable);
-	}
+	reference = e_attachment_get_reference (attachment);
 
 	if (file_info != NULL)
 		g_object_ref (file_info);
@@ -169,6 +160,14 @@
 	attachment->priv->file_info = file_info;
 
 	g_object_notify (G_OBJECT (attachment), "file-info");
+
+	/* Tell the EAttachmentStore its total size changed. */
+	if (reference != NULL) {
+		GtkTreeModel *model;
+		model = gtk_tree_row_reference_get_model (reference);
+		g_object_notify (G_OBJECT (model), "total-size");
+	}
+
 	attachment_notify_model (attachment);
 }
 
@@ -176,9 +175,24 @@
 attachment_set_loading (EAttachment *attachment,
                         gboolean loading)
 {
+	GtkTreeRowReference *reference;
+
+	reference = e_attachment_get_reference (attachment);
+
+	attachment->priv->percent = 0;
 	attachment->priv->loading = loading;
 
+	g_object_freeze_notify (G_OBJECT (attachment));
+	g_object_notify (G_OBJECT (attachment), "percent");
 	g_object_notify (G_OBJECT (attachment), "loading");
+	g_object_thaw_notify (G_OBJECT (attachment));
+
+	if (reference != NULL) {
+		GtkTreeModel *model;
+		model = gtk_tree_row_reference_get_model (reference);
+		g_object_notify (G_OBJECT (model), "num-loading");
+	}
+
 	attachment_notify_model (attachment);
 }
 
@@ -186,170 +200,53 @@
 attachment_set_saving (EAttachment *attachment,
                        gboolean saving)
 {
+	attachment->priv->percent = 0;
 	attachment->priv->saving = saving;
 
-	g_object_notify (G_OBJECT (attachment), "saving");
-	attachment_notify_model (attachment);
-}
-
-static void
-attachment_reset (EAttachment *attachment)
-{
-	GCancellable *cancellable;
-
-	cancellable = attachment->priv->cancellable;
-
 	g_object_freeze_notify (G_OBJECT (attachment));
-
-	/* Cancel any I/O operations in progress. */
-	if (!g_cancellable_is_cancelled (cancellable)) {
-		g_cancellable_cancel (cancellable);
-		g_cancellable_reset (cancellable);
-	}
-
-	if (attachment->priv->file != NULL) {
-		g_object_notify (G_OBJECT (attachment), "file");
-		g_object_unref (attachment->priv->file);
-		attachment->priv->file = NULL;
-	}
-
-	if (attachment->priv->mime_part != NULL) {
-		g_object_notify (G_OBJECT (attachment), "mime-part");
-		g_object_unref (attachment->priv->mime_part);
-		attachment->priv->mime_part = NULL;
-	}
-
-	attachment_set_file_info (attachment, NULL);
-
+	g_object_notify (G_OBJECT (attachment), "percent");
+	g_object_notify (G_OBJECT (attachment), "saving");
 	g_object_thaw_notify (G_OBJECT (attachment));
+
+	attachment_notify_model (attachment);
 }
 
 static void
-attachment_file_info_ready_cb (GFile *file,
-                               GAsyncResult *result,
-                               EAttachment *attachment)
+attachment_progress_cb (goffset current_num_bytes,
+                        goffset total_num_bytes,
+                        EAttachment *attachment)
 {
-	GFileInfo *file_info;
-	GError *error = NULL;
+	attachment->priv->percent =
+		(current_num_bytes * 100) / total_num_bytes;
 
-	/* Even if we failed to obtain a GFileInfo, we still emit a
-	 * "notify::file-info" to signal the async operation finished. */
-	file_info = g_file_query_info_finish (file, result, &error);
-	attachment_set_file_info (attachment, file_info);
+	g_object_notify (G_OBJECT (attachment), "percent");
 
-	if (file_info != NULL)
-		g_object_unref (file_info);
-	else {
-		g_warning ("%s", error->message);
-		g_error_free (error);
-	}
+	attachment_notify_model (attachment);
 }
 
-static void
-attachment_file_info_to_mime_part (EAttachment *attachment,
-                                   CamelMimePart *mime_part)
+static gboolean
+attachment_cancelled_timeout_cb (EAttachment *attachment)
 {
-	GFileInfo *file_info;
-	const gchar *attribute;
-	const gchar *string;
-	gchar *allocated;
-
-	file_info = e_attachment_get_file_info (attachment);
+	attachment->priv->emblem_timeout_id = 0;
+	g_cancellable_reset (attachment->priv->cancellable);
 
-	if (file_info == NULL || mime_part == NULL)
-		return;
-
-	/* XXX Note that we skip "standard::size" here.
-	 *     The CamelMimePart already knows the size. */
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
-	string = g_file_info_get_attribute_string (file_info, attribute);
-	allocated = g_content_type_get_mime_type (string);
-	camel_mime_part_set_content_type (mime_part, allocated);
-	g_free (allocated);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME;
-	string = g_file_info_get_attribute_string (file_info, attribute);
-	camel_mime_part_set_filename (mime_part, string);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
-	string = g_file_info_get_attribute_string (file_info, attribute);
-	camel_mime_part_set_description (mime_part, string);
+	attachment_notify_model (attachment);
 
-	string = e_attachment_get_disposition (attachment);
-	camel_mime_part_set_disposition (mime_part, string);
+	return FALSE;
 }
 
 static void
-attachment_populate_file_info (EAttachment *attachment,
-                               GFileInfo *file_info)
+attachment_cancelled_cb (EAttachment *attachment)
 {
-	CamelContentType *content_type;
-	CamelMimePart *mime_part;
-	const gchar *attribute;
-	const gchar *string;
-	gchar *allocated;
-	guint64 v_uint64;
-
-	mime_part = e_attachment_get_mime_part (attachment);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
-	content_type = camel_mime_part_get_content_type (mime_part);
-	allocated = camel_content_type_simple (content_type);
-	if (allocated != NULL) {
-		GIcon *icon;
-		gchar *cp;
-
-		/* GIO expects lowercase MIME types. */
-		for (cp = allocated; *cp != '\0'; cp++)
-			*cp = g_ascii_tolower (*cp);
-
-		/* Swap the MIME type for a content type. */
-		cp = g_content_type_from_mime_type (allocated);
-		g_free (allocated);
-		allocated = cp;
-
-		/* Use the MIME part's filename if we have to. */
-		if (g_content_type_is_unknown (allocated)) {
-			string = camel_mime_part_get_filename (mime_part);
-			if (string != NULL) {
-				g_free (allocated);
-				allocated = g_content_type_guess (
-					string, NULL, 0, NULL);
-			}
-		}
-
-		g_file_info_set_attribute_string (
-			file_info, attribute, allocated);
-
-		attribute = G_FILE_ATTRIBUTE_STANDARD_ICON;
-		icon = g_content_type_get_icon (allocated);
-		if (icon != NULL) {
-			g_file_info_set_attribute_object (
-				file_info, attribute, G_OBJECT (icon));
-			g_object_unref (icon);
-		}
-	}
-	g_free (allocated);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME;
-	string = camel_mime_part_get_filename (mime_part);
-	if (string != NULL)
-		g_file_info_set_attribute_string (
-			file_info, attribute, string);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
-	string = camel_mime_part_get_description (mime_part);
-	if (string != NULL)
-		g_file_info_set_attribute_string (
-			file_info, attribute, string);
+	/* Reset the GCancellable after one second.  This causes a
+	 * cancel emblem to be briefly shown on the attachment icon
+	 * as visual feedback that an operation was cancelled. */
 
-	attribute = G_FILE_ATTRIBUTE_STANDARD_SIZE;
-	v_uint64 = camel_mime_part_get_content_size (mime_part);
-	g_file_info_set_attribute_uint64 (file_info, attribute, v_uint64);
+	if (attachment->priv->emblem_timeout_id > 0)
+		g_source_remove (attachment->priv->emblem_timeout_id);
 
-	string = camel_mime_part_get_disposition (mime_part);
-	e_attachment_set_disposition (attachment, string);
+	attachment->priv->emblem_timeout_id = g_timeout_add_seconds (
+		1, (GSourceFunc) attachment_cancelled_timeout_cb, attachment);
 }
 
 static void
@@ -383,6 +280,12 @@
 				g_value_get_boxed (value));
 			return;
 
+		case PROP_REFERENCE:
+			e_attachment_set_reference (
+				E_ATTACHMENT (object),
+				g_value_get_boxed (value));
+			return;
+
 		case PROP_SIGNED:
 			e_attachment_set_signed (
 				E_ATTACHMENT (object),
@@ -436,6 +339,24 @@
 				E_ATTACHMENT (object)));
 			return;
 
+		case PROP_PERCENT:
+			g_value_set_int (
+				value, e_attachment_get_percent (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_REFERENCE:
+			g_value_set_boxed (
+				value, e_attachment_get_reference (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_SAVING:
+			g_value_set_boolean (
+				value, e_attachment_get_saving (
+				E_ATTACHMENT (object)));
+			return;
+
 		case PROP_SIGNED:
 			g_value_set_int (
 				value, e_attachment_get_signed (
@@ -453,12 +374,6 @@
 
 	priv = E_ATTACHMENT_GET_PRIVATE (object);
 
-	if (priv->cancellable != NULL) {
-		g_cancellable_cancel (priv->cancellable);
-		g_object_unref (priv->cancellable);
-		priv->cancellable = NULL;
-	}
-
 	if (priv->file != NULL) {
 		g_object_unref (priv->file);
 		priv->file = NULL;
@@ -469,11 +384,21 @@
 		priv->file_info = NULL;
 	}
 
+	if (priv->cancellable != NULL) {
+		g_object_unref (priv->cancellable);
+		priv->cancellable = NULL;
+	}
+
 	if (priv->mime_part != NULL) {
 		camel_object_unref (priv->mime_part);
 		priv->mime_part = NULL;
 	}
 
+	if (priv->emblem_timeout_id > 0) {
+		g_source_remove (priv->emblem_timeout_id);
+		priv->emblem_timeout_id = 0;
+	}
+
 	/* This accepts NULL arguments. */
 	gtk_tree_row_reference_free (priv->reference);
 	priv->reference = NULL;
@@ -575,6 +500,38 @@
 			E_TYPE_CAMEL_OBJECT,
 			G_PARAM_READWRITE));
 
+	g_object_class_install_property (
+		object_class,
+		PROP_PERCENT,
+		g_param_spec_int (
+			"percent",
+			"Percent",
+			NULL,
+			0,
+			100,
+			0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REFERENCE,
+		g_param_spec_boxed (
+			"reference",
+			"Reference",
+			NULL,
+			GTK_TYPE_TREE_ROW_REFERENCE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SAVING,
+		g_param_spec_boolean (
+			"saving",
+			"Saving",
+			NULL,
+			FALSE,
+			G_PARAM_READABLE));
+
 	/* FIXME Define a GEnumClass for this. */
 	g_object_class_install_property (
 		object_class,
@@ -597,6 +554,10 @@
 	attachment->priv->cancellable = g_cancellable_new ();
 	attachment->priv->encrypted = CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE;
 	attachment->priv->signed_ = CAMEL_CIPHER_VALIDITY_SIGN_NONE;
+
+	g_signal_connect_swapped (
+		attachment->priv->cancellable, "cancelled",
+		G_CALLBACK (attachment_cancelled_cb), attachment);
 }
 
 GType
@@ -779,6 +740,14 @@
 	camel_multipart_add_part (multipart, mime_part);
 }
 
+void
+e_attachment_cancel (EAttachment *attachment)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	g_cancellable_cancel (attachment->priv->cancellable);
+}
+
 const gchar *
 e_attachment_get_disposition (EAttachment *attachment)
 {
@@ -811,34 +780,19 @@
 e_attachment_set_file (EAttachment *attachment,
                        GFile *file)
 {
-	GCancellable *cancellable;
-
 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
 
-	g_object_freeze_notify (G_OBJECT (attachment));
-
 	if (file != NULL) {
 		g_return_if_fail (G_IS_FILE (file));
 		g_object_ref (file);
 	}
 
-	attachment_reset (attachment);
-	attachment->priv->file = file;
-
-	cancellable = attachment->priv->cancellable;
+	if (attachment->priv->file != NULL)
+		g_object_unref (attachment->priv->file);
 
-	if (file != NULL)
-		g_file_query_info_async (
-			file, ATTACHMENT_QUERY,
-			G_FILE_QUERY_INFO_NONE,
-			G_PRIORITY_DEFAULT, cancellable,
-			(GAsyncReadyCallback)
-			attachment_file_info_ready_cb,
-			attachment);
+	attachment->priv->file = file;
 
 	g_object_notify (G_OBJECT (attachment), "file");
-
-	g_object_thaw_notify (G_OBJECT (attachment));
 }
 
 GFileInfo *
@@ -849,6 +803,14 @@
 	return attachment->priv->file_info;
 }
 
+gboolean
+e_attachment_get_loading (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	return attachment->priv->loading;
+}
+
 CamelMimePart *
 e_attachment_get_mime_part (EAttachment *attachment)
 {
@@ -863,35 +825,67 @@
 {
 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
 
-	g_object_freeze_notify (G_OBJECT (attachment));
-
 	if (mime_part != NULL) {
 		g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
 		camel_object_ref (mime_part);
 	}
 
-	attachment_reset (attachment);
-	attachment->priv->mime_part = mime_part;
-
-	if (mime_part != NULL) {
-		GFileInfo *file_info;
+	if (attachment->priv->mime_part != NULL)
+		camel_object_unref (attachment->priv->mime_part);
 
-		file_info = g_file_info_new ();
-		attachment_populate_file_info (attachment, file_info);
-		attachment_set_file_info (attachment, file_info);
-		g_object_unref (file_info);
-	}
+	attachment->priv->mime_part = mime_part;
 
 	g_object_notify (G_OBJECT (attachment), "mime-part");
-
-	g_object_thaw_notify (G_OBJECT (attachment));
 }
 
-camel_cipher_validity_encrypt_t
-e_attachment_get_encrypted (EAttachment *attachment)
+gint
+e_attachment_get_percent (EAttachment *attachment)
 {
-	g_return_val_if_fail (
-		E_IS_ATTACHMENT (attachment),
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), 0);
+
+	return attachment->priv->percent;
+}
+
+GtkTreeRowReference *
+e_attachment_get_reference (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	/* Don't return an invalid tree row reference. */
+	if (!gtk_tree_row_reference_valid (attachment->priv->reference))
+		e_attachment_set_reference (attachment, NULL);
+
+	return attachment->priv->reference;
+}
+
+void
+e_attachment_set_reference (EAttachment *attachment,
+                            GtkTreeRowReference *reference)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	if (reference != NULL)
+		reference = gtk_tree_row_reference_copy (reference);
+
+	gtk_tree_row_reference_free (attachment->priv->reference);
+	attachment->priv->reference = reference;
+
+	g_object_notify (G_OBJECT (attachment), "reference");
+}
+
+gboolean
+e_attachment_get_saving (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	return attachment->priv->saving;
+}
+
+camel_cipher_validity_encrypt_t
+e_attachment_get_encrypted (EAttachment *attachment)
+{
+	g_return_val_if_fail (
+		E_IS_ATTACHMENT (attachment),
 		CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE);
 
 	return attachment->priv->encrypted;
@@ -932,40 +926,6 @@
 }
 
 const gchar *
-e_attachment_get_content_type (EAttachment *attachment)
-{
-	GFileInfo *file_info;
-	const gchar *attribute;
-
-	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
-	file_info = e_attachment_get_file_info (attachment);
-
-	if (file_info == NULL)
-		return NULL;
-
-	return g_file_info_get_attribute_string (file_info, attribute);
-}
-
-const gchar *
-e_attachment_get_display_name (EAttachment *attachment)
-{
-	GFileInfo *file_info;
-	const gchar *attribute;
-
-	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME;
-	file_info = e_attachment_get_file_info (attachment);
-
-	if (file_info == NULL)
-		return NULL;
-
-	return g_file_info_get_attribute_string (file_info, attribute);
-}
-
-const gchar *
 e_attachment_get_description (EAttachment *attachment)
 {
 	GFileInfo *file_info;
@@ -982,32 +942,6 @@
 	return g_file_info_get_attribute_string (file_info, attribute);
 }
 
-GIcon *
-e_attachment_get_icon (EAttachment *attachment)
-{
-	GFileInfo *file_info;
-	const gchar *attribute;
-
-	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_ICON;
-	file_info = e_attachment_get_file_info (attachment);
-
-	if (file_info == NULL)
-		return NULL;
-
-	return (GIcon *)
-		g_file_info_get_attribute_object (file_info, attribute);
-}
-
-gboolean
-e_attachment_get_loading (EAttachment *attachment)
-{
-	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
-
-	return attachment->priv->loading;
-}
-
 const gchar *
 e_attachment_get_thumbnail_path (EAttachment *attachment)
 {
@@ -1026,39 +960,18 @@
 }
 
 gboolean
-e_attachment_get_saving (EAttachment *attachment)
-{
-	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
-
-	return attachment->priv->saving;
-}
-
-guint64
-e_attachment_get_size (EAttachment *attachment)
-{
-	GFileInfo *file_info;
-	const gchar *attribute;
-
-	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), 0);
-
-	attribute = G_FILE_ATTRIBUTE_STANDARD_SIZE;
-	file_info = e_attachment_get_file_info (attachment);
-
-	if (file_info == NULL)
-		return 0;
-
-	return g_file_info_get_attribute_uint64 (file_info, attribute);
-}
-
-gboolean
 e_attachment_is_image (EAttachment *attachment)
 {
+	GFileInfo *file_info;
 	const gchar *content_type;
 
 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
 
-	content_type = e_attachment_get_content_type (attachment);
+	file_info = e_attachment_get_file_info (attachment);
+	if (file_info == NULL)
+		return FALSE;
 
+	content_type = g_file_info_get_content_type (file_info);
 	if (content_type == NULL)
 		return FALSE;
 
@@ -1068,12 +981,16 @@
 gboolean
 e_attachment_is_rfc822 (EAttachment *attachment)
 {
+	GFileInfo *file_info;
 	const gchar *content_type;
 
 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
 
-	content_type = e_attachment_get_content_type (attachment);
+	file_info = e_attachment_get_file_info (attachment);
+	if (file_info == NULL)
+		return FALSE;
 
+	content_type = g_file_info_get_content_type (file_info);
 	if (content_type == NULL)
 		return FALSE;
 
@@ -1084,14 +1001,18 @@
 e_attachment_list_apps (EAttachment *attachment)
 {
 	GList *app_info_list;
+	GFileInfo *file_info;
 	const gchar *content_type;
 	const gchar *display_name;
 	gchar *allocated;
 
 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
 
-	content_type = e_attachment_get_content_type (attachment);
-	display_name = e_attachment_get_display_name (attachment);
+	file_info = e_attachment_get_file_info (attachment);
+	g_return_val_if_fail (file_info != NULL, NULL);
+
+	content_type = g_file_info_get_content_type (file_info);
+	display_name = g_file_info_get_display_name (file_info);
 	g_return_val_if_fail (content_type != NULL, NULL);
 
 	app_info_list = g_app_info_get_all_for_type (content_type);
@@ -1113,11 +1034,20 @@
 GList *
 e_attachment_list_emblems (EAttachment *attachment)
 {
+	GCancellable *cancellable;
 	GList *list = NULL;
 	GIcon *icon;
 
 	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
 
+	cancellable = attachment->priv->cancellable;
+
+	if (g_cancellable_is_cancelled (cancellable)) {
+		icon = g_themed_icon_new (EMBLEM_CANCELLED);
+		list = g_list_append (list, g_emblem_new (icon));
+		g_object_unref (icon);
+	}
+
 	if (e_attachment_get_loading (attachment)) {
 		icon = g_themed_icon_new (EMBLEM_LOADING);
 		list = g_list_append (list, g_emblem_new (icon));
@@ -1180,552 +1110,1179 @@
 	return list;
 }
 
-/************************ e_attachment_launch_async() ************************/
+/************************* e_attachment_load_async() *************************/
+
+typedef struct _AttachmentLoadContext AttachmentLoadContext;
+
+struct _AttachmentLoadContext {
+	EAttachment *attachment;
+	GSimpleAsyncResult *simple;
+	GInputStream *input_stream;
+	GOutputStream *output_stream;
+	GFileInfo *file_info;
+	goffset total_num_bytes;
+	gssize bytes_read;
+	gchar buffer[4096];
+};
 
+/* Forward Declaration */
 static void
-attachment_launch_file (EActivity *activity,
-                        GAppInfo *app_info,
-                        GFile *file)
+attachment_load_stream_read_cb (GInputStream *input_stream,
+                                GAsyncResult *result,
+                                AttachmentLoadContext *load_context);
+
+static AttachmentLoadContext *
+attachment_load_context_new (EAttachment *attachment,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
 {
-	GdkAppLaunchContext *launch_context;
-	GList *file_list;
-	GError *error = NULL;
-
-	file_list = g_list_prepend (NULL, file);
-	launch_context = gdk_app_launch_context_new ();
+	AttachmentLoadContext *load_context;
+	GSimpleAsyncResult *simple;
 
-	g_app_info_launch (
-		app_info, file_list,
-		G_APP_LAUNCH_CONTEXT (launch_context), &error);
+	simple = g_simple_async_result_new (
+		G_OBJECT (attachment), callback,
+		user_data, e_attachment_load_async);
 
-	g_list_free (file_list);
-	g_object_unref (launch_context);
+	load_context = g_slice_new0 (AttachmentLoadContext);
+	load_context->attachment = g_object_ref (attachment);
+	load_context->simple = simple;
 
-	if (error != NULL) {
-		e_activity_set_error (activity, error);
-		g_error_free (error);
-	}
+	attachment_set_loading (load_context->attachment, TRUE);
 
-	e_activity_complete (activity);
-	g_object_unref (activity);
+	return load_context;
 }
 
-void
-e_attachment_launch_async (EAttachment *attachment,
-                           EFileActivity *file_activity,
-                           GAppInfo *app_info)
+static void
+attachment_load_context_free (AttachmentLoadContext *load_context)
 {
-	CamelMimePart *mime_part;
-	GFile *file;
+	/* Do not free the GSimpleAsyncResult. */
+	g_object_unref (load_context->attachment);
 
-	g_return_if_fail (E_IS_ATTACHMENT (attachment));
-	g_return_if_fail (E_IS_FILE_ACTIVITY (file_activity));
-	g_return_if_fail (G_IS_APP_INFO (app_info));
+	if (load_context->input_stream != NULL)
+		g_object_unref (load_context->input_stream);
 
-	file = e_attachment_get_file (attachment);
-	mime_part = e_attachment_get_mime_part (attachment);
-	g_return_if_fail (file != NULL || mime_part != NULL);
+	if (load_context->output_stream != NULL)
+		g_object_unref (load_context->output_stream);
 
-	/* If the attachment already references a GFile, we can launch
-	 * the application directly.  Otherwise we have to save the MIME
-	 * part to a temporary file and launch the application from that. */
-	if (G_IS_FILE (file)) {
-		EActivity *activity = g_object_ref (file_activity);
-		attachment_launch_file (activity, app_info, file);
+	if (load_context->file_info != NULL)
+		g_object_unref (load_context->file_info);
 
-	} else if (CAMEL_IS_MIME_PART (mime_part)) {
-		/* XXX Not done yet. */
-	}
+	g_slice_free (AttachmentLoadContext, load_context);
 }
 
-/************************* e_attachment_save_async() *************************/
+static void
+attachment_load_finish (AttachmentLoadContext *load_context)
+{
+	GFileInfo *file_info;
+	EAttachment *attachment;
+	GMemoryOutputStream *output_stream;
+	GSimpleAsyncResult *simple;
+	CamelDataWrapper *wrapper;
+	CamelMimePart *mime_part;
+	CamelStream *stream;
+	const gchar *attribute;
+	const gchar *content_type;
+	const gchar *display_name;
+	const gchar *description;
+	const gchar *disposition;
+	gchar *mime_type;
+	gpointer data;
+	gsize size;
 
-typedef struct _AttachmentSaveContext AttachmentSaveContext;
+	/* Steal the reference. */
+	simple = load_context->simple;
+	load_context->simple = NULL;
+
+	file_info = load_context->file_info;
+	attachment = load_context->attachment;
+	output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
 
-struct _AttachmentSaveContext {
-	EAttachment *attachment;
-	EFileActivity *file_activity;
-	GOutputStream *output_stream;
-};
+	if (e_attachment_is_rfc822 (attachment))
+		wrapper = (CamelDataWrapper *) camel_mime_message_new ();
+	else
+		wrapper = camel_data_wrapper_new ();
 
-static AttachmentSaveContext *
-attachment_save_context_new (EAttachment *attachment,
-                             EFileActivity *file_activity)
-{
-	AttachmentSaveContext *save_context;
+	content_type = g_file_info_get_content_type (file_info);
+	mime_type = g_content_type_get_mime_type (content_type);
 
-	save_context = g_slice_new (AttachmentSaveContext);
-	save_context->attachment = g_object_ref (attachment);
-	save_context->file_activity = g_object_ref (file_activity);
-	save_context->output_stream = NULL;
+	data = g_memory_output_stream_get_data (output_stream);
+	size = g_memory_output_stream_get_data_size (output_stream);
 
-	attachment_set_saving (save_context->attachment, TRUE);
+	stream = camel_stream_mem_new_with_buffer (data, size);
+	camel_data_wrapper_construct_from_stream (wrapper, stream);
+	camel_data_wrapper_set_mime_type (wrapper, mime_type);
+	camel_stream_close (stream);
+	camel_object_unref (stream);
 
-	return save_context;
-}
+	mime_part = camel_mime_part_new ();
+	camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper);
 
-static void
-attachment_save_context_free (AttachmentSaveContext *save_context)
-{
-	attachment_set_saving (save_context->attachment, FALSE);
+	camel_object_unref (wrapper);
+	g_free (mime_type);
 
-	g_object_unref (save_context->attachment);
-	g_object_unref (save_context->file_activity);
+	display_name = g_file_info_get_display_name (file_info);
+	if (display_name != NULL)
+		camel_mime_part_set_filename (mime_part, display_name);
 
-	if (save_context->output_stream != NULL)
-		g_object_unref (save_context->output_stream);
+	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
+	description = g_file_info_get_attribute_string (file_info, attribute);
+	if (description != NULL)
+		camel_mime_part_set_description (mime_part, description);
+
+	disposition = e_attachment_get_disposition (attachment);
+	if (disposition != NULL)
+		camel_mime_part_set_disposition (mime_part, disposition);
 
-	g_slice_free (AttachmentSaveContext, save_context);
+	g_simple_async_result_set_op_res_gpointer (
+		simple, mime_part, (GDestroyNotify) camel_object_unref);
+
+	g_simple_async_result_complete (simple);
+
+	attachment_load_context_free (load_context);
 }
 
 static void
-attachment_save_file_cb (GFile *source,
-                         GAsyncResult *result,
-                         AttachmentSaveContext *save_context)
+attachment_load_write_cb (GOutputStream *output_stream,
+                          GAsyncResult *result,
+                          AttachmentLoadContext *load_context)
 {
-	EActivity *activity;
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GInputStream *input_stream;
+	gssize bytes_written;
 	GError *error = NULL;
 
-	activity = E_ACTIVITY (save_context->file_activity);
+	bytes_written = g_output_stream_write_finish (
+		output_stream, result, &error);
 
-	if (!g_file_copy_finish (source, result, &error)) {
-		e_activity_set_error (activity, error);
+	if (error != NULL) {
+		GSimpleAsyncResult *simple;
+
+		/* Steal the reference. */
+		simple = load_context->simple;
+		load_context->simple = NULL;
+
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
 		g_error_free (error);
+
+		attachment_load_context_free (load_context);
+
+		return;
 	}
 
-	e_activity_complete (activity);
-	attachment_save_context_free (save_context);
+	attachment = load_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	input_stream = load_context->input_stream;
+
+	attachment_progress_cb (
+		g_seekable_tell (G_SEEKABLE (output_stream)),
+		load_context->total_num_bytes, attachment);
+
+	if (bytes_written < load_context->bytes_read) {
+		g_memmove (
+			load_context->buffer,
+			load_context->buffer + bytes_written,
+			load_context->bytes_read - bytes_written);
+		load_context->bytes_read -= bytes_written;
+
+		g_output_stream_write_async (
+			output_stream,
+			load_context->buffer,
+			load_context->bytes_read,
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_load_write_cb,
+			load_context);
+	} else
+		g_input_stream_read_async (
+			input_stream,
+			load_context->buffer,
+			sizeof (load_context->buffer),
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_load_stream_read_cb,
+			load_context);
 }
 
-static gpointer
-attachment_save_part_thread (AttachmentSaveContext *save_context)
+static void
+attachment_load_stream_read_cb (GInputStream *input_stream,
+                                GAsyncResult *result,
+                                AttachmentLoadContext *load_context)
 {
-	GObject *object;
 	EAttachment *attachment;
 	GCancellable *cancellable;
 	GOutputStream *output_stream;
-	EFileActivity *file_activity;
-	CamelDataWrapper *wrapper;
-	CamelMimePart *mime_part;
-	CamelStream *stream;
+	gssize bytes_read;
 	GError *error = NULL;
 
-	attachment = save_context->attachment;
-	file_activity = save_context->file_activity;
-	output_stream = save_context->output_stream;
+	bytes_read = g_input_stream_read_finish (
+		input_stream, result, &error);
 
-	/* Last chance to cancel. */
-	cancellable = e_file_activity_get_cancellable (file_activity);
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error))
-		goto exit;
+	if (error != NULL) {
+		GSimpleAsyncResult *simple;
 
-	object = g_object_ref (output_stream);
-	stream = camel_stream_vfs_new_with_stream (object);
-	mime_part = e_attachment_get_mime_part (attachment);
-	wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+		/* Steal the reference. */
+		simple = load_context->simple;
+		load_context->simple = NULL;
 
-	if (camel_data_wrapper_decode_to_stream (wrapper, stream) < 0)
-		g_set_error (
-			&error, G_IO_ERROR,
-			g_io_error_from_errno (errno),
-			g_strerror (errno));
-
-	else if (camel_stream_flush (stream) < 0)
-		g_set_error (
-			&error, G_IO_ERROR,
-			g_io_error_from_errno (errno),
-			g_strerror (errno));
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_error_free (error);
 
-	camel_object_unref (stream);
+		attachment_load_context_free (load_context);
 
-exit:
-	if (error != NULL) {
-		e_activity_set_error (E_ACTIVITY (file_activity), error);
-		g_error_free (error);
+		return;
 	}
 
-	e_activity_complete_in_idle (E_ACTIVITY (file_activity));
-	attachment_save_context_free (save_context);
+	if (bytes_read == 0) {
+		attachment_load_finish (load_context);
+		return;
+	}
+
+	attachment = load_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	output_stream = load_context->output_stream;
+	load_context->bytes_read = bytes_read;
 
-	return NULL;
+	g_output_stream_write_async (
+		output_stream,
+		load_context->buffer,
+		load_context->bytes_read,
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_load_write_cb,
+		load_context);
 }
 
 static void
-attachment_save_part_cb (GFile *destination,
-                         GAsyncResult *result,
-                         AttachmentSaveContext *save_context)
+attachment_load_file_read_cb (GFile *file,
+                              GAsyncResult *result,
+                              AttachmentLoadContext *load_context)
 {
-	GFileOutputStream *output_stream;
-	EActivity *activity;
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GFileInputStream *input_stream;
+	GOutputStream *output_stream;
 	GError *error = NULL;
 
-	activity = E_ACTIVITY (save_context->file_activity);
-	output_stream = g_file_replace_finish (destination, result, &error);
-	save_context->output_stream = G_OUTPUT_STREAM (output_stream);
-
-	if (output_stream != NULL)
-		g_thread_create (
-			(GThreadFunc) attachment_save_part_thread,
-			save_context, FALSE, &error);
+	input_stream = g_file_read_finish (file, result, &error);
+	load_context->input_stream = G_INPUT_STREAM (input_stream);
 
 	if (error != NULL) {
-		e_activity_set_error (activity, error);
-		e_activity_complete (activity);
+		GSimpleAsyncResult *simple;
+
+		/* Steal the reference. */
+		simple = load_context->simple;
+		load_context->simple = NULL;
+
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
 		g_error_free (error);
 
-		attachment_save_context_free (save_context);
+		attachment_load_context_free (load_context);
+
+		return;
 	}
-}
 
-void
-e_attachment_save_async (EAttachment *attachment,
-                         EFileActivity *file_activity,
-                         GFile *destination)
-{
-	AttachmentSaveContext *save_context;
-	GFileProgressCallback progress_callback;
+	/* Load the contents into a GMemoryOutputStream. */
+	output_stream = g_memory_output_stream_new (
+		NULL, 0, g_realloc, g_free);
+
+	attachment = load_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	load_context->output_stream = output_stream;
+
+	g_input_stream_read_async (
+		load_context->input_stream,
+		load_context->buffer,
+		sizeof (load_context->buffer),
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_load_stream_read_cb,
+		load_context);
+}
+
+static void
+attachment_load_query_info_cb (GFile *file,
+                               GAsyncResult *result,
+                               AttachmentLoadContext *load_context)
+{
+	EAttachment *attachment;
 	GCancellable *cancellable;
-	CamelMimePart *mime_part;
-	GFile *source;
+	GFileInfo *file_info;
+	GError *error = NULL;
 
-	g_return_if_fail (E_IS_ATTACHMENT (attachment));
-	g_return_if_fail (E_IS_FILE_ACTIVITY (file_activity));
-	g_return_if_fail (G_IS_FILE (destination));
+	attachment = load_context->attachment;
+	cancellable = attachment->priv->cancellable;
 
-	/* The attachment content is either a GFile (on disk) or a
-	 * CamelMimePart (in memory).  Each is saved differently. */
+	file_info = g_file_query_info_finish (file, result, &error);
+	attachment_set_file_info (attachment, file_info);
+	load_context->file_info = file_info;
 
-	source = e_attachment_get_file (attachment);
+	if (error != NULL) {
+		GSimpleAsyncResult *simple;
+
+		/* Steal the reference. */
+		simple = load_context->simple;
+		load_context->simple = NULL;
+
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_error_free (error);
+
+		attachment_load_context_free (load_context);
+
+		return;
+	}
+
+	load_context->total_num_bytes = g_file_info_get_size (file_info);
+
+	g_file_read_async (
+		file, G_PRIORITY_DEFAULT,
+		cancellable, (GAsyncReadyCallback)
+		attachment_load_file_read_cb, load_context);
+}
+
+static void
+attachment_load_from_mime_part (AttachmentLoadContext *load_context)
+{
+	GFileInfo *file_info;
+	EAttachment *attachment;
+	GSimpleAsyncResult *simple;
+	CamelContentType *content_type;
+	CamelMimePart *mime_part;
+	const gchar *attribute;
+	const gchar *string;
+	gchar *allocated;
+	goffset size;
+
+	attachment = load_context->attachment;
 	mime_part = e_attachment_get_mime_part (attachment);
-	g_return_if_fail (source != NULL || mime_part != NULL);
 
-	save_context = attachment_save_context_new (attachment, file_activity);
-	cancellable = e_file_activity_get_cancellable (file_activity);
-	progress_callback = e_file_activity_progress;
+	file_info = g_file_info_new ();
+	load_context->file_info = file_info;
 
-	/* GFile is the easier, but probably less common case.  The
-	 * attachment already references an on-disk file, so we can
-	 * just use GIO to copy it asynchronously.
-	 *
-	 * We use G_FILE_COPY_OVERWRITE because the user should have
-	 * already confirmed the overwrite through the save dialog. */
-	if (G_IS_FILE (source))
-		g_file_copy_async (
-			source, destination,
-			G_FILE_COPY_OVERWRITE,
-			G_PRIORITY_DEFAULT, cancellable,
-			progress_callback, file_activity,
-			(GAsyncReadyCallback) attachment_save_file_cb,
-			save_context);
+	content_type = camel_mime_part_get_content_type (mime_part);
+	allocated = camel_content_type_simple (content_type);
+	if (allocated != NULL) {
+		GIcon *icon;
+		gchar *cp;
 
-	/* CamelMimePart can only be decoded to a file synchronously, so
-	 * we do this in two stages.  Stage one asynchronously opens the
-	 * destination file for writing.  Stage two spawns a thread that
-	 * decodes the MIME part to the destination file.  This stage is
-	 * not cancellable, unfortunately. */
-	else if (CAMEL_IS_MIME_PART (mime_part)) {
-		g_object_set_data_full (
-			G_OBJECT (file_activity),
-			"attachment", g_object_ref (attachment),
-			(GDestroyNotify) g_object_unref);
-		g_file_replace_async (
-			destination, NULL, FALSE,
-			G_FILE_CREATE_REPLACE_DESTINATION,
-			G_PRIORITY_DEFAULT, cancellable,
-			(GAsyncReadyCallback) attachment_save_part_cb,
-			save_context);
+		/* GIO expects lowercase MIME types. */
+		for (cp = allocated; *cp != '\0'; cp++)
+			*cp = g_ascii_tolower (*cp);
+
+		/* Swap the MIME type for a content type. */
+		cp = g_content_type_from_mime_type (allocated);
+		g_free (allocated);
+		allocated = cp;
+
+		/* Use the MIME part's filename if we have to. */
+		if (g_content_type_is_unknown (allocated)) {
+			string = camel_mime_part_get_filename (mime_part);
+			if (string != NULL) {
+				g_free (allocated);
+				allocated = g_content_type_guess (
+					string, NULL, 0, NULL);
+			}
+		}
+
+		g_file_info_set_content_type (file_info, allocated);
+
+		icon = g_content_type_get_icon (allocated);
+		if (icon != NULL) {
+			g_file_info_set_icon (file_info, icon);
+			g_object_unref (icon);
+		}
 	}
+	g_free (allocated);
+
+	string = camel_mime_part_get_filename (mime_part);
+	if (string != NULL)
+		g_file_info_set_display_name (file_info, string);
+
+	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
+	string = camel_mime_part_get_description (mime_part);
+	if (string != NULL)
+		g_file_info_set_attribute_string (
+			file_info, attribute, string);
+
+	size = (goffset) camel_mime_part_get_content_size (mime_part);
+	g_file_info_set_size (file_info, size);
+
+	string = camel_mime_part_get_disposition (mime_part);
+	e_attachment_set_disposition (attachment, string);
+
+	attachment_set_file_info (attachment, file_info);
+
+	/* Steal the reference. */
+	simple = load_context->simple;
+	load_context->simple = NULL;
+
+	camel_object_ref (mime_part);
+	g_simple_async_result_set_op_res_gpointer (
+		simple, mime_part,
+		(GDestroyNotify) camel_object_unref);
+	g_simple_async_result_complete_in_idle (simple);
+
+	attachment_load_context_free (load_context);
 }
 
-#if 0
-typedef struct {
-	gint io_priority;
+void
+e_attachment_load_async (EAttachment *attachment,
+                         GAsyncReadyCallback callback,
+                         gpointer user_data)
+{
+	AttachmentLoadContext *load_context;
 	GCancellable *cancellable;
+	CamelMimePart *mime_part;
+	GFile *file;
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (callback != NULL);
+
+	g_return_if_fail (!e_attachment_get_loading (attachment));
+	g_return_if_fail (!e_attachment_get_saving (attachment));
+
+	file = e_attachment_get_file (attachment);
+	mime_part = e_attachment_get_mime_part (attachment);
+	g_return_if_fail (file != NULL || mime_part != NULL);
+
+	load_context = attachment_load_context_new (
+		attachment, callback, user_data);
+
+	cancellable = attachment->priv->cancellable;
+	g_cancellable_reset (cancellable);
+
+	/* Handle the trivial case first. */
+	if (mime_part != NULL)
+		attachment_load_from_mime_part (load_context);
+
+	else if (file != NULL)
+		g_file_query_info_async (
+			file, ATTACHMENT_QUERY,
+			G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,
+			cancellable, (GAsyncReadyCallback)
+			attachment_load_query_info_cb, load_context);
+}
+
+gboolean
+e_attachment_load_finish (EAttachment *attachment,
+                          GAsyncResult *result,
+                          GError **error)
+{
 	GSimpleAsyncResult *simple;
+	CamelMimePart *mime_part;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (result,
+		G_OBJECT (attachment), e_attachment_load_async), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	mime_part = g_simple_async_result_get_op_res_gpointer (simple);
+	if (mime_part != NULL)
+		e_attachment_set_mime_part (attachment, mime_part);
+	g_simple_async_result_propagate_error (simple, error);
+	g_object_unref (simple);
+
+	attachment_set_loading (attachment, FALSE);
+
+	return (mime_part != NULL);
+}
+
+void
+e_attachment_load_handle_error (EAttachment *attachment,
+                                GAsyncResult *result,
+                                GtkWindow *parent)
+{
+	GtkWidget *dialog;
 	GFileInfo *file_info;
-} BuildMimePartData;
+	const gchar *display_name;
+	const gchar *primary_text;
+	GError *error = NULL;
 
-static BuildMimePartData *
-attachment_build_mime_part_data_new (EAttachment *attachment,
-                                     gint io_priority,
-                                     GCancellable *cancellable,
-                                     GAsyncReadyCallback callback,
-                                     gpointer user_data,
-                                     gpointer source_tag)
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (G_IS_ASYNC_RESULT (result));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
+
+	if (e_attachment_load_finish (attachment, result, &error))
+		return;
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info != NULL)
+		display_name = g_file_info_get_display_name (file_info);
+	else
+		display_name = NULL;
+
+	if (display_name != NULL)
+		primary_text = g_strdup_printf (
+			_("Could not load '%s'"), display_name);
+	else
+		primary_text = g_strdup_printf (
+			_("Could not load the attachment"));
+
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>", primary_text);
+
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
+}
+
+/************************* e_attachment_open_async() *************************/
+
+typedef struct _AttachmentOpenContext AttachmentOpenContext;
+
+struct _AttachmentOpenContext {
+	EAttachment *attachment;
+	GSimpleAsyncResult *simple;
+	GAppInfo *app_info;
+	GFile *file;
+};
+
+static AttachmentOpenContext *
+attachment_open_context_new (EAttachment *attachment,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
 {
-	BuildMimePartData *data;
+	AttachmentOpenContext *open_context;
 	GSimpleAsyncResult *simple;
 
 	simple = g_simple_async_result_new (
-		G_OBJECT (attachment), callback, user_data, source_tag);
+		G_OBJECT (attachment), callback,
+		user_data, e_attachment_open_async);
 
-	if (G_IS_CANCELLABLE (cancellable))
-		g_object_ref (cancellable);
+	open_context = g_slice_new0 (AttachmentOpenContext);
+	open_context->attachment = g_object_ref (attachment);
+	open_context->simple = simple;
 
-	data = g_slice_new0 (BuildMimePartData);
-	data->io_priority = io_priority;
-	data->cancellable = cancellable;
-	data->simple = simple;
-	return data;
+	return open_context;
 }
 
 static void
-attachment_build_mime_part_data_free (BuildMimePartData *data)
+attachment_open_context_free (AttachmentOpenContext *open_context)
 {
-	if (data->attachment != NULL)
-		g_object_unref (data->attachment);
+	/* Do not free the GSimpleAsyncResult. */
+	g_object_unref (open_context->attachment);
 
-	if (data->cancellable != NULL)
-		g_object_unref (data->cancellable);
+	if (open_context->app_info != NULL)
+		g_object_unref (open_context->app_info);
 
-	if (data->simple != NULL)
-		g_object_unref (data->simple);
+	if (open_context->file != NULL)
+		g_object_unref (open_context->file);
 
-	if (data->file_info != NULL)
-		g_object_unref (data->file_info);
+	g_slice_free (AttachmentOpenContext, open_context);
+}
 
-	g_slice_free (BuildMimePartData, data);
+static void
+attachment_open_file (AttachmentOpenContext *open_context)
+{
+	GdkAppLaunchContext *context;
+	GSimpleAsyncResult *simple;
+	GList *file_list;
+	gboolean success;
+	GError *error = NULL;
+
+	/* Steal the reference. */
+	simple = open_context->simple;
+	open_context->simple = NULL;
+
+	if (open_context->app_info == NULL)
+		open_context->app_info = g_file_query_default_handler (
+			open_context->file, NULL, &error);
+
+	if (open_context->app_info == NULL)
+		goto exit;
+
+	context = gdk_app_launch_context_new ();
+	file_list = g_list_prepend (NULL, open_context->file);
+
+	success = g_app_info_launch (
+		open_context->app_info, file_list,
+		G_APP_LAUNCH_CONTEXT (context), &error);
+
+	g_simple_async_result_set_op_res_gboolean (simple, success);
+
+	g_list_free (file_list);
+	g_object_unref (context);
+
+exit:
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (simple, error);
+		g_error_free (error);
+	}
+
+	g_simple_async_result_complete (simple);
+	attachment_open_context_free (open_context);
 }
 
 static void
-attachment_build_mime_part_splice_cb (GObject *source,
-                                      GAsyncResult *result,
-                                      gpointer user_data)
+attachment_open_save_finished_cb (EAttachment *attachment,
+                                  GAsyncResult *result,
+                                  AttachmentOpenContext *open_context)
 {
-	GSimpleAsyncResult *final_result;
-	GCancellable *cancellable;
-	EAttachment *attachment;
-	CamelDataWrapper *wrapper;
+	GError *error = NULL;
+
+	if (e_attachment_save_finish (attachment, result, &error))
+		attachment_open_file (open_context);
+	else {
+		GSimpleAsyncResult *simple;
+
+		/* Steal the reference. */
+		simple = open_context->simple;
+		open_context->simple = NULL;
+
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_error_free (error);
+
+		attachment_open_context_free (open_context);
+	}
+}
+
+static void
+attachment_open_save_temporary (AttachmentOpenContext *open_context)
+{
+	gchar *path;
+	gint fd;
+	GError *error = NULL;
+
+	fd = e_file_open_tmp (&path, &error);
+	if (error != NULL) {
+		GSimpleAsyncResult *simple;
+
+		/* Steal the reference. */
+		simple = open_context->simple;
+		open_context->simple = NULL;
+
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_error_free (error);
+
+		attachment_open_context_free (open_context);
+		return;
+	}
+
+	close (fd);
+
+	open_context->file = g_file_new_for_path (path);
+
+	e_attachment_save_async (
+		open_context->attachment, open_context->file,
+		(GAsyncReadyCallback) attachment_open_save_finished_cb,
+		open_context);
+}
+
+void
+e_attachment_open_async (EAttachment *attachment,
+                         GAppInfo *app_info,
+                         GAsyncReadyCallback callback,
+                         gpointer user_data)
+{
+	AttachmentOpenContext *open_context;
 	CamelMimePart *mime_part;
-	CamelStream *stream;
-	const gchar *content_type;
-	gchar *mime_type;
-	gssize length;
-	gpointer data;
+	GFile *file;
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (callback != NULL);
+
+	g_return_if_fail (!e_attachment_get_loading (attachment));
+	g_return_if_fail (!e_attachment_get_saving (attachment));
+
+	file = e_attachment_get_file (attachment);
+	mime_part = e_attachment_get_mime_part (attachment);
+	g_return_if_fail (file != NULL || mime_part != NULL);
+
+	open_context = attachment_open_context_new (
+		attachment, callback, user_data);
+
+	if (G_IS_APP_INFO (app_info))
+		open_context->app_info = g_object_ref (app_info);
+
+	/* If the attachment already references a GFile, we can launch
+	 * the application directly.  Otherwise we have to save the MIME
+	 * part to a temporary file and launch the application from that. */
+	if (file != NULL) {
+		open_context->file = g_object_ref (file);
+		attachment_open_file (open_context);
+
+	} else if (mime_part != NULL)
+		attachment_open_save_temporary (open_context);
+}
+
+gboolean
+e_attachment_open_finish (EAttachment *attachment,
+                          GAsyncResult *result,
+                          GError **error)
+{
+	GSimpleAsyncResult *simple;
+	gboolean success;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (result,
+		G_OBJECT (attachment), e_attachment_open_async), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	success = g_simple_async_result_get_op_res_gboolean (simple);
+	g_simple_async_result_propagate_error (simple, error);
+	g_object_unref (simple);
+
+	return success;
+}
+
+void
+e_attachment_open_handle_error (EAttachment *attachment,
+                                GAsyncResult *result,
+                                GtkWindow *parent)
+{
+	GtkWidget *dialog;
+	GFileInfo *file_info;
+	const gchar *display_name;
+	const gchar *primary_text;
 	GError *error = NULL;
 
-	final_result = G_SIMPLE_ASYNC_RESULT (user_data);
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (G_IS_ASYNC_RESULT (result));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
 
-	cancellable = g_cancellable_get_current ();
-	g_cancellable_pop_current (cancellable);
-	g_object_unref (cancellable);
-
-	length = g_output_stream_splice_finish (
-		G_OUTPUT_STREAM (source), result, &error);
-	if (error != NULL)
-		goto fail;
-
-	data = g_memory_output_stream_get_data (
-		G_MEMORY_OUTPUT_STREAM (source));
-
-	attachment = E_ATTACHMENT (
-		g_async_result_get_source_object (
-		G_ASYNC_RESULT (final_result)));
+	if (e_attachment_open_finish (attachment, result, &error))
+		return;
 
-	if (e_attachment_is_rfc822 (attachment))
-		wrapper = (CamelDataWrapper *) camel_mime_message_new ();
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info != NULL)
+		display_name = g_file_info_get_display_name (file_info);
 	else
-		wrapper = camel_data_wrapper_new ();
+		display_name = NULL;
 
-	content_type = e_attachment_get_content_type (attachment);
-	mime_type = g_content_type_get_mime_type (content_type);
+	if (display_name != NULL)
+		primary_text = g_strdup_printf (
+			_("Could not open '%s'"), display_name);
+	else
+		primary_text = g_strdup_printf (
+			_("Could not open the attachment"));
 
-	stream = camel_stream_mem_new_with_buffer (data, length);
-	camel_data_wrapper_construct_from_stream (wrapper, stream);
-	camel_data_wrapper_set_mime_type (wrapper, mime_type);
-	camel_object_unref (stream);
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>", primary_text);
 
-	mime_part = camel_mime_part_new ();
-	camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper);
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
 
-	g_simple_async_result_set_op_res_gpointer (
-		final_result, mime_part, camel_object_unref);
+	gtk_dialog_run (GTK_DIALOG (dialog));
 
-	g_simple_async_result_complete (final_result);
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
+}
 
-	camel_object_unref (wrapper);
-	g_free (mime_type);
+/************************* e_attachment_save_async() *************************/
 
-	return;
+typedef struct _AttachmentSaveContext AttachmentSaveContext;
 
-fail:
-	g_simple_async_result_set_from_error (final_result, error);
-	g_simple_async_result_complete (final_result);
-	g_error_free (error);
+struct _AttachmentSaveContext {
+	EAttachment *attachment;
+	GSimpleAsyncResult *simple;
+	GInputStream *input_stream;
+	GOutputStream *output_stream;
+	goffset total_num_bytes;
+	gssize bytes_read;
+	gchar buffer[4096];
+};
+
+/* Forward Declaration */
+static void
+attachment_save_read_cb (GInputStream *input_stream,
+                         GAsyncResult *result,
+                         AttachmentSaveContext *save_context);
+
+static AttachmentSaveContext *
+attachment_save_context_new (EAttachment *attachment,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+	AttachmentSaveContext *save_context;
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (attachment), callback,
+		user_data, e_attachment_save_async);
+
+	save_context = g_slice_new0 (AttachmentSaveContext);
+	save_context->attachment = g_object_ref (attachment);
+	save_context->simple = simple;
+
+	attachment_set_saving (save_context->attachment, TRUE);
+
+	return save_context;
 }
 
 static void
-attachment_build_mime_part_read_cb (GObject *source,
-                                    GAsyncResult *result,
-                                    BuildMimePartData *data)
+attachment_save_context_free (AttachmentSaveContext *save_context)
 {
-	GFileInputStream *input_stream;
-	GOutputStream *output_stream;
+	/* Do not free the GSimpleAsyncResult. */
+	g_object_unref (save_context->attachment);
+
+	if (save_context->input_stream != NULL)
+		g_object_unref (save_context->input_stream);
+
+	if (save_context->output_stream != NULL)
+		g_object_unref (save_context->output_stream);
+
+	g_slice_free (AttachmentSaveContext, save_context);
+}
+
+static void
+attachment_save_file_cb (GFile *source,
+                         GAsyncResult *result,
+                         AttachmentSaveContext *save_context)
+{
+	GSimpleAsyncResult *simple;
+	gboolean success;
+	GError *error = NULL;
+
+	/* Steal the reference. */
+	simple = save_context->simple;
+	save_context->simple = NULL;
+
+	success = g_file_copy_finish (source, result, &error);
+	g_simple_async_result_set_op_res_gboolean (simple, success);
+
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (simple, error);
+		g_error_free (error);
+	}
+
+	g_simple_async_result_complete (simple);
+	attachment_save_context_free (save_context);
+}
+
+static void
+attachment_save_write_cb (GOutputStream *output_stream,
+                          GAsyncResult *result,
+                          AttachmentSaveContext *save_context)
+{
+	EAttachment *attachment;
 	GCancellable *cancellable;
+	GInputStream *input_stream;
+	gssize bytes_written;
 	GError *error = NULL;
 
-	input_stream = g_file_read_finish (G_FILE (source), result, &error);
-	if (error != NULL)
-		goto fail;
+	bytes_written = g_output_stream_write_finish (
+		output_stream, result, &error);
 
-	output_stream = g_memory_output_stream_new (
-		NULL, 0, g_realloc, g_free);
+	if (error != NULL) {
+		GSimpleAsyncResult *simple;
 
-	g_output_stream_splice_async (
-		output_stream, G_INPUT_STREAM (input_stream),
-		G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
-		G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
-		G_PRIORITY_DEFAULT, cancellable,
-		attachment_build_mime_part_splice_cb, result);
+		/* Steal the reference. */
+		simple = save_context->simple;
+		save_context->simple = NULL;
 
-	g_cancellable_push_current (cancellable);
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_error_free (error);
 
-	g_object_unref (input_stream);
-	g_object_unref (output_stream);
+		attachment_save_context_free (save_context);
 
-	return;
+		return;
+	}
 
-fail:
-	g_simple_async_result_set_from_error (final_result, error);
-	g_simple_async_result_complete (final_result);
-	g_error_free (error);
+	attachment = save_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	input_stream = save_context->input_stream;
+
+	if (bytes_written < save_context->bytes_read) {
+		g_memmove (
+			save_context->buffer,
+			save_context->buffer + bytes_written,
+			save_context->bytes_read - bytes_written);
+		save_context->bytes_read -= bytes_written;
+
+		g_output_stream_write_async (
+			output_stream,
+			save_context->buffer,
+			save_context->bytes_read,
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_save_write_cb,
+			save_context);
+	} else
+		g_input_stream_read_async (
+			input_stream,
+			save_context->buffer,
+			sizeof (save_context->buffer),
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_save_read_cb,
+			save_context);
 }
 
-static gboolean
-attachment_build_mime_part_idle_cb (BuildMimePartData *data)
+static void
+attachment_save_read_cb (GInputStream *input_stream,
+                         GAsyncResult *result,
+                         AttachmentSaveContext *save_context)
 {
-	GObject *source;
-	GAsyncResult *result;
-	GFileInfo *file_info;
-	GFile *file;
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GOutputStream *output_stream;
+	gssize bytes_read;
 	GError *error = NULL;
 
-	if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
-		goto cancelled;
+	bytes_read = g_input_stream_read_finish (
+		input_stream, result, &error);
 
-	result = G_ASYNC_RESULT (data->simple);
-	source = g_async_result_get_source_object (result);
-	file_info = e_attachment_get_file_info (E_ATTACHMENT (source));
-
-	/* Poll again on the next idle. */
-	if (!G_IS_FILE_INFO (file_info))
-		return TRUE;
-
-	/* We have a GFileInfo, so on to step 2. */
-
-	data->file_info = g_file_info_dup (file_info);
-	file = e_attachment_get_file (E_ATTACHMENT (source));
-
-	/* Because Camel's stream API is synchronous and not
-	 * cancellable, we have to asynchronously read the file
-	 * into memory and then encode it to a MIME part.  That
-	 * means double buffering the file contents in memory,
-	 * unfortunately. */
-	g_file_read_async (
-		file, data->io_priority, data->cancellable,
-		attachment_build_mime_part_read_cb, data);
+	if (error != NULL) {
+		GSimpleAsyncResult *simple;
 
-	return FALSE;
+		/* Steal the reference. */
+		simple = save_context->simple;
+		save_context->simple = NULL;
 
-cancelled:
-	g_simple_async_result_set_op_res_gboolean (data->simple, FALSE);
-	g_simple_async_result_set_from_error (data->simple, error);
-	g_simple_async_result_complete (data->simple);
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_error_free (error);
 
-	build_mime_part_data_free (data);
-	g_error_free (error);
+		attachment_save_context_free (save_context);
 
-	return FALSE;
+		return;
+	}
+
+	if (bytes_read == 0) {
+		GSimpleAsyncResult *simple;
+
+		/* Steal the reference. */
+		simple = save_context->simple;
+		save_context->simple = NULL;
+
+		g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+		g_simple_async_result_complete (simple);
+
+		attachment_save_context_free (save_context);
+
+		return;
+	}
+
+	attachment = save_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	output_stream = save_context->output_stream;
+	save_context->bytes_read = bytes_read;
+
+	attachment_progress_cb (
+		g_seekable_tell (G_SEEKABLE (input_stream)),
+		save_context->total_num_bytes, attachment);
+
+	g_output_stream_write_async (
+		output_stream,
+		save_context->buffer,
+		save_context->bytes_read,
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_save_write_cb,
+		save_context);
 }
 
-void
-e_attachment_build_mime_part_async (EAttachment *attachment,
-                                    GCancellable *cancellable,
-                                    GAsyncReadyCallback callback,
-                                    gpointer user_data)
+static void
+attachment_save_replace_cb (GFile *destination,
+                            GAsyncResult *result,
+                            AttachmentSaveContext *save_context)
 {
+	GCancellable *cancellable;
+	GInputStream *input_stream;
+	GFileOutputStream *output_stream;
+	CamelDataWrapper *wrapper;
 	CamelMimePart *mime_part;
-	GSimpleAsyncResult *result;
-	GFile *file;
+	CamelStream *stream;
+	EAttachment *attachment;
+	GByteArray *buffer;
+	GError *error = NULL;
 
-	g_return_if_fail (E_IS_ATTACHMENT (attachment));
-	g_return_if_fail (callback != NULL);
+	output_stream = g_file_replace_finish (destination, result, &error);
+	save_context->output_stream = G_OUTPUT_STREAM (output_stream);
 
-	file = e_attachment_get_file (attachment);
-	mime_part = e_attachment_get_mime_part (attachment);
-	g_return_if_fail (file != NULL || mime_part != NULL);
+	if (error != NULL) {
+		GSimpleAsyncResult *simple;
 
-	result = g_simple_async_result_new (
-		G_OBJECT (attachment), callback, user_data,
-		e_attachment_build_mime_part_async);
+		/* Steal the reference. */
+		simple = save_context->simple;
+		save_context->simple = NULL;
+
+		g_simple_async_result_set_from_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_error_free (error);
+
+		attachment_save_context_free (save_context);
 
-	/* First try the easy way out. */
-	if (CAMEL_IS_MIME_PART (mime_part)) {
-		camel_object_ref (mime_part);
-		g_simple_async_result_set_op_res_gpointer (
-			result, mime_part, camel_object_unref);
-		g_simple_async_result_complete_in_idle (result);
 		return;
 	}
 
-	/* XXX g_cancellable_push_current() documentation lies.
-	 *     The function rejects NULL pointers, so create a
-	 *     dummy GCancellable if necessary. */
-	if (cancellable == NULL)
-		cancellable = g_cancellable_new ();
-	else
-		g_object_ref (cancellable);
+	attachment = save_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	mime_part = e_attachment_get_mime_part (attachment);
 
-	/* Because Camel's stream API is synchronous and not
-	 * cancellable, we have to asynchronously read the file
-	 * into memory and then encode it to a MIME part.  That
-	 * means it's double buffered, unfortunately. */
-	g_file_read_async (
-		file, G_PRIORITY_DEFAULT, cancellable,
-		attachment_build_mime_part_read_cb, result);
+	/* Decode the MIME part to an in-memory buffer.  We have to do
+	 * this because CamelStream is synchronous-only, and using threads
+	 * is dangerous because CamelDataWrapper is not reentrant. */
+	buffer = g_byte_array_new ();
+	stream = camel_stream_mem_new_with_byte_array (buffer);
+	wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+	camel_data_wrapper_decode_to_stream (wrapper, stream);
+	camel_object_unref (stream);
 
-	g_cancellable_push_current (cancellable);
+	/* Load the buffer into a GMemoryInputStream. */
+	input_stream = g_memory_input_stream_new_from_data (
+		buffer->data, (gssize) buffer->len,
+		(GDestroyNotify) g_free);
+	save_context->input_stream = input_stream;
+	save_context->total_num_bytes = (goffset) buffer->len;
+	g_byte_array_free (buffer, FALSE);
+
+	g_input_stream_read_async (
+		input_stream,
+		save_context->buffer,
+		sizeof (save_context->buffer),
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_save_read_cb,
+		save_context);
 }
 
-CamelMimePart *
-e_attachment_build_mime_part_finish (EAttachment *attachment,
-                                     GAsyncResult *result,
-                                     GError **error)
+void
+e_attachment_save_async (EAttachment *attachment,
+                         GFile *destination,
+                         GAsyncReadyCallback callback,
+                         gpointer user_data)
 {
+	AttachmentSaveContext *save_context;
+	GCancellable *cancellable;
 	CamelMimePart *mime_part;
-	GSimpleAsyncResult *simple_result;
-	gboolean async_result_is_valid;
-	gpointer source_tag;
+	GFile *source;
 
-	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
-	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (G_IS_FILE (destination));
+	g_return_if_fail (callback != NULL);
 
-	source_tag = e_attachment_build_mime_part_async;
-	async_result_is_valid = g_simple_async_result_is_valid (
-		result, G_OBJECT (attachment), source_tag);
-	g_return_val_if_fail (async_result_is_valid, NULL);
-
-	simple_result = G_SIMPLE_ASYNC_RESULT (result);
-	g_simple_async_result_propagate_error (simple_result, error);
-	mime_part = g_simple_async_result_get_op_res_gpointer (simple_result);
-	attachment_file_info_to_mime_part (attachment, mime_part);
+	g_return_if_fail (!e_attachment_get_loading (attachment));
+	g_return_if_fail (!e_attachment_get_saving (attachment));
 
-	if (CAMEL_IS_MIME_PART (mime_part))
-		camel_object_ref (mime_part);
+	/* The attachment content is either a GFile (on disk) or a
+	 * CamelMimePart (in memory).  Each is saved differently. */
+
+	source = e_attachment_get_file (attachment);
+	mime_part = e_attachment_get_mime_part (attachment);
+	g_return_if_fail (source != NULL || mime_part != NULL);
+
+	save_context = attachment_save_context_new (
+		attachment, callback, user_data);
+
+	cancellable = attachment->priv->cancellable;
+	g_cancellable_reset (cancellable);
+
+	/* GFile is the easier, but probably less common case.  The
+	 * attachment already references an on-disk file, so we can
+	 * just use GIO to copy it asynchronously.
+	 *
+	 * We use G_FILE_COPY_OVERWRITE because the user should have
+	 * already confirmed the overwrite through the save dialog. */
+	if (G_IS_FILE (source))
+		g_file_copy_async (
+			source, destination,
+			G_FILE_COPY_OVERWRITE,
+			G_PRIORITY_DEFAULT, cancellable,
+			(GFileProgressCallback) attachment_progress_cb,
+			attachment,
+			(GAsyncReadyCallback) attachment_save_file_cb,
+			save_context);
+
+	/* CamelMimePart can only be decoded to a file synchronously, so
+	 * we do this in two stages.  Stage one asynchronously opens the
+	 * destination file for writing.  Stage two spawns a thread that
+	 * decodes the MIME part to the destination file.  This stage is
+	 * not cancellable, unfortunately. */
+	else if (CAMEL_IS_MIME_PART (mime_part))
+		g_file_replace_async (
+			destination, NULL, FALSE,
+			G_FILE_CREATE_REPLACE_DESTINATION,
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_save_replace_cb,
+			save_context);
+}
+
+gboolean
+e_attachment_save_finish (EAttachment *attachment,
+                          GAsyncResult *result,
+                          GError **error)
+{
+	GSimpleAsyncResult *simple;
+	gboolean success;
 
-	g_object_unref (result);
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (result,
+		G_OBJECT (attachment), e_attachment_save_async), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	success = g_simple_async_result_get_op_res_gboolean (simple);
+	g_simple_async_result_propagate_error (simple, error);
+	g_object_unref (simple);
 
-	return mime_part;
+	attachment_set_saving (attachment, FALSE);
+
+	return success;
 }
-#endif
 
 void
-_e_attachment_set_reference (EAttachment *attachment,
-                             GtkTreeRowReference *reference)
+e_attachment_save_handle_error (EAttachment *attachment,
+                                GAsyncResult *result,
+                                GtkWindow *parent)
 {
+	GtkWidget *dialog;
+	GFileInfo *file_info;
+	const gchar *display_name;
+	const gchar *primary_text;
+	GError *error = NULL;
+
 	g_return_if_fail (E_IS_ATTACHMENT (attachment));
-	g_return_if_fail (reference != NULL);
+	g_return_if_fail (G_IS_ASYNC_RESULT (result));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
 
-	gtk_tree_row_reference_free (attachment->priv->reference);
-	attachment->priv->reference = gtk_tree_row_reference_copy (reference);
+	if (e_attachment_save_finish (attachment, result, &error))
+		return;
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info != NULL)
+		display_name = g_file_info_get_display_name (file_info);
+	else
+		display_name = NULL;
+
+	if (display_name != NULL)
+		primary_text = g_strdup_printf (
+			_("Could not save '%s'"), display_name);
+	else
+		primary_text = g_strdup_printf (
+			_("Could not save the attachment"));
+
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>", primary_text);
+
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
 }

Modified: branches/kill-bonobo/widgets/misc/e-attachment.h
==============================================================================
--- branches/kill-bonobo/widgets/misc/e-attachment.h	(original)
+++ branches/kill-bonobo/widgets/misc/e-attachment.h	Thu Mar 26 04:48:21 2009
@@ -22,12 +22,11 @@
 #ifndef E_ATTACHMENT_H
 #define E_ATTACHMENT_H
 
-#include <gio/gio.h>
+#include <gtk/gtk.h>
 #include <camel/camel-cipher-context.h>
 #include <camel/camel-mime-part.h>
 #include <camel/camel-mime-message.h>
 #include <camel/camel-multipart.h>
-#include <widgets/misc/e-file-activity.h>
 
 /* Standard GObject macros */
 #define E_TYPE_ATTACHMENT \
@@ -71,6 +70,7 @@
 void		e_attachment_add_to_multipart	(EAttachment *attachment,
 						 CamelMultipart *multipart,
 						 const gchar *default_charset);
+void		e_attachment_cancel		(EAttachment *attachment);
 const gchar *	e_attachment_get_disposition	(EAttachment *attachment);
 void		e_attachment_set_disposition	(EAttachment *attachment,
 						 const gchar *disposition);
@@ -78,9 +78,16 @@
 void		e_attachment_set_file		(EAttachment *attachment,
 						 GFile *file);
 GFileInfo *	e_attachment_get_file_info	(EAttachment *attachment);
+gboolean	e_attachment_get_loading	(EAttachment *attachment);
 CamelMimePart *	e_attachment_get_mime_part	(EAttachment *attachment);
 void		e_attachment_set_mime_part	(EAttachment *attachment,
 						 CamelMimePart *mime_part);
+gint		e_attachment_get_percent	(EAttachment *attachment);
+GtkTreeRowReference *
+		e_attachment_get_reference	(EAttachment *attachment);
+void		e_attachment_set_reference	(EAttachment *attachment,
+						 GtkTreeRowReference *reference);
+gboolean	e_attachment_get_saving		(EAttachment *attachment);
 camel_cipher_validity_encrypt_t
 		e_attachment_get_encrypted	(EAttachment *attachment);
 void		e_attachment_set_encrypted	(EAttachment *attachment,
@@ -89,42 +96,45 @@
 		e_attachment_get_signed		(EAttachment *attachment);
 void		e_attachment_set_signed		(EAttachment *attachment,
 						 camel_cipher_validity_sign_t signed_);
-const gchar *	e_attachment_get_content_type	(EAttachment *attachment);
-const gchar *	e_attachment_get_display_name	(EAttachment *attachment);
 const gchar *	e_attachment_get_description	(EAttachment *attachment);
-GIcon *		e_attachment_get_icon		(EAttachment *attachment);
-gboolean	e_attachment_get_loading	(EAttachment *attachment);
 const gchar *	e_attachment_get_thumbnail_path	(EAttachment *attachment);
-gboolean	e_attachment_get_saving		(EAttachment *attachment);
-guint64		e_attachment_get_size		(EAttachment *attachment);
 gboolean	e_attachment_is_image		(EAttachment *attachment);
 gboolean	e_attachment_is_rfc822		(EAttachment *attachment);
 GList *		e_attachment_list_apps		(EAttachment *attachment);
 GList *		e_attachment_list_emblems	(EAttachment *attachment);
 
 /* Asynchronous Operations */
-void		e_attachment_launch_async	(EAttachment *attachment,
-						 EFileActivity *file_activity,
-						 GAppInfo *app_info);
+void		e_attachment_load_async		(EAttachment *attachment,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_attachment_load_finish	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GError **error);
+void		e_attachment_open_async		(EAttachment *attachment,
+						 GAppInfo *app_info,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_attachment_open_finish	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GError **error);
 void		e_attachment_save_async		(EAttachment *attachment,
-						 EFileActivity *file_activity,
-						 GFile *destination);
-
-#if 0
-void		e_attachment_build_mime_part_async
-						(EAttachment *attachment,
-						 GCancellable *cancellable,
+						 GFile *destination,
 						 GAsyncReadyCallback callback,
 						 gpointer user_data);
-CamelMimePart *	e_attachment_build_mime_part_finish
-						(EAttachment *attachment,
+gboolean	e_attachment_save_finish	(EAttachment *attachment,
 						 GAsyncResult *result,
 						 GError **error);
-#endif
 
-/* For use by EAttachmentStore only. */
-void		_e_attachment_set_reference	(EAttachment *attachment,
-						 GtkTreeRowReference *reference);
+/* Handy GAsyncReadyCallback Functions */
+void		e_attachment_load_handle_error	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GtkWindow *parent);
+void		e_attachment_open_handle_error	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GtkWindow *parent);
+void		e_attachment_save_handle_error	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GtkWindow *parent);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]