From c05c973cff53769ef575bfc5257a2a414117b323 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Thu, 26 Mar 2009 04:48:21 +0000 Subject: Saving progress again on the attachment rewrite. svn path=/branches/kill-bonobo/; revision=37476 --- widgets/misc/e-attachment-dialog.c | 14 +- widgets/misc/e-attachment-icon-view.c | 42 +- widgets/misc/e-attachment-store.c | 303 ++--- widgets/misc/e-attachment-store.h | 4 +- widgets/misc/e-attachment-tree-view.c | 44 + widgets/misc/e-attachment-view.c | 151 ++- widgets/misc/e-attachment-view.h | 3 + widgets/misc/e-attachment.c | 1941 +++++++++++++++++++++------------ widgets/misc/e-attachment.h | 58 +- 9 files changed, 1586 insertions(+), 974 deletions(-) (limited to 'widgets/misc') diff --git a/widgets/misc/e-attachment-dialog.c b/widgets/misc/e-attachment-dialog.c index da3c3a855c..a844c228eb 100644 --- a/widgets/misc/e-attachment-dialog.c +++ b/widgets/misc/e-attachment-dialog.c @@ -58,20 +58,24 @@ attachment_dialog_update (EAttachmentDialog *dialog) 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; diff --git a/widgets/misc/e-attachment-icon-view.c b/widgets/misc/e-attachment-icon-view.c index 6d27429743..f6e42b2fbe 100644 --- a/widgets/misc/e-attachment-icon-view.c +++ b/widgets/misc/e-attachment-icon-view.c @@ -21,6 +21,7 @@ #include "e-attachment-icon-view.h" +#include #include #include "e-attachment.h" @@ -163,6 +164,15 @@ attachment_icon_view_popup_menu (GtkWidget *widget) 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 @@ attachment_icon_view_class_init (EAttachmentIconViewClass *class) { 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 @@ attachment_icon_view_class_init (EAttachmentIconViewClass *class) 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 @@ attachment_icon_view_init (EAttachmentIconView *icon_view) 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 @@ attachment_icon_view_init (EAttachmentIconView *icon_view) 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 diff --git a/widgets/misc/e-attachment-store.c b/widgets/misc/e-attachment-store.c index bd6cb18481..6086ed4d2f 100644 --- a/widgets/misc/e-attachment-store.c +++ b/widgets/misc/e-attachment-store.c @@ -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 @@ enum { 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) @@ -100,183 +91,6 @@ attachment_store_set_background_options (EAttachmentStore *store, g_object_notify (G_OBJECT (store), "background-options"); } -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, @@ -366,7 +180,6 @@ attachment_store_dispose (GObject *object) 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 @@ attachment_store_finalize (GObject *object) 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 @@ attachment_store_row_changed (GtkTreeModel *model, { EAttachmentStorePrivate *priv; EAttachment *attachment; + GFileInfo *file_info; GFile *file; GIcon *icon; GList *list; @@ -429,8 +242,9 @@ attachment_store_row_changed (GtkTreeModel *model, 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 @@ attachment_store_row_changed (GtkTreeModel *model, 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 @@ attachment_store_row_changed (GtkTreeModel *model, 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 @@ static void 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 @@ e_attachment_store_add_attachment (EAttachmentStore *store, 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 @@ e_attachment_store_remove_attachment (EAttachmentStore *store, { GtkTreeRowReference *reference; GHashTable *hash_table; - EActivity *activity; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; @@ -756,21 +567,14 @@ e_attachment_store_remove_attachment (EAttachmentStore *store, 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 @@ e_attachment_store_add_to_multipart (EAttachmentStore *store, 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 @@ e_attachment_store_get_num_attachments (EAttachmentStore *store) 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 @@ e_attachment_store_get_total_size (EAttachmentStore *store) 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 @@ e_attachment_store_run_load_dialog (EAttachmentStore *store, 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 @@ e_attachment_store_run_load_dialog (EAttachmentStore *store, 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 @@ e_attachment_store_run_save_dialog (EAttachmentStore *store, { 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 @@ e_attachment_store_run_save_dialog (EAttachmentStore *store, 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 @@ e_attachment_store_run_save_dialog (EAttachmentStore *store, 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); diff --git a/widgets/misc/e-attachment-store.h b/widgets/misc/e-attachment-store.h index 971868258f..e7f74102be 100644 --- a/widgets/misc/e-attachment-store.h +++ b/widgets/misc/e-attachment-store.h @@ -60,13 +60,13 @@ struct _EAttachmentStoreClass { }; 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 @@ guint e_attachment_store_get_num_attachments (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, diff --git a/widgets/misc/e-attachment-tree-view.c b/widgets/misc/e-attachment-tree-view.c index df01e07040..548ed3aa4f 100644 --- a/widgets/misc/e-attachment-tree-view.c +++ b/widgets/misc/e-attachment-tree-view.c @@ -182,6 +182,16 @@ attachment_tree_view_popup_menu (GtkWidget *widget) 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 @@ attachment_tree_view_class_init (EAttachmentTreeViewClass *class) { 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 @@ attachment_tree_view_class_init (EAttachmentTreeViewClass *class) 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 @@ attachment_tree_view_init (EAttachmentTreeView *tree_view) 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 @@ attachment_tree_view_init (EAttachmentTreeView *tree_view) 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 @@ attachment_tree_view_init (EAttachmentTreeView *tree_view) 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); diff --git a/widgets/misc/e-attachment-view.c b/widgets/misc/e-attachment-view.c index bdb0a0067e..cb735fbfec 100644 --- a/widgets/misc/e-attachment-view.c +++ b/widgets/misc/e-attachment-view.c @@ -67,6 +67,7 @@ static struct { static const gchar *ui = "" " " +" " " " " " " " @@ -99,6 +100,23 @@ action_add_cb (GtkAction *action, e_attachment_store_run_load_dialog (store, parent); } +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 @@ action_open_in_cb (GtkAction *action, 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 @@ action_recent_cb (GtkAction *action, 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 @@ static void 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 @@ drop_message_rfc822 (EAttachmentView *view, const gchar *data; gboolean success = FALSE; gboolean delete = FALSE; + gpointer parent; gint length; priv = e_attachment_view_get_private (view); @@ -301,8 +350,14 @@ drop_message_rfc822 (EAttachmentView *view, 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 @@ drop_netscape_url (EAttachmentView *view, EAttachmentViewPrivate *priv; EAttachment *attachment; const gchar *data; + gpointer parent; gchar *copied_data; gchar **strv; gint length; @@ -339,8 +395,14 @@ drop_netscape_url (EAttachmentView *view, 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 @@ drop_text_uri_list (EAttachmentView *view, GdkDragAction action) { EAttachmentViewPrivate *priv; + gpointer parent; gchar **uris; gint ii; @@ -362,11 +425,17 @@ drop_text_uri_list (EAttachmentView *view, 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 @@ drop_text_generic (EAttachmentView *view, CamelMimePart *mime_part; GdkAtom atom; const gchar *data; + gpointer parent; gchar *content_type; gint length; @@ -402,9 +472,15 @@ drop_text_generic (EAttachmentView *view, 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 @@ e_attachment_view_get_selected_attachments (EAttachmentView *view) 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 @@ e_attachment_view_get_selected_attachments (EAttachmentView *view) 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 @@ e_attachment_view_update_actions (EAttachmentView *view) 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 @@ e_attachment_view_update_actions (EAttachmentView *view) 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 @@ e_attachment_view_update_actions (EAttachmentView *view) 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); diff --git a/widgets/misc/e-attachment-view.h b/widgets/misc/e-attachment-view.h index e6e04a82a3..96a0f2dc16 100644 --- a/widgets/misc/e-attachment-view.h +++ b/widgets/misc/e-attachment-view.h @@ -107,6 +107,9 @@ void e_attachment_view_set_editable (EAttachmentView *view, 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); diff --git a/widgets/misc/e-attachment.c b/widgets/misc/e-attachment.c index 015a706dc4..55b3280e87 100644 --- a/widgets/misc/e-attachment.c +++ b/widgets/misc/e-attachment.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -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 @@ struct _EAttachmentPrivate { 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 @@ enum { 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) { @@ -146,19 +121,35 @@ attachment_get_default_charset (void) return charset; } +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_set_file_info (EAttachment *attachment, 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 @@ static void 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 @@ static void 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 @@ attachment_set_property (GObject *object, 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 @@ attachment_get_property (GObject *object, 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 @@ attachment_dispose (GObject *object) 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 @@ attachment_dispose (GObject *object) 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 @@ attachment_class_init (EAttachmentClass *class) 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_init (EAttachment *attachment) 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 @@ exit: 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 @@ void 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 @@ e_attachment_get_file_info (EAttachment *attachment) 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 @@ e_attachment_set_mime_part (EAttachment *attachment, { 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; @@ -931,40 +925,6 @@ e_attachment_set_signed (EAttachment *attachment, attachment_notify_model (attachment); } -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) { @@ -982,32 +942,6 @@ e_attachment_get_description (EAttachment *attachment) 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) { @@ -1025,40 +959,19 @@ e_attachment_get_thumbnail_path (EAttachment *attachment) return g_file_info_get_attribute_byte_string (file_info, attribute); } -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 @@ e_attachment_is_image (EAttachment *attachment) 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 @@ GList * 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 @@ exit: 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 @@ e_attachment_list_emblems (EAttachment *attachment) 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; -struct _AttachmentSaveContext { - EAttachment *attachment; - EFileActivity *file_activity; - GOutputStream *output_stream; -}; + file_info = load_context->file_info; + attachment = load_context->attachment; + output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream); -static AttachmentSaveContext * -attachment_save_context_new (EAttachment *attachment, - EFileActivity *file_activity) -{ - AttachmentSaveContext *save_context; + if (e_attachment_is_rfc822 (attachment)) + wrapper = (CamelDataWrapper *) camel_mime_message_new (); + else + wrapper = camel_data_wrapper_new (); - 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; + content_type = g_file_info_get_content_type (file_info); + mime_type = g_content_type_get_mime_type (content_type); - attachment_set_saving (save_context->attachment, TRUE); + data = g_memory_output_stream_get_data (output_stream); + size = g_memory_output_stream_get_data_size (output_stream); - return save_context; -} + 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); -static void -attachment_save_context_free (AttachmentSaveContext *save_context) -{ - attachment_set_saving (save_context->attachment, FALSE); + mime_part = camel_mime_part_new (); + camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper); - g_object_unref (save_context->attachment); - g_object_unref (save_context->file_activity); + camel_object_unref (wrapper); + g_free (mime_type); - if (save_context->output_stream != NULL) - g_object_unref (save_context->output_stream); + display_name = g_file_info_get_display_name (file_info); + if (display_name != NULL) + camel_mime_part_set_filename (mime_part, display_name); - g_slice_free (AttachmentSaveContext, save_context); + 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_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 (error != NULL) { + GSimpleAsyncResult *simple; + + /* Steal the reference. */ + simple = load_context->simple; + load_context->simple = NULL; - if (!g_file_copy_finish (source, result, &error)) { - e_activity_set_error (activity, error); + 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)); + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_error_free (error); - else if (camel_stream_flush (stream) < 0) - g_set_error ( - &error, G_IO_ERROR, - g_io_error_from_errno (errno), - g_strerror (errno)); + attachment_load_context_free (load_context); - camel_object_unref (stream); + return; + } -exit: - if (error != NULL) { - e_activity_set_error (E_ACTIVITY (file_activity), error); - g_error_free (error); + if (bytes_read == 0) { + attachment_load_finish (load_context); + return; } - e_activity_complete_in_idle (E_ACTIVITY (file_activity)); - attachment_save_context_free (save_context); + 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; + + 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, + "%s", primary_text); -static BuildMimePartData * -attachment_build_mime_part_data_new (EAttachment *attachment, - gint io_priority, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data, - gpointer source_tag) + 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); + if (e_attachment_open_finish (attachment, result, &error)) + return; - length = g_output_stream_splice_finish ( - G_OUTPUT_STREAM (source), result, &error); - if (error != NULL) - goto fail; + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; - data = g_memory_output_stream_get_data ( - G_MEMORY_OUTPUT_STREAM (source)); + file_info = e_attachment_get_file_info (attachment); - attachment = E_ATTACHMENT ( - g_async_result_get_source_object ( - G_ASYNC_RESULT (final_result))); + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; - if (e_attachment_is_rfc822 (attachment)) - wrapper = (CamelDataWrapper *) camel_mime_message_new (); + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not open '%s'"), display_name); else - wrapper = camel_data_wrapper_new (); + primary_text = g_strdup_printf ( + _("Could not open the attachment")); - content_type = e_attachment_get_content_type (attachment); - mime_type = g_content_type_get_mime_type (content_type); + dialog = gtk_message_dialog_new_with_markup ( + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "%s", primary_text); - 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); + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s", error->message); - mime_part = camel_mime_part_new (); - camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper); + gtk_dialog_run (GTK_DIALOG (dialog)); - g_simple_async_result_set_op_res_gpointer ( - final_result, mime_part, camel_object_unref); + gtk_widget_destroy (dialog); + g_error_free (error); +} - g_simple_async_result_complete (final_result); +/************************* e_attachment_save_async() *************************/ - camel_object_unref (wrapper); - g_free (mime_type); +typedef struct _AttachmentSaveContext AttachmentSaveContext; - return; +struct _AttachmentSaveContext { + EAttachment *attachment; + GSimpleAsyncResult *simple; + GInputStream *input_stream; + GOutputStream *output_stream; + goffset total_num_bytes; + gssize bytes_read; + gchar buffer[4096]; +}; -fail: - g_simple_async_result_set_from_error (final_result, error); - g_simple_async_result_complete (final_result); - g_error_free (error); +/* 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)); + if (error != NULL) { + GSimpleAsyncResult *simple; - /* Poll again on the next idle. */ - if (!G_IS_FILE_INFO (file_info)) - return TRUE; + /* Steal the reference. */ + simple = save_context->simple; + save_context->simple = NULL; - /* We have a GFileInfo, so on to step 2. */ + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_error_free (error); - data->file_info = g_file_info_dup (file_info); - file = e_attachment_get_file (E_ATTACHMENT (source)); + attachment_save_context_free (save_context); - /* 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); + return; + } - return FALSE; + if (bytes_read == 0) { + GSimpleAsyncResult *simple; -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); + /* Steal the reference. */ + simple = save_context->simple; + save_context->simple = NULL; - build_mime_part_data_free (data); - g_error_free (error); + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + g_simple_async_result_complete (simple); - return FALSE; + 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); + g_return_if_fail (!e_attachment_get_loading (attachment)); + g_return_if_fail (!e_attachment_get_saving (attachment)); - 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); + /* The attachment content is either a GFile (on disk) or a + * CamelMimePart (in memory). Each is saved differently. */ - if (CAMEL_IS_MIME_PART (mime_part)) - camel_object_ref (mime_part); + 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); - g_object_unref (result); + cancellable = attachment->priv->cancellable; + g_cancellable_reset (cancellable); - return mime_part; + /* 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_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); + + 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, + "%s", 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); } diff --git a/widgets/misc/e-attachment.h b/widgets/misc/e-attachment.h index d9ef68bf39..1ca5b3121b 100644 --- a/widgets/misc/e-attachment.h +++ b/widgets/misc/e-attachment.h @@ -22,12 +22,11 @@ #ifndef E_ATTACHMENT_H #define E_ATTACHMENT_H -#include +#include #include #include #include #include -#include /* Standard GObject macros */ #define E_TYPE_ATTACHMENT \ @@ -71,6 +70,7 @@ EAttachment * e_attachment_new_for_message (CamelMimeMessage *message); 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 @@ GFile * e_attachment_get_file (EAttachment *attachment); 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 @@ camel_cipher_validity_sign_t 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 -- cgit v1.2.3