diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2009-04-28 03:36:19 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2009-04-28 03:36:19 +0800 |
commit | 4449a34101406bffe508dd40b8b653f7c7d14c7d (patch) | |
tree | e9fb2ce7efd3901cf1090040666f0ce15ad70554 /widgets/misc/e-attachment.c | |
parent | e377ea5e61171e57f9e892652d0fd1f77953eda8 (diff) | |
download | gsoc2013-evolution-4449a34101406bffe508dd40b8b653f7c7d14c7d.tar.gz gsoc2013-evolution-4449a34101406bffe508dd40b8b653f7c7d14c7d.tar.zst gsoc2013-evolution-4449a34101406bffe508dd40b8b653f7c7d14c7d.zip |
Commit the rest of the attachment UI rewrite
Oops, last commit only included the -new- files.
This also removes EExpander, which is no longer used.
Diffstat (limited to 'widgets/misc/e-attachment.c')
-rw-r--r-- | widgets/misc/e-attachment.c | 3021 |
1 files changed, 2406 insertions, 615 deletions
diff --git a/widgets/misc/e-attachment.c b/widgets/misc/e-attachment.c index 4f5e9ace34..f472a26ff3 100644 --- a/widgets/misc/e-attachment.c +++ b/widgets/misc/e-attachment.c @@ -1,4 +1,5 @@ /* + * e-attachment.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -14,162 +15,845 @@ * License along with the program; if not, see <http://www.gnu.org/licenses/> * * - * Authors: - * Ettore Perazzoli <ettore@ximian.com> - * Jeffrey Stedfast <fejj@ximian.com> - * Srinivasa Ragavan <sragavan@novell.com> - * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef G_OS_WIN32 -/* Include <windows.h> early (as the gio stuff below will - * include it anyway, sigh) to workaround the DATADIR problem. - * <windows.h> (and the headers it includes) stomps all over the - * namespace like a baboon on crack, and especially the DATADIR enum - * in objidl.h causes problems. - */ -#undef DATADIR -#define DATADIR crap_DATADIR -#include <windows.h> -#undef DATADIR -#endif +#include "e-attachment.h" -#include <sys/stat.h> -#include <string.h> #include <errno.h> - -#include <camel/camel.h> - +#include <config.h> #include <glib/gi18n.h> -#include <glib/gstdio.h> - -#include <libebook/e-vcard.h> +#include <camel/camel-iconv.h> +#include <camel/camel-data-wrapper.h> +#include <camel/camel-mime-message.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-stream-null.h> +#include <camel/camel-stream-vfs.h> #include "e-util/e-util.h" -#include "e-util/e-error.h" #include "e-util/e-mktemp.h" -#include "e-util/e-util-private.h" - -#include "e-attachment.h" +#include "e-attachment-store.h" + +#define E_ATTACHMENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_ATTACHMENT, EAttachmentPrivate)) + +/* Fallback Icon */ +#define DEFAULT_ICON_NAME "mail-attachment" + +/* Emblems */ +#define EMBLEM_CANCELLED "gtk-cancel" +#define EMBLEM_LOADING "emblem-downloads" +#define EMBLEM_SAVING "document-save" +#define EMBLEM_ENCRYPT_WEAK "security-low" +#define EMBLEM_ENCRYPT_STRONG "security-high" +#define EMBLEM_ENCRYPT_UNKNOWN "security-medium" +#define EMBLEM_SIGN_BAD "stock_signature_bad" +#define EMBLEM_SIGN_GOOD "stock_signature-ok" +#define EMBLEM_SIGN_UNKNOWN "stock_signature" + +/* Attributes needed for EAttachmentStore columns. */ +#define ATTACHMENT_QUERY "standard::*,preview::*,thumbnail::*" + +struct _EAttachmentPrivate { + GFile *file; + GFileInfo *file_info; + GCancellable *cancellable; + CamelMimePart *mime_part; + guint emblem_timeout_id; + gchar *disposition; + gint percent; + + guint can_show : 1; + guint loading : 1; + guint saving : 1; + guint shown : 1; + + camel_cipher_validity_encrypt_t encrypted; + camel_cipher_validity_sign_t signed_; + + /* This is a reference to our row in an EAttachmentStore, + * serving as a means of broadcasting "row-changed" signals. + * If we are removed from the store, we lazily free the + * reference when it is found to be to be invalid. */ + GtkTreeRowReference *reference; +}; enum { - CHANGED, - UPDATE, - LAST_SIGNAL + PROP_0, + PROP_CAN_SHOW, + PROP_DISPOSITION, + PROP_ENCRYPTED, + PROP_FILE, + PROP_FILE_INFO, + PROP_LOADING, + PROP_MIME_PART, + PROP_PERCENT, + PROP_REFERENCE, + PROP_SAVING, + PROP_SHOWN, + PROP_SIGNED }; -static guint signals[LAST_SIGNAL] = { 0 }; +static gpointer parent_class; -static GObjectClass *parent_class = NULL; +static gchar * +attachment_get_default_charset (void) +{ + GConfClient *client; + const gchar *key; + gchar *charset; + + /* XXX This doesn't really belong here. */ + + client = gconf_client_get_default (); + key = "/apps/evolution/mail/composer/charset"; + charset = gconf_client_get_string (client, key, NULL); + if (charset == NULL || *charset == '\0') { + g_free (charset); + key = "/apps/evolution/mail/format/charset"; + charset = gconf_client_get_string (client, key, NULL); + if (charset == NULL || *charset == '\0') { + g_free (charset); + charset = NULL; + } + } + g_object_unref (client); + + if (charset == NULL) + charset = g_strdup (camel_iconv_locale_charset ()); + + if (charset == NULL) + charset = g_strdup ("us-ascii"); + + return charset; +} static void -changed (EAttachment *attachment) +attachment_update_file_info_columns (EAttachment *attachment) { - g_signal_emit (attachment, signals[CHANGED], 0); -} + GtkTreeRowReference *reference; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GFileInfo *file_info; + const gchar *content_type; + const gchar *description; + const gchar *display_name; + gchar *content_desc; + gchar *display_size; + gchar *caption; + goffset size; + + reference = e_attachment_get_reference (attachment); + if (!gtk_tree_row_reference_valid (reference)) + return; + + file_info = e_attachment_get_file_info (attachment); + if (file_info == 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_path_free (path); + + content_type = g_file_info_get_content_type (file_info); + display_name = g_file_info_get_display_name (file_info); + size = g_file_info_get_size (file_info); + content_desc = g_content_type_get_description (content_type); + display_size = g_format_size_for_display (size); -/* GtkObject methods. */ + description = e_attachment_get_description (attachment); + if (description == NULL || *description == '\0') + description = display_name; + + if (size > 0) + caption = g_strdup_printf ( + "%s\n(%s)", description, display_size); + else + caption = g_strdup (description); + + gtk_list_store_set ( + GTK_LIST_STORE (model), &iter, + E_ATTACHMENT_STORE_COLUMN_CAPTION, caption, + E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, content_desc, + E_ATTACHMENT_STORE_COLUMN_DESCRIPTION, description, + E_ATTACHMENT_STORE_COLUMN_SIZE, size, + -1); + + g_free (content_desc); + g_free (display_size); + g_free (caption); +} static void -finalise (GObject *object) +attachment_update_icon_column (EAttachment *attachment) { - EAttachment *attachment = (EAttachment *) object; - GtkWidget *dialog; + GtkTreeRowReference *reference; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GFileInfo *file_info; + GCancellable *cancellable; + GIcon *icon = NULL; + const gchar *emblem_name = NULL; + const gchar *thumbnail_path = NULL; - if (attachment->editor_gui != NULL) { - dialog = glade_xml_get_widget (attachment->editor_gui, "dialog"); - g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_CLOSE); + reference = e_attachment_get_reference (attachment); + if (!gtk_tree_row_reference_valid (reference)) + 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_path_free (path); + + cancellable = attachment->priv->cancellable; + file_info = e_attachment_get_file_info (attachment); + + if (file_info != NULL) { + icon = g_file_info_get_icon (file_info); + thumbnail_path = g_file_info_get_attribute_byte_string ( + file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); } - if (attachment->is_available_local) { - camel_object_unref (attachment->body); - if (attachment->pixbuf_cache != NULL) - g_object_unref (attachment->pixbuf_cache); - } else { - if (attachment->cancellable) { - /* the operation is still running, so cancel it */ - g_cancellable_cancel (attachment->cancellable); - attachment->cancellable = NULL; + /* Prefer the thumbnail if we have one. */ + if (thumbnail_path != NULL && *thumbnail_path != '\0') { + GFile *file; + + file = g_file_new_for_path (thumbnail_path); + icon = g_file_icon_new (file); + g_object_unref (file); + + /* Else use the standard icon for the content type. */ + } else if (icon != NULL) + g_object_ref (icon); + + /* Last ditch fallback. (GFileInfo not yet loaded?) */ + else + icon = g_themed_icon_new (DEFAULT_ICON_NAME); + + /* Pick an emblem, limit one. Choices listed by priority. */ + + if (g_cancellable_is_cancelled (cancellable)) + emblem_name = EMBLEM_CANCELLED; + + else if (e_attachment_get_loading (attachment)) + emblem_name = EMBLEM_LOADING; + + else if (e_attachment_get_saving (attachment)) + emblem_name = EMBLEM_SAVING; + + else if (e_attachment_get_encrypted (attachment)) + switch (e_attachment_get_encrypted (attachment)) { + case CAMEL_CIPHER_VALIDITY_ENCRYPT_WEAK: + emblem_name = EMBLEM_ENCRYPT_WEAK; + break; + + case CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED: + emblem_name = EMBLEM_ENCRYPT_UNKNOWN; + break; + + case CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG: + emblem_name = EMBLEM_ENCRYPT_STRONG; + break; + + default: + g_warn_if_reached (); + break; } - g_free (attachment->description); + + else if (e_attachment_get_signed (attachment)) + switch (e_attachment_get_signed (attachment)) { + case CAMEL_CIPHER_VALIDITY_SIGN_GOOD: + emblem_name = EMBLEM_SIGN_GOOD; + break; + + case CAMEL_CIPHER_VALIDITY_SIGN_BAD: + emblem_name = EMBLEM_SIGN_BAD; + break; + + case CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN: + case CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY: + emblem_name = EMBLEM_SIGN_UNKNOWN; + break; + + default: + g_warn_if_reached (); + break; + } + + if (emblem_name != NULL) { + GIcon *emblemed_icon; + GEmblem *emblem; + + emblemed_icon = g_themed_icon_new (emblem_name); + emblem = g_emblem_new (emblemed_icon); + g_object_unref (emblemed_icon); + + emblemed_icon = g_emblemed_icon_new (icon, emblem); + g_object_unref (emblem); + g_object_unref (icon); + + icon = emblemed_icon; } - g_free (attachment->file_name); - g_free (attachment->store_uri); + gtk_list_store_set ( + GTK_LIST_STORE (model), &iter, + E_ATTACHMENT_STORE_COLUMN_ICON, icon, + -1); - G_OBJECT_CLASS (parent_class)->finalize (object); + g_object_unref (icon); } +static void +attachment_update_progress_columns (EAttachment *attachment) +{ + GtkTreeRowReference *reference; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + gboolean loading; + gboolean saving; + gint percent; + + reference = e_attachment_get_reference (attachment); + if (!gtk_tree_row_reference_valid (reference)) + return; -/* Signals. */ + 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); + + /* Don't show progress bars until we have progress to report. */ + percent = e_attachment_get_percent (attachment); + loading = e_attachment_get_loading (attachment) && (percent > 0); + saving = e_attachment_get_saving (attachment) && (percent > 0); + + gtk_list_store_set ( + GTK_LIST_STORE (model), &iter, + E_ATTACHMENT_STORE_COLUMN_LOADING, loading, + E_ATTACHMENT_STORE_COLUMN_PERCENT, percent, + E_ATTACHMENT_STORE_COLUMN_SAVING, saving, + -1); +} static void -real_changed (EAttachment *attachment) +attachment_set_file_info (EAttachment *attachment, + GFileInfo *file_info) { - g_return_if_fail (E_IS_ATTACHMENT (attachment)); + GtkTreeRowReference *reference; + GIcon *icon; + + reference = e_attachment_get_reference (attachment); + + if (file_info != NULL) + g_object_ref (file_info); + + if (attachment->priv->file_info != NULL) + g_object_unref (attachment->priv->file_info); + + attachment->priv->file_info = file_info; + + /* If the GFileInfo contains a GThemedIcon, append a + * fallback icon name to ensure we display something. */ + icon = g_file_info_get_icon (file_info); + if (G_IS_THEMED_ICON (icon)) + g_themed_icon_append_name ( + G_THEMED_ICON (icon), DEFAULT_ICON_NAME); + + g_object_notify (G_OBJECT (attachment), "file-info"); + + /* Tell the EAttachmentStore its total size changed. */ + if (gtk_tree_row_reference_valid (reference)) { + GtkTreeModel *model; + model = gtk_tree_row_reference_get_model (reference); + g_object_notify (G_OBJECT (model), "total-size"); + } } static void -real_update_attachment (EAttachment *attachment, char *msg) +attachment_set_loading (EAttachment *attachment, + gboolean loading) { - g_return_if_fail (E_IS_ATTACHMENT (attachment)); + 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 (gtk_tree_row_reference_valid (reference)) { + GtkTreeModel *model; + model = gtk_tree_row_reference_get_model (reference); + g_object_notify (G_OBJECT (model), "num-loading"); + } +} + +static void +attachment_set_saving (EAttachment *attachment, + gboolean saving) +{ + attachment->priv->percent = 0; + attachment->priv->saving = saving; + + g_object_freeze_notify (G_OBJECT (attachment)); + g_object_notify (G_OBJECT (attachment), "percent"); + g_object_notify (G_OBJECT (attachment), "saving"); + g_object_thaw_notify (G_OBJECT (attachment)); +} + +static void +attachment_progress_cb (goffset current_num_bytes, + goffset total_num_bytes, + EAttachment *attachment) +{ + attachment->priv->percent = + (current_num_bytes * 100) / total_num_bytes; + + g_object_notify (G_OBJECT (attachment), "percent"); +} + +static gboolean +attachment_cancelled_timeout_cb (EAttachment *attachment) +{ + attachment->priv->emblem_timeout_id = 0; + g_cancellable_reset (attachment->priv->cancellable); + + attachment_update_icon_column (attachment); + + return FALSE; +} + +static void +attachment_cancelled_cb (EAttachment *attachment) +{ + /* 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. */ + + if (attachment->priv->emblem_timeout_id > 0) + g_source_remove (attachment->priv->emblem_timeout_id); + + attachment->priv->emblem_timeout_id = g_timeout_add_seconds ( + 1, (GSourceFunc) attachment_cancelled_timeout_cb, attachment); + + attachment_update_icon_column (attachment); +} + +static void +attachment_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CAN_SHOW: + e_attachment_set_can_show ( + E_ATTACHMENT (object), + g_value_get_boolean (value)); + return; + + case PROP_DISPOSITION: + e_attachment_set_disposition ( + E_ATTACHMENT (object), + g_value_get_string (value)); + return; + + case PROP_ENCRYPTED: + e_attachment_set_encrypted ( + E_ATTACHMENT (object), + g_value_get_int (value)); + return; + + case PROP_FILE: + e_attachment_set_file ( + E_ATTACHMENT (object), + g_value_get_object (value)); + return; + + case PROP_SHOWN: + e_attachment_set_shown ( + E_ATTACHMENT (object), + g_value_get_boolean (value)); + return; + + case PROP_MIME_PART: + e_attachment_set_mime_part ( + E_ATTACHMENT (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), + g_value_get_int (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +attachment_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CAN_SHOW: + g_value_set_boolean ( + value, e_attachment_get_can_show ( + E_ATTACHMENT (object))); + return; + + case PROP_DISPOSITION: + g_value_set_string ( + value, e_attachment_get_disposition ( + E_ATTACHMENT (object))); + return; + + case PROP_ENCRYPTED: + g_value_set_int ( + value, e_attachment_get_encrypted ( + E_ATTACHMENT (object))); + return; + + case PROP_FILE: + g_value_set_object ( + value, e_attachment_get_file ( + E_ATTACHMENT (object))); + return; + + case PROP_FILE_INFO: + g_value_set_object ( + value, e_attachment_get_file_info ( + E_ATTACHMENT (object))); + return; + + case PROP_SHOWN: + g_value_set_boolean ( + value, e_attachment_get_shown ( + E_ATTACHMENT (object))); + return; + + case PROP_LOADING: + g_value_set_boolean ( + value, e_attachment_get_loading ( + E_ATTACHMENT (object))); + return; + + case PROP_MIME_PART: + g_value_set_boxed ( + value, e_attachment_get_mime_part ( + 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 ( + E_ATTACHMENT (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } +static void +attachment_dispose (GObject *object) +{ + EAttachmentPrivate *priv; + + priv = E_ATTACHMENT_GET_PRIVATE (object); + + if (priv->file != NULL) { + g_object_unref (priv->file); + priv->file = NULL; + } + + if (priv->file_info != NULL) { + g_object_unref (priv->file_info); + 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; + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (parent_class)->dispose (object); +} static void -class_init (EAttachmentClass *klass) +attachment_finalize (GObject *object) +{ + EAttachmentPrivate *priv; + + priv = E_ATTACHMENT_GET_PRIVATE (object); + + g_free (priv->disposition); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +attachment_class_init (EAttachmentClass *class) { GObjectClass *object_class; - object_class = (GObjectClass*) klass; - parent_class = g_type_class_ref (G_TYPE_OBJECT); - - object_class->finalize = finalise; - klass->changed = real_changed; - klass->update = real_update_attachment; - - signals[CHANGED] = g_signal_new ("changed", - E_TYPE_ATTACHMENT, - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (EAttachmentClass, changed), - NULL, - NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - signals[UPDATE] = g_signal_new ("update", - E_TYPE_ATTACHMENT, - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (EAttachmentClass, update), - NULL, - NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - -} - -static void -init (EAttachment *attachment) -{ - attachment->editor_gui = NULL; - attachment->body = NULL; - attachment->size = 0; - attachment->pixbuf_cache = NULL; - attachment->index = -1; - attachment->file_name = NULL; - attachment->percentage = -1; - attachment->description = NULL; - attachment->disposition = FALSE; - attachment->sign = CAMEL_CIPHER_VALIDITY_SIGN_NONE; - attachment->encrypt = CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE; - attachment->store_uri = NULL; - attachment->cancellable = NULL; + parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (EAttachmentPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = attachment_set_property; + object_class->get_property = attachment_get_property; + object_class->dispose = attachment_dispose; + object_class->finalize = attachment_finalize; + + g_object_class_install_property ( + object_class, + PROP_CAN_SHOW, + g_param_spec_boolean ( + "can-show", + "Can Show", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + object_class, + PROP_DISPOSITION, + g_param_spec_string ( + "disposition", + "Disposition", + NULL, + "attachment", + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + /* FIXME Define a GEnumClass for this. */ + g_object_class_install_property ( + object_class, + PROP_ENCRYPTED, + g_param_spec_int ( + "encrypted", + "Encrypted", + NULL, + CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE, + CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG, + CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + object_class, + PROP_FILE, + g_param_spec_object ( + "file", + "File", + NULL, + G_TYPE_FILE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + object_class, + PROP_FILE_INFO, + g_param_spec_object ( + "file-info", + "File Info", + NULL, + G_TYPE_FILE_INFO, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_LOADING, + g_param_spec_boolean ( + "loading", + "Loading", + NULL, + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_MIME_PART, + g_param_spec_boxed ( + "mime-part", + "MIME Part", + NULL, + 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)); + + g_object_class_install_property ( + object_class, + PROP_SHOWN, + g_param_spec_boolean ( + "shown", + "Shown", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + /* FIXME Define a GEnumClass for this. */ + g_object_class_install_property ( + object_class, + PROP_SIGNED, + g_param_spec_int ( + "signed", + "Signed", + NULL, + CAMEL_CIPHER_VALIDITY_SIGN_NONE, + CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY, + CAMEL_CIPHER_VALIDITY_SIGN_NONE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +attachment_init (EAttachment *attachment) +{ + attachment->priv = E_ATTACHMENT_GET_PRIVATE (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 ( + attachment, "notify::encrypted", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::file-info", + G_CALLBACK (attachment_update_file_info_columns), NULL); + + g_signal_connect ( + attachment, "notify::file-info", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::loading", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::loading", + G_CALLBACK (attachment_update_progress_columns), NULL); + + g_signal_connect ( + attachment, "notify::percent", + G_CALLBACK (attachment_update_progress_columns), NULL); + + g_signal_connect ( + attachment, "notify::reference", + G_CALLBACK (attachment_update_file_info_columns), NULL); + + g_signal_connect ( + attachment, "notify::reference", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::reference", + G_CALLBACK (attachment_update_progress_columns), NULL); + + g_signal_connect ( + attachment, "notify::saving", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::saving", + G_CALLBACK (attachment_update_progress_columns), NULL); + + g_signal_connect ( + attachment, "notify::signed", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect_swapped ( + attachment->priv->cancellable, "cancelled", + G_CALLBACK (attachment_cancelled_cb), attachment); } GType @@ -177,707 +861,1814 @@ e_attachment_get_type (void) { static GType type = 0; - if (type == 0) { - static const GTypeInfo info = { + if (G_UNLIKELY (type == 0)) { + static const GTypeInfo type_info = { sizeof (EAttachmentClass), - NULL, - NULL, - (GClassInitFunc) class_init, - NULL, - NULL, + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) attachment_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ sizeof (EAttachment), - 0, - (GInstanceInitFunc) init, + 0, /* n_preallocs */ + (GInstanceInitFunc) attachment_init, + NULL /* value_table */ }; - type = g_type_register_static (G_TYPE_OBJECT, "EAttachment", &info, 0); + type = g_type_register_static ( + G_TYPE_OBJECT, "EAttachment", &type_info, 0); } return type; } -/** - * file_ext_is: - * @param file_name: path for file - * @param ext: desired extension, with a dot - * @return if file_name has extension ext or not - **/ +EAttachment * +e_attachment_new (void) +{ + return g_object_new (E_TYPE_ATTACHMENT, NULL); +} -static gboolean -file_ext_is (const char *file_name, const char *ext) +EAttachment * +e_attachment_new_for_path (const gchar *path) { - int i, dot = -1; + EAttachment *attachment; + GFile *file; - if (!file_name || !ext) - return FALSE; + g_return_val_if_fail (path != NULL, NULL); - for (i = 0; file_name[i]; i++) { - if (file_name [i] == '.') - dot = i; - } + file = g_file_new_for_path (path); + attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL); + g_object_unref (file); - if (dot > 0) { - return 0 == g_ascii_strcasecmp (file_name + dot, ext); - } + return attachment; +} - return FALSE; +EAttachment * +e_attachment_new_for_uri (const gchar *uri) +{ + EAttachment *attachment; + GFile *file; + + g_return_val_if_fail (uri != NULL, NULL); + + file = g_file_new_for_uri (uri); + attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL); + g_object_unref (file); + + return attachment; } -static char * -attachment_guess_mime_type (const char *file_name) +EAttachment * +e_attachment_new_for_message (CamelMimeMessage *message) { - char *type; - gchar *content = NULL; + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + EAttachment *attachment; + GString *description; + const gchar *subject; - type = e_util_guess_mime_type (file_name, TRUE); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); - if (type && strcmp (type, "text/directory") == 0 && - file_ext_is (file_name, ".vcf") && - g_file_get_contents (file_name, &content, NULL, NULL) && - content) { - EVCard *vc = e_vcard_new_from_string (content); + mime_part = camel_mime_part_new (); + camel_mime_part_set_disposition (mime_part, "inline"); + subject = camel_mime_message_get_subject (message); - if (vc) { - g_free (type); - g_object_unref (G_OBJECT (vc)); + description = g_string_new (_("Attached message")); + if (subject != NULL) + g_string_append_printf (description, " - %s", subject); + camel_mime_part_set_description (mime_part, description->str); + g_string_free (description, TRUE); + + wrapper = CAMEL_DATA_WRAPPER (message); + camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper); + camel_mime_part_set_content_type (mime_part, "message/rfc822"); + + attachment = e_attachment_new (); + e_attachment_set_mime_part (attachment, mime_part); + camel_object_unref (mime_part); + + return attachment; +} + +void +e_attachment_add_to_multipart (EAttachment *attachment, + CamelMultipart *multipart, + const gchar *default_charset) +{ + CamelContentType *content_type; + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + + /* XXX EMsgComposer might be a better place for this function. */ + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (CAMEL_IS_MULTIPART (multipart)); + + /* Still loading? Too bad. */ + mime_part = e_attachment_get_mime_part (attachment); + if (mime_part == NULL) + return; + + content_type = camel_mime_part_get_content_type (mime_part); + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); + + if (CAMEL_IS_MULTIPART (wrapper)) + goto exit; + + /* For text content, determine the best encoding and character set. */ + if (camel_content_type_is (content_type, "text", "*")) { + CamelTransferEncoding encoding; + CamelStreamFilter *filtered_stream; + CamelMimeFilterBestenc *filter; + CamelStream *stream; + const gchar *charset; + + charset = camel_content_type_param (content_type, "charset"); + + /* Determine the best encoding by writing the MIME + * part to a NULL stream with a "bestenc" filter. */ + stream = camel_stream_null_new (); + filtered_stream = camel_stream_filter_new_with_stream (stream); + filter = camel_mime_filter_bestenc_new ( + CAMEL_BESTENC_GET_ENCODING); + camel_stream_filter_add ( + filtered_stream, CAMEL_MIME_FILTER (filter)); + camel_data_wrapper_decode_to_stream ( + wrapper, CAMEL_STREAM (filtered_stream)); + camel_object_unref (filtered_stream); + camel_object_unref (stream); + + /* Retrieve the best encoding from the filter. */ + encoding = camel_mime_filter_bestenc_get_best_encoding ( + filter, CAMEL_BESTENC_8BIT); + camel_mime_part_set_encoding (mime_part, encoding); + camel_object_unref (filter); + + if (encoding == CAMEL_TRANSFER_ENCODING_7BIT) { + /* The text fits within us-ascii, so this is safe. + * FIXME Check that this isn't iso-2022-jp? */ + default_charset = "us-ascii"; + + } else if (charset == NULL && default_charset == NULL) { + default_charset = attachment_get_default_charset (); + /* FIXME Check that this fits within the + * default_charset and if not, find one + * that does and/or allow the user to + * specify. */ + } - type = g_strdup ("text/x-vcard"); + if (charset == NULL) { + gchar *type; + + camel_content_type_set_param ( + content_type, "charset", default_charset); + type = camel_content_type_format (content_type); + camel_mime_part_set_content_type (mime_part, type); + g_free (type); } + /* Otherwise, unless it's a message/rfc822, Base64 encode it. */ + } else if (!CAMEL_IS_MIME_MESSAGE (wrapper)) + camel_mime_part_set_encoding ( + mime_part, CAMEL_TRANSFER_ENCODING_BASE64); + +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); +} + +gboolean +e_attachment_get_can_show (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + return attachment->priv->can_show; +} + +void +e_attachment_set_can_show (EAttachment *attachment, + gboolean can_show) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + attachment->priv->can_show = can_show; + + g_object_notify (G_OBJECT (attachment), "can-show"); +} + +const gchar * +e_attachment_get_disposition (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->disposition; +} + +void +e_attachment_set_disposition (EAttachment *attachment, + const gchar *disposition) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + g_free (attachment->priv->disposition); + attachment->priv->disposition = g_strdup (disposition); + + g_object_notify (G_OBJECT (attachment), "disposition"); +} + +GFile * +e_attachment_get_file (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->file; +} + +void +e_attachment_set_file (EAttachment *attachment, + GFile *file) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + if (file != NULL) { + g_return_if_fail (G_IS_FILE (file)); + g_object_ref (file); } - g_free (content); + if (attachment->priv->file != NULL) + g_object_unref (attachment->priv->file); - if (type) { - /* Check if returned mime_type is valid */ - CamelContentType *ctype = camel_content_type_decode (type); + attachment->priv->file = file; - if (!ctype) { - g_free (type); - type = NULL; - } else - camel_content_type_unref (ctype); + g_object_notify (G_OBJECT (attachment), "file"); +} + +GFileInfo * +e_attachment_get_file_info (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + 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) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->mime_part; +} + +void +e_attachment_set_mime_part (EAttachment *attachment, + CamelMimePart *mime_part) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + if (mime_part != NULL) { + g_return_if_fail (CAMEL_IS_MIME_PART (mime_part)); + camel_object_ref (mime_part); } - return type; + if (attachment->priv->mime_part != NULL) + camel_object_unref (attachment->priv->mime_part); + + attachment->priv->mime_part = mime_part; + + g_object_notify (G_OBJECT (attachment), "mime-part"); } - -/** - * e_attachment_new: - * @file_name: filename to attach - * @disposition: Content-Disposition of the attachment - * @ex: exception - * - * Return value: the new attachment, or %NULL on error - **/ -EAttachment * -e_attachment_new (const char *file_name, const char *disposition, CamelException *ex) +gint +e_attachment_get_percent (EAttachment *attachment) { - EAttachment *new; - CamelMimePart *part; - CamelDataWrapper *wrapper; - CamelStream *stream; - struct stat statbuf; - char *mime_type; - char *filename; - CamelURL *url; + 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); + + 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; +} + +gboolean +e_attachment_get_shown (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + return attachment->priv->shown; +} + +void +e_attachment_set_shown (EAttachment *attachment, + gboolean shown) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + attachment->priv->shown = shown; + + g_object_notify (G_OBJECT (attachment), "shown"); +} + +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); - g_return_val_if_fail (file_name != NULL, NULL); + return attachment->priv->encrypted; +} + +void +e_attachment_set_encrypted (EAttachment *attachment, + camel_cipher_validity_encrypt_t encrypted) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + attachment->priv->encrypted = encrypted; + + g_object_notify (G_OBJECT (attachment), "encrypted"); +} - if (g_stat (file_name, &statbuf) < 0) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: %s"), - file_name, g_strerror (errno)); +camel_cipher_validity_sign_t +e_attachment_get_signed (EAttachment *attachment) +{ + g_return_val_if_fail ( + E_IS_ATTACHMENT (attachment), + CAMEL_CIPHER_VALIDITY_SIGN_NONE); + + return attachment->priv->signed_; +} + +void +e_attachment_set_signed (EAttachment *attachment, + camel_cipher_validity_sign_t signed_) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + attachment->priv->signed_ = signed_; + + g_object_notify (G_OBJECT (attachment), "signed"); +} + +const gchar * +e_attachment_get_description (EAttachment *attachment) +{ + GFileInfo *file_info; + const gchar *attribute; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION; + file_info = e_attachment_get_file_info (attachment); + + if (file_info == NULL) return NULL; - } - /* return if it's not a regular file */ - if (!S_ISREG (statbuf.st_mode)) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: not a regular file"), - file_name); + return g_file_info_get_attribute_string (file_info, attribute); +} + +const gchar * +e_attachment_get_thumbnail_path (EAttachment *attachment) +{ + GFileInfo *file_info; + const gchar *attribute; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH; + file_info = e_attachment_get_file_info (attachment); + + if (file_info == NULL) return NULL; - } - if (!(stream = camel_stream_fs_new_with_name (file_name, O_RDONLY, 0))) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: %s"), - file_name, g_strerror (errno)); + return g_file_info_get_attribute_byte_string (file_info, attribute); +} + +gboolean +e_attachment_is_rfc822 (EAttachment *attachment) +{ + GFileInfo *file_info; + const gchar *content_type; + gchar *mime_type; + gboolean is_rfc822; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + 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; + + mime_type = g_content_type_get_mime_type (content_type); + is_rfc822 = (g_ascii_strcasecmp (mime_type, "message/rfc822") == 0); + g_free (mime_type); + + return is_rfc822; +} + +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); + + file_info = e_attachment_get_file_info (attachment); + if (file_info == NULL) return NULL; - } - if ((mime_type = attachment_guess_mime_type (file_name))) { - if (!g_ascii_strcasecmp (mime_type, "message/rfc822")) { - wrapper = (CamelDataWrapper *) camel_mime_message_new (); - } else { - wrapper = camel_data_wrapper_new (); - } + 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); + + if (app_info_list != NULL || display_name == NULL) + goto exit; + + if (!g_content_type_is_unknown (content_type)) + goto exit; - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_data_wrapper_set_mime_type (wrapper, mime_type); - g_free (mime_type); - } else { + allocated = g_content_type_guess (display_name, NULL, 0, NULL); + app_info_list = g_app_info_get_all_for_type (allocated); + g_free (allocated); + +exit: + return app_info_list; +} + +/************************* e_attachment_load_async() *************************/ + +typedef struct _LoadContext LoadContext; + +struct _LoadContext { + 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_load_stream_read_cb (GInputStream *input_stream, + GAsyncResult *result, + LoadContext *load_context); + +static LoadContext * +attachment_load_context_new (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadContext *load_context; + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new ( + G_OBJECT (attachment), callback, + user_data, e_attachment_load_async); + + load_context = g_slice_new0 (LoadContext); + load_context->attachment = g_object_ref (attachment); + load_context->simple = simple; + + attachment_set_loading (load_context->attachment, TRUE); + + return load_context; +} + +static void +attachment_load_context_free (LoadContext *load_context) +{ + /* Do not free the GSimpleAsyncResult. */ + g_object_unref (load_context->attachment); + + if (load_context->input_stream != NULL) + g_object_unref (load_context->input_stream); + + if (load_context->output_stream != NULL) + g_object_unref (load_context->output_stream); + + if (load_context->file_info != NULL) + g_object_unref (load_context->file_info); + + g_slice_free (LoadContext, load_context); +} + +static gboolean +attachment_load_check_for_error (LoadContext *load_context, + GError *error) +{ + GSimpleAsyncResult *simple; + + if (error == NULL) + return FALSE; + + /* Steal the result. */ + 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 TRUE; +} + +static void +attachment_load_finish (LoadContext *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; + + /* Steal the result. */ + simple = load_context->simple; + load_context->simple = NULL; + + file_info = load_context->file_info; + attachment = load_context->attachment; + output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream); + + if (e_attachment_is_rfc822 (attachment)) + wrapper = (CamelDataWrapper *) camel_mime_message_new (); + else wrapper = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_data_wrapper_set_mime_type (wrapper, "application/octet-stream"); - } + content_type = g_file_info_get_content_type (file_info); + mime_type = g_content_type_get_mime_type (content_type); + + data = g_memory_output_stream_get_data (output_stream); + size = g_memory_output_stream_get_data_size (output_stream); + + 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); - part = camel_mime_part_new (); - camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); + mime_part = camel_mime_part_new (); + camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper); + camel_object_unref (wrapper); + g_free (mime_type); - camel_mime_part_set_disposition (part, disposition); - filename = g_path_get_basename (file_name); - camel_mime_part_set_filename (part, filename); - -#if 0 - /* Note: Outlook 2002 is broken with respect to Content-Ids on - non-multipart/related parts, so as an interoperability - workaround, don't set a Content-Id on these parts. Fixes - bug #10032 */ - /* set the Content-Id */ - content_id = camel_header_msgid_generate (); - camel_mime_part_set_content_id (part, content_id); - g_free (content_id); -#endif + display_name = g_file_info_get_display_name (file_info); + if (display_name != NULL) + camel_mime_part_set_filename (mime_part, display_name); - new = g_object_new (E_TYPE_ATTACHMENT, NULL); - new->editor_gui = NULL; - new->body = part; - new->size = statbuf.st_size; - new->guessed_type = TRUE; - new->cancellable = NULL; - new->is_available_local = TRUE; - new->file_name = filename; + 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); - url = camel_url_new ("file://", NULL); - camel_url_set_path (url, file_name); - new->store_uri = camel_url_to_string (url, 0); - camel_url_free (url); + disposition = e_attachment_get_disposition (attachment); + if (disposition != NULL) + camel_mime_part_set_disposition (mime_part, disposition); - return new; -} + 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); +} -typedef struct { +static void +attachment_load_write_cb (GOutputStream *output_stream, + GAsyncResult *result, + LoadContext *load_context) +{ EAttachment *attachment; - char *file_name; - char *uri; - GtkWindow *parent; /* for error dialog */ - - guint64 file_size; /* zero indicates unknown size */ - GInputStream *istream; /* read from here ... */ - GOutputStream *ostream; /* ...and write into this. */ - gboolean was_error; GCancellable *cancellable; + GInputStream *input_stream; + gssize bytes_written; + GError *error = NULL; + + bytes_written = g_output_stream_write_finish ( + output_stream, result, &error); - void *buffer; /* read into this, not more than buffer_size bytes */ - gsize buffer_size; -} DownloadInfo; + if (attachment_load_check_for_error (load_context, error)) + return; + + 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 void -download_info_free (DownloadInfo *download_info) +attachment_load_stream_read_cb (GInputStream *input_stream, + GAsyncResult *result, + LoadContext *load_context) { - /* if there was an error, then free attachment too */ - if (download_info->was_error) - g_object_unref (download_info->attachment); + EAttachment *attachment; + GCancellable *cancellable; + GOutputStream *output_stream; + gssize bytes_read; + GError *error = NULL; - if (download_info->ostream) - g_object_unref (download_info->ostream); + bytes_read = g_input_stream_read_finish ( + input_stream, result, &error); - if (download_info->istream) - g_object_unref (download_info->istream); + if (attachment_load_check_for_error (load_context, error)) + return; - if (download_info->cancellable) - g_object_unref (download_info->cancellable); + if (bytes_read == 0) { + attachment_load_finish (load_context); + return; + } - g_free (download_info->file_name); - g_free (download_info->uri); - g_free (download_info->buffer); - g_free (download_info); + attachment = load_context->attachment; + cancellable = attachment->priv->cancellable; + output_stream = load_context->output_stream; + load_context->bytes_read = bytes_read; + + 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 -data_ready_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) +attachment_load_file_read_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) { - DownloadInfo *download_info = (DownloadInfo *)user_data; + EAttachment *attachment; + GCancellable *cancellable; + GFileInputStream *input_stream; + GOutputStream *output_stream; GError *error = NULL; - gssize read; - g_return_if_fail (download_info != NULL); + /* Input stream might be NULL, so don't use cast macro. */ + input_stream = g_file_read_finish (file, result, &error); + load_context->input_stream = (GInputStream *) input_stream; - if (g_cancellable_is_cancelled (download_info->cancellable)) { - /* finish the operation and close both streams */ - g_input_stream_read_finish (G_INPUT_STREAM (source_object), res, NULL); + if (attachment_load_check_for_error (load_context, error)) + return; - g_output_stream_close (download_info->ostream, NULL, NULL); - g_input_stream_close (download_info->istream, NULL, NULL); + /* 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); +} - /* The only way how to get this canceled is in EAttachment's finalize method, - and because the download_info_free free's the attachment on error, - then do not consider cancellation as an error. */ - download_info_free (download_info); +static void +attachment_load_query_info_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GFileInfo *file_info; + GError *error = NULL; + + attachment = load_context->attachment; + cancellable = attachment->priv->cancellable; + + file_info = g_file_query_info_finish (file, result, &error); + attachment_set_file_info (attachment, file_info); + load_context->file_info = file_info; + + if (attachment_load_check_for_error (load_context, error)) 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 (LoadContext *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); + + file_info = g_file_info_new (); + load_context->file_info = file_info; + + 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_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); - read = g_input_stream_read_finish (G_INPUT_STREAM (source_object), res, &error); + string = camel_mime_part_get_filename (mime_part); + if (string == NULL) + /* Translators: Default attachment filename. */ + string = _("attachment.dat"); + g_file_info_set_display_name (file_info, string); - if (!error) - g_output_stream_write_all (download_info->ostream, download_info->buffer, read, NULL, download_info->cancellable, &error); + 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); - if (error) { - download_info->was_error = error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED; - if (download_info->was_error) - e_error_run (download_info->parent, "mail-composer:no-attach", download_info->uri, error->message, NULL); + size = (goffset) camel_mime_part_get_content_size (mime_part); + g_file_info_set_size (file_info, size); - g_error_free (error); + string = camel_mime_part_get_disposition (mime_part); + e_attachment_set_disposition (attachment, string); + + attachment_set_file_info (attachment, file_info); - download_info->attachment->cancellable = NULL; - download_info_free (download_info); + /* Steal the result. */ + 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); +} + +void +e_attachment_load_async (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadContext *load_context; + GCancellable *cancellable; + CamelMimePart *mime_part; + GFile *file; + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (callback != NULL); + + if (e_attachment_get_loading (attachment)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_BUSY, + _("A load operation is already in progress")); + return; + } + + if (e_attachment_get_saving (attachment)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_BUSY, + _("A save operation is already in progress")); return; } - if (read == 0) { - CamelException ex; + file = e_attachment_get_file (attachment); + mime_part = e_attachment_get_mime_part (attachment); + g_return_if_fail (file != NULL || mime_part != NULL); - /* done with reading */ - g_output_stream_close (download_info->ostream, NULL, NULL); - g_input_stream_close (download_info->istream, NULL, NULL); + load_context = attachment_load_context_new ( + attachment, callback, user_data); - download_info->attachment->cancellable = NULL; + cancellable = attachment->priv->cancellable; + g_cancellable_reset (cancellable); - camel_exception_init (&ex); - e_attachment_build_remote_file (download_info->file_name, download_info->attachment, "attachment", &ex); + 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); - if (camel_exception_is_set (&ex)) { - download_info->was_error = TRUE; - e_error_run (download_info->parent, "mail-composer:no-attach", download_info->uri, camel_exception_get_description (&ex), NULL); - camel_exception_clear (&ex); - } + else if (mime_part != NULL) + attachment_load_from_mime_part (load_context); + +} + +gboolean +e_attachment_load_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + CamelMimePart *mime_part; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), 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); +} - download_info->attachment->percentage = -1; - download_info->attachment->is_available_local = TRUE; - g_signal_emit (download_info->attachment, signals[UPDATE], 0); +void +e_attachment_load_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent) +{ + GtkWidget *dialog; + GFileInfo *file_info; + GtkTreeRowReference *reference; + const gchar *display_name; + const gchar *primary_text; + GError *error = NULL; - download_info_free (download_info); + 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; - } else if (download_info->file_size) { - download_info->attachment->percentage = read * 100 / download_info->file_size; - download_info->file_size -= MIN (download_info->file_size, read); - g_signal_emit (download_info->attachment, signals[UPDATE], 0); - } else { - download_info->attachment->percentage = 0; - g_signal_emit (download_info->attachment, signals[UPDATE], 0); + + /* XXX Calling EAttachmentStore functions from here violates + * the abstraction, but for now it's not hurting anything. */ + reference = e_attachment_get_reference (attachment); + if (gtk_tree_row_reference_valid (reference)) { + GtkTreeModel *model; + + model = gtk_tree_row_reference_get_model (reference); + + e_attachment_store_remove_attachment ( + E_ATTACHMENT_STORE (model), attachment); } - /* read next chunk */ - g_input_stream_read_async (download_info->istream, download_info->buffer, download_info->buffer_size, G_PRIORITY_DEFAULT, download_info->cancellable, data_ready_cb, download_info); + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + file_info = e_attachment_get_file_info (attachment); + + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; + + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not load '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not load the attachment")); + + dialog = gtk_message_dialog_new_with_markup ( + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "<big><b>%s</b></big>", primary_text); + + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s", error->message); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + g_error_free (error); +} + +/************************* e_attachment_open_async() *************************/ + +typedef struct _OpenContext OpenContext; + +struct _OpenContext { + EAttachment *attachment; + GSimpleAsyncResult *simple; + + GAppInfo *app_info; +}; + +static OpenContext * +attachment_open_context_new (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) +{ + OpenContext *open_context; + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new ( + G_OBJECT (attachment), callback, + user_data, e_attachment_open_async); + + open_context = g_slice_new0 (OpenContext); + open_context->attachment = g_object_ref (attachment); + open_context->simple = simple; + + return open_context; +} + +static void +attachment_open_context_free (OpenContext *open_context) +{ + /* Do not free the GSimpleAsyncResult. */ + g_object_unref (open_context->attachment); + + if (open_context->app_info != NULL) + g_object_unref (open_context->app_info); + + g_slice_free (OpenContext, open_context); } static gboolean -download_to_local_path (DownloadInfo *download_info, CamelException *ex) +attachment_open_check_for_error (OpenContext *open_context, + GError *error) { - GError *error = NULL; - GFile *src = g_file_new_for_uri (download_info->uri); - GFile *des = g_file_new_for_path (download_info->file_name); - gboolean res = FALSE; + GSimpleAsyncResult *simple; - g_return_val_if_fail (src != NULL && des != NULL, FALSE); + if (error == NULL) + return FALSE; - download_info->ostream = G_OUTPUT_STREAM (g_file_replace (des, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error)); + /* Steal the result. */ + simple = open_context->simple; + open_context->simple = NULL; - if (download_info->ostream && !error) { - GFileInfo *fi; + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_error_free (error); - fi = g_file_query_info (src, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, NULL); + attachment_open_context_free (open_context); - if (fi) { - download_info->file_size = g_file_info_get_attribute_uint64 (fi, G_FILE_ATTRIBUTE_STANDARD_SIZE); - g_object_unref (fi); - } else { - download_info->file_size = 0; - } + return TRUE; +} - download_info->istream = G_INPUT_STREAM (g_file_read (src, NULL, &error)); +static void +attachment_open_file (GFile *file, + OpenContext *open_context) +{ + GdkAppLaunchContext *context; + GSimpleAsyncResult *simple; + GList *file_list; + gboolean success; + GError *error = NULL; - if (download_info->istream && !error) { - download_info->cancellable = g_cancellable_new (); - download_info->attachment->cancellable = download_info->cancellable; - download_info->buffer_size = 10240; /* max 10KB chunk */ - download_info->buffer = g_malloc (sizeof (char) * download_info->buffer_size); + /* Steal the result. */ + simple = open_context->simple; + open_context->simple = NULL; - g_input_stream_read_async (download_info->istream, download_info->buffer, download_info->buffer_size, G_PRIORITY_DEFAULT, download_info->cancellable, data_ready_cb, download_info); + /* Find a default app based on content type. */ + if (open_context->app_info == NULL) { + EAttachment *attachment; + GFileInfo *file_info; + const gchar *content_type; - res = TRUE; - } + attachment = open_context->attachment; + file_info = e_attachment_get_file_info (attachment); + if (file_info == NULL) + goto exit; + + content_type = g_file_info_get_content_type (file_info); + if (content_type == NULL) + goto exit; + + open_context->app_info = g_app_info_get_default_for_type ( + content_type, FALSE); } - if (error) { - /* propagate error */ - if (ex) - camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, error->message); + if (open_context->app_info == NULL) + goto exit; + + context = gdk_app_launch_context_new (); + file_list = g_list_prepend (NULL, 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); - download_info->was_error = TRUE; - download_info_free (download_info); } - g_object_unref (src); - g_object_unref (des); + g_simple_async_result_complete (simple); + attachment_open_context_free (open_context); +} - return res; +static void +attachment_open_save_finished_cb (EAttachment *attachment, + GAsyncResult *result, + OpenContext *open_context) +{ + GFile *file; + GError *error = NULL; + + file = e_attachment_save_finish (attachment, result, &error); + + if (attachment_open_check_for_error (open_context, error)) + return; + + attachment_open_file (file, open_context); + g_object_unref (file); } -EAttachment * -e_attachment_new_remote_file (GtkWindow *error_dlg_parent, const char *uri, const char *disposition, const char *path, CamelException *ex) +static void +attachment_open_save_temporary (OpenContext *open_context) { - EAttachment *new; - DownloadInfo *download_info; - CamelURL *url; - char *base; + GFile *file; + gchar *template; + gchar *path; + GError *error = NULL; - g_return_val_if_fail (uri != NULL, NULL); + errno = 0; - url = camel_url_new (uri, NULL); - base = g_path_get_basename (url->path); - camel_url_free (url); - - new = g_object_new (E_TYPE_ATTACHMENT, NULL); - new->editor_gui = NULL; - new->body = NULL; - new->size = 0; - new->guessed_type = FALSE; - new->cancellable = NULL; - new->is_available_local = FALSE; - new->percentage = 0; - new->file_name = g_build_filename (path, base, NULL); - - g_free (base); - - download_info = g_new0 (DownloadInfo, 1); - download_info->attachment = new; - download_info->file_name = g_strdup (new->file_name); - download_info->uri = g_strdup (uri); - download_info->parent = error_dlg_parent; - download_info->was_error = FALSE; - - /* it frees all on the error, so do not free it twice */ - if (!download_to_local_path (download_info, ex)) - return NULL; + /* XXX This could trigger a blocking temp directory cleanup. */ + template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ()); + path = e_mktemp (template); + g_free (template); - return new; + /* XXX Let's hope errno got set properly. */ + if (path == NULL) + g_set_error ( + &error, G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); + + /* We already know if there's an error, but this does the cleanup. */ + if (attachment_open_check_for_error (open_context, error)) + return; + + file = g_file_new_for_path (path); + + g_free (path); + + e_attachment_save_async ( + open_context->attachment, file, (GAsyncReadyCallback) + attachment_open_save_finished_cb, open_context); + + g_object_unref (file); +} + +void +e_attachment_open_async (EAttachment *attachment, + GAppInfo *app_info, + GAsyncReadyCallback callback, + gpointer user_data) +{ + OpenContext *open_context; + CamelMimePart *mime_part; + GFile *file; + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (callback != NULL); + + 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) { + attachment_open_file (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 (E_IS_ATTACHMENT (attachment), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), 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_build_remote_file (const char *file_name, EAttachment *attachment, const char *disposition, CamelException *ex) +e_attachment_open_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent) { - CamelMimePart *part; - CamelDataWrapper *wrapper; - CamelStream *stream; - struct stat statbuf; - char *mime_type; - char *filename; - CamelURL *url; - - g_return_if_fail (file_name != NULL); - - if (g_stat (file_name, &statbuf) == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: %s"), - file_name, g_strerror (errno)); - g_message ("Cannot attach file %s: %s\n", file_name, g_strerror (errno)); - return; - } + GtkWidget *dialog; + GFileInfo *file_info; + const gchar *display_name; + const gchar *primary_text; + GError *error = NULL; - /* return if it's not a regular file */ - if (!S_ISREG (statbuf.st_mode)) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: not a regular file"), - file_name); - g_message ("Cannot attach file %s: not a regular file", file_name); + 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_open_finish (attachment, result, &error)) return; - } - if (!(stream = camel_stream_fs_new_with_name (file_name, O_RDONLY, 0))) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: %s"), - file_name, g_strerror (errno)); + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; - } - if ((mime_type = attachment_guess_mime_type (file_name))) { - if (!g_ascii_strcasecmp (mime_type, "message/rfc822")) { - wrapper = (CamelDataWrapper *) camel_mime_message_new (); - } else { - wrapper = camel_data_wrapper_new (); - } + file_info = e_attachment_get_file_info (attachment); - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_data_wrapper_set_mime_type (wrapper, mime_type); - g_free (mime_type); - } else { - wrapper = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_data_wrapper_set_mime_type (wrapper, "application/octet-stream"); - } + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; - camel_object_unref (stream); + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not open '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not open the attachment")); - part = camel_mime_part_new (); - camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); - camel_object_unref (wrapper); + dialog = gtk_message_dialog_new_with_markup ( + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "<big><b>%s</b></big>", primary_text); - if (attachment->disposition) - camel_mime_part_set_disposition (part, "inline"); - else - camel_mime_part_set_disposition (part, "attachment"); + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s", error->message); - if (!attachment->file_name) - filename = g_path_get_basename (file_name); - else - filename = g_path_get_basename (attachment->file_name); + gtk_dialog_run (GTK_DIALOG (dialog)); - camel_mime_part_set_filename (part, filename); + gtk_widget_destroy (dialog); + g_error_free (error); +} - if (attachment->description) { - camel_mime_part_set_description (part, attachment->description); - g_free (attachment->description); - attachment->description = NULL; - } +/************************* e_attachment_save_async() *************************/ - attachment->editor_gui = NULL; - attachment->body = part; - attachment->size = statbuf.st_size; - attachment->guessed_type = TRUE; - g_free (attachment->file_name); - attachment->file_name = filename; +typedef struct _SaveContext SaveContext; - url = camel_url_new ("file://", NULL); - camel_url_set_path (url, file_name); - attachment->store_uri = camel_url_to_string (url, 0); - camel_url_free (url); +struct _SaveContext { + EAttachment *attachment; + GSimpleAsyncResult *simple; + + GFile *directory; + GFile *destination; + GInputStream *input_stream; + GOutputStream *output_stream; + goffset total_num_bytes; + gssize bytes_read; + gchar buffer[4096]; + gint count; +}; -} +/* Forward Declaration */ +static void +attachment_save_read_cb (GInputStream *input_stream, + GAsyncResult *result, + SaveContext *save_context); + +static SaveContext * +attachment_save_context_new (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SaveContext *save_context; + GSimpleAsyncResult *simple; + simple = g_simple_async_result_new ( + G_OBJECT (attachment), callback, + user_data, e_attachment_save_async); -/** - * e_attachment_new_from_mime_part: - * @part: a CamelMimePart - * - * Return value: a new EAttachment based on the mime part - **/ -EAttachment * -e_attachment_new_from_mime_part (CamelMimePart *part) + save_context = g_slice_new0 (SaveContext); + save_context->attachment = g_object_ref (attachment); + save_context->simple = simple; + + attachment_set_saving (save_context->attachment, TRUE); + + return save_context; +} + +static void +attachment_save_context_free (SaveContext *save_context) { - EAttachment *new; + /* Do not free the GSimpleAsyncResult. */ + g_object_unref (save_context->attachment); - g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL); + if (save_context->directory != NULL) + g_object_unref (save_context->directory); - new = g_object_new (E_TYPE_ATTACHMENT, NULL); - new->editor_gui = NULL; - camel_object_ref (part); - new->body = part; - new->guessed_type = FALSE; - new->is_available_local = TRUE; - new->size = camel_mime_part_get_content_size (part); - new->file_name = g_strdup (camel_mime_part_get_filename(part)); + if (save_context->destination != NULL) + g_object_unref (save_context->destination); - return new; + 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 (SaveContext, save_context); } - -/* The attachment property dialog. */ +static gboolean +attachment_save_check_for_error (SaveContext *save_context, + GError *error) +{ + GSimpleAsyncResult *simple; -typedef struct { - GtkWidget *dialog; - GtkEntry *file_name_entry; - GtkEntry *description_entry; - GtkEntry *mime_type_entry; - GtkToggleButton *disposition_checkbox; + if (error == NULL) + return FALSE; + + /* Steal the result. */ + 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); + + return TRUE; +} + +static GFile * +attachment_save_new_candidate (SaveContext *save_context) +{ + GFile *candidate; + GFileInfo *file_info; EAttachment *attachment; -} DialogData; + const gchar *display_name; + gchar *basename; + + attachment = save_context->attachment; + file_info = e_attachment_get_file_info (attachment); + + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + if (display_name == NULL) + /* Translators: Default attachment filename. */ + display_name = _("attachment.dat"); + + if (save_context->count == 0) + basename = g_strdup (display_name); + else { + GString *string; + const gchar *ext; + gsize length; + + string = g_string_sized_new (strlen (display_name)); + ext = g_utf8_strchr (display_name, -1, '.'); + + if (ext != NULL) + length = ext - display_name; + else + length = strlen (display_name); + + g_string_append_len (string, display_name, length); + g_string_append_printf (string, " (%d)", save_context->count); + g_string_append (string, (ext != NULL) ? ext : ""); + + basename = g_string_free (string, FALSE); + } + + save_context->count++; + + candidate = g_file_get_child (save_context->directory, basename); + + g_free (basename); + + return candidate; +} static void -destroy_dialog_data (DialogData *data) +attachment_save_write_cb (GOutputStream *output_stream, + GAsyncResult *result, + SaveContext *save_context) { - g_free (data); -} + EAttachment *attachment; + GCancellable *cancellable; + GInputStream *input_stream; + gssize bytes_written; + GError *error = NULL; -/* - * fixme: I am converting EVERYTHING to/from UTF-8, although mime types - * are in ASCII. This is not strictly necessary, but we want to be - * consistent and possibly check for errors somewhere. - */ + bytes_written = g_output_stream_write_finish ( + output_stream, result, &error); + + if (attachment_save_check_for_error (save_context, error)) + return; + + 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 void -set_entry (GladeXML *xml, const char *widget_name, const char *value) +attachment_save_read_cb (GInputStream *input_stream, + GAsyncResult *result, + SaveContext *save_context) { - GtkEntry *entry; + EAttachment *attachment; + GCancellable *cancellable; + GOutputStream *output_stream; + gssize bytes_read; + GError *error = NULL; - entry = GTK_ENTRY (glade_xml_get_widget (xml, widget_name)); - if (entry == NULL) - g_warning ("Entry for `%s' not found.", widget_name); - else - gtk_entry_set_text (entry, value ? value : ""); + bytes_read = g_input_stream_read_finish ( + input_stream, result, &error); + + if (attachment_save_check_for_error (save_context, error)) + return; + + if (bytes_read == 0) { + GSimpleAsyncResult *simple; + GFile *destination; + + /* Steal the result. */ + simple = save_context->simple; + save_context->simple = NULL; + + /* Steal the destination. */ + destination = save_context->destination; + save_context->destination = NULL; + + g_simple_async_result_set_op_res_gpointer ( + simple, destination, (GDestroyNotify) g_object_unref); + g_simple_async_result_complete (simple); + + attachment_save_context_free (save_context); + + return; + } + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + output_stream = save_context->output_stream; + save_context->bytes_read = bytes_read; + + attachment_progress_cb ( + g_seekable_tell (G_SEEKABLE (input_stream)), + save_context->total_num_bytes, attachment); + + g_output_stream_write_async ( + output_stream, + save_context->buffer, + save_context->bytes_read, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_write_cb, + save_context); } static void -connect_widget (GladeXML *gui, const char *name, const char *signal_name, - GCallback func, gpointer data) +attachment_save_got_output_stream (SaveContext *save_context) { - GtkWidget *widget; + GCancellable *cancellable; + GInputStream *input_stream; + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + CamelStream *stream; + EAttachment *attachment; + GByteArray *buffer; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + mime_part = e_attachment_get_mime_part (attachment); + + /* 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 (); + camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buffer); + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); + camel_data_wrapper_decode_to_stream (wrapper, stream); + camel_object_unref (stream); - widget = glade_xml_get_widget (gui, name); - g_signal_connect (widget, signal_name, func, data); + /* 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); } static void -close_cb (GtkWidget *widget, gpointer data) +attachment_save_create_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) { EAttachment *attachment; - DialogData *dialog_data; + GCancellable *cancellable; + GFileOutputStream *output_stream; + GError *error = NULL; + + /* Output stream might be NULL, so don't use cast macro. */ + output_stream = g_file_create_finish (destination, result, &error); + save_context->output_stream = (GOutputStream *) output_stream; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { + destination = attachment_save_new_candidate (save_context); - dialog_data = (DialogData *) data; - attachment = dialog_data->attachment; + g_file_create_async ( + destination, G_FILE_CREATE_NONE, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_create_cb, + save_context); - gtk_widget_destroy (dialog_data->dialog); - g_object_unref (attachment->editor_gui); - attachment->editor_gui = NULL; + g_object_unref (destination); + g_error_free (error); + return; + } + + if (attachment_save_check_for_error (save_context, error)) + return; + + save_context->destination = g_object_ref (destination); + attachment_save_got_output_stream (save_context); +} + +static void +attachment_save_replace_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) +{ + GFileOutputStream *output_stream; + GError *error = NULL; - destroy_dialog_data (dialog_data); + /* Output stream might be NULL, so don't use cast macro. */ + output_stream = g_file_replace_finish (destination, result, &error); + save_context->output_stream = (GOutputStream *) output_stream; + + if (attachment_save_check_for_error (save_context, error)) + return; + + save_context->destination = g_object_ref (destination); + attachment_save_got_output_stream (save_context); } static void -ok_cb (GtkWidget *widget, gpointer data) +attachment_save_query_info_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) { - DialogData *dialog_data; EAttachment *attachment; - const char *str; - - dialog_data = (DialogData *) data; - attachment = dialog_data->attachment; - - str = gtk_entry_get_text (dialog_data->file_name_entry); - if (attachment->is_available_local) - camel_mime_part_set_filename (attachment->body, str); - g_free (attachment->file_name); - attachment->file_name = g_strdup (str); - - str = gtk_entry_get_text (dialog_data->description_entry); - if (attachment->is_available_local) { - camel_mime_part_set_description (attachment->body, str); - } else { - g_free (attachment->description); - attachment->description = g_strdup (str); + GCancellable *cancellable; + GFileInfo *file_info; + GFileType file_type; + GError *error = NULL; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + + file_info = g_file_query_info_finish (destination, result, &error); + + /* G_IO_ERROR_NOT_FOUND just means we're creating a new file. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + g_error_free (error); + goto replace; } - str = gtk_entry_get_text (dialog_data->mime_type_entry); - if (attachment->is_available_local) { - camel_mime_part_set_content_type (attachment->body, str); - camel_data_wrapper_set_mime_type(camel_medium_get_content_object(CAMEL_MEDIUM (attachment->body)), str); + if (attachment_save_check_for_error (save_context, error)) + return; + + file_type = g_file_info_get_file_type (file_info); + g_object_unref (file_info); + + if (file_type == G_FILE_TYPE_DIRECTORY) { + save_context->directory = g_object_ref (destination); + destination = attachment_save_new_candidate (save_context); + + g_file_create_async ( + destination, G_FILE_CREATE_NONE, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_create_cb, + save_context); + + g_object_unref (destination); + + return; } - if (attachment->is_available_local) { - switch (gtk_toggle_button_get_active (dialog_data->disposition_checkbox)) { - case 0: - camel_mime_part_set_disposition (attachment->body, "attachment"); - break; - case 1: - camel_mime_part_set_disposition (attachment->body, "inline"); - break; - default: - /* Hmmmm? */ - break; - } - } else { - attachment->disposition = gtk_toggle_button_get_active (dialog_data->disposition_checkbox); +replace: + g_file_replace_async ( + destination, NULL, FALSE, +#if GLIB_CHECK_VERSION(2,20,0) + G_FILE_CREATE_REPLACE_DESTINATION, +#else + G_FILE_CREATE_NONE, +#endif + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_replace_cb, + save_context); +} + +void +e_attachment_save_async (EAttachment *attachment, + GFile *destination, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SaveContext *save_context; + GCancellable *cancellable; + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (G_IS_FILE (destination)); + g_return_if_fail (callback != NULL); + + if (e_attachment_get_loading (attachment)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_BUSY, + _("A load operation is already in progress")); + return; } - changed (attachment); - close_cb (widget, data); + if (e_attachment_get_saving (attachment)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_BUSY, + _("A save operation is already in progress")); + return; + } + + if (e_attachment_get_mime_part (attachment) == NULL) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Attachment contents not loaded")); + return; + } + + save_context = attachment_save_context_new ( + attachment, callback, user_data); + + cancellable = attachment->priv->cancellable; + g_cancellable_reset (cancellable); + + /* First we need to know if destination is a directory. */ + g_file_query_info_async ( + destination, G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, + cancellable, (GAsyncReadyCallback) + attachment_save_query_info_cb, save_context); } -static void -response_cb (GtkWidget *widget, gint response, gpointer data) +GFile * +e_attachment_save_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error) { - if (response == GTK_RESPONSE_OK) - ok_cb (widget, data); - else - close_cb (widget, data); + GSimpleAsyncResult *simple; + GFile *destination; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + destination = g_simple_async_result_get_op_res_gpointer (simple); + if (destination != NULL) + g_object_ref (destination); + g_simple_async_result_propagate_error (simple, error); + g_object_unref (simple); + + attachment_set_saving (attachment, FALSE); + + return destination; } void -e_attachment_edit (EAttachment *attachment, GtkWidget *parent) +e_attachment_save_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent) { - CamelContentType *content_type; - const char *disposition; - DialogData *dialog_data; - GladeXML *editor_gui; - GtkWidget *window; - char *type; - char *filename; + GFile *file; + GFileInfo *file_info; + GtkWidget *dialog; + 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)); + + file = e_attachment_save_finish (attachment, result, &error); - if (attachment->editor_gui != NULL) { - window = glade_xml_get_widget (attachment->editor_gui, "dialog"); - gdk_window_show (window->window); + if (file != NULL) { + g_object_unref (file); return; } - filename = g_build_filename (EVOLUTION_GLADEDIR, "e-attachment.glade", NULL); - editor_gui = glade_xml_new (filename, NULL, NULL); - g_free (filename); - - if (editor_gui == NULL) { - g_warning ("Cannot load `e-attachment.glade'"); + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; - } - attachment->editor_gui = editor_gui; - - gtk_window_set_transient_for (GTK_WINDOW (glade_xml_get_widget (editor_gui, "dialog")), - GTK_WINDOW (gtk_widget_get_toplevel (parent))); - - dialog_data = g_new (DialogData, 1); - dialog_data->attachment = attachment; - dialog_data->dialog = glade_xml_get_widget (editor_gui, "dialog"); - dialog_data->file_name_entry = GTK_ENTRY (glade_xml_get_widget (editor_gui, "file_name_entry")); - dialog_data->description_entry = GTK_ENTRY (glade_xml_get_widget (editor_gui, "description_entry")); - dialog_data->mime_type_entry = GTK_ENTRY (glade_xml_get_widget (editor_gui, "mime_type_entry")); - dialog_data->disposition_checkbox = GTK_TOGGLE_BUTTON (glade_xml_get_widget (editor_gui, "disposition_checkbox")); - - if (attachment->is_available_local && attachment->body) { - set_entry (editor_gui, "file_name_entry", camel_mime_part_get_filename (attachment->body)); - set_entry (editor_gui, "description_entry", camel_mime_part_get_description (attachment->body)); - content_type = camel_mime_part_get_content_type (attachment->body); - type = camel_content_type_simple (content_type); - set_entry (editor_gui, "mime_type_entry", type); - g_free (type); - - disposition = camel_mime_part_get_disposition (attachment->body); - gtk_toggle_button_set_active (dialog_data->disposition_checkbox, - disposition && !g_ascii_strcasecmp (disposition, "inline")); - } else { - set_entry (editor_gui, "file_name_entry", attachment->file_name); - set_entry (editor_gui, "description_entry", attachment->description); - if ((type = attachment_guess_mime_type (attachment->file_name))) { - set_entry (editor_gui, "mime_type_entry", type); - g_free (type); - } else { - set_entry (editor_gui, "mime_type_entry", ""); - } + file_info = e_attachment_get_file_info (attachment); - gtk_toggle_button_set_active (dialog_data->disposition_checkbox, attachment->disposition); - } + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; + + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not save '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not save the attachment")); + + dialog = gtk_message_dialog_new_with_markup ( + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "<big><b>%s</b></big>", primary_text); + + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s", error->message); - connect_widget (editor_gui, "dialog", "response", (GCallback)response_cb, dialog_data); + gtk_dialog_run (GTK_DIALOG (dialog)); - /* make sure that when the parent gets hidden/closed that our windows also close */ - parent = gtk_widget_get_toplevel (parent); - gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog_data->dialog), TRUE); + gtk_widget_destroy (dialog); + g_error_free (error); } |