diff options
Diffstat (limited to 'e-util/e-attachment.c')
-rw-r--r-- | e-util/e-attachment.c | 2882 |
1 files changed, 2882 insertions, 0 deletions
diff --git a/e-util/e-attachment.c b/e-util/e-attachment.c new file mode 100644 index 0000000000..3357775111 --- /dev/null +++ b/e-util/e-attachment.c @@ -0,0 +1,2882 @@ +/* + * 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 + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-attachment.h" + +#include <errno.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include <libedataserver/libedataserver.h> + +#include "e-attachment-store.h" +#include "e-icon-factory.h" +#include "e-mktemp.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; + GIcon *icon; + GFileInfo *file_info; + GCancellable *cancellable; + CamelMimePart *mime_part; + guint emblem_timeout_id; + gchar *disposition; + gint percent; + gint64 last_percent_notify; /* to avoid excessive notifications */ + + 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 { + PROP_0, + PROP_CAN_SHOW, + PROP_DISPOSITION, + PROP_ENCRYPTED, + PROP_FILE, + PROP_FILE_INFO, + PROP_ICON, + PROP_LOADING, + PROP_MIME_PART, + PROP_PERCENT, + PROP_REFERENCE, + PROP_SAVING, + PROP_SHOWN, + PROP_SIGNED +}; + +G_DEFINE_TYPE ( + EAttachment, + e_attachment, + G_TYPE_OBJECT) + +static gboolean +create_system_thumbnail (EAttachment *attachment, + GIcon **icon) +{ + GFile *file; + GFile *icon_file; + gchar *thumbnail = NULL; + + g_return_val_if_fail (attachment != NULL, FALSE); + g_return_val_if_fail (icon != NULL, FALSE); + + file = e_attachment_get_file (attachment); + + if (file && g_file_has_uri_scheme (file, "file")) { + gchar *path = g_file_get_path (file); + if (path) { + thumbnail = e_icon_factory_create_thumbnail (path); + g_free (path); + } + } + + if (thumbnail == NULL) + return FALSE; + + icon_file = g_file_new_for_path (thumbnail); + + if (*icon) + g_object_unref (*icon); + + *icon = g_file_icon_new (icon_file); + + g_object_unref (icon_file); + + if (file) { + GFileInfo *file_info; + const gchar *attribute; + + file_info = e_attachment_get_file_info (attachment); + attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH; + + if (file_info != NULL) + g_file_info_set_attribute_byte_string ( + file_info, attribute, thumbnail); + } + + g_free (thumbnail); + + return TRUE; +} + +static gchar * +attachment_get_default_charset (void) +{ + GSettings *settings; + gchar *charset; + + /* XXX This doesn't really belong here. */ + + settings = g_settings_new ("org.gnome.evolution.mail"); + charset = g_settings_get_string (settings, "composer-charset"); + if (charset == NULL || *charset == '\0') { + g_free (charset); + /* FIXME This was "/apps/evolution/mail/format/charset", + * not sure it relates to "charset" */ + charset = g_settings_get_string (settings, "charset"); + if (charset == NULL || *charset == '\0') { + g_free (charset); + charset = NULL; + } + } + g_object_unref (settings); + + if (charset == NULL) + charset = g_strdup (camel_iconv_locale_charset ()); + + if (charset == NULL) + charset = g_strdup ("us-ascii"); + + return charset; +} + +static void +attachment_update_file_info_columns (EAttachment *attachment) +{ + 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 (size); + + 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 +attachment_update_icon_column (EAttachment *attachment) +{ + 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; + + 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); + } + + /* 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); + + /* Try the system thumbnailer. */ + } else if (create_system_thumbnail (attachment, &icon)) { + /* Nothing to do, just use the icon. */ + + /* 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; + } + + 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; + } + + gtk_list_store_set ( + GTK_LIST_STORE (model), &iter, + E_ATTACHMENT_STORE_COLUMN_ICON, icon, + -1); + + /* Cache the icon to reuse for things like drag-n-drop. */ + if (attachment->priv->icon != NULL) + g_object_unref (attachment->priv->icon); + attachment->priv->icon = icon; + g_object_notify (G_OBJECT (attachment), "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; + + 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 +attachment_set_loading (EAttachment *attachment, + gboolean loading) +{ + GtkTreeRowReference *reference; + + reference = e_attachment_get_reference (attachment); + + attachment->priv->percent = 0; + attachment->priv->loading = loading; + attachment->priv->last_percent_notify = 0; + + 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; + attachment->priv->last_percent_notify = 0; + + 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) +{ + gint new_percent; + + /* Avoid dividing by zero. */ + if (total_num_bytes == 0) + return; + + /* do not notify too often, 5 times per second is sufficient */ + if (g_get_monotonic_time () - attachment->priv->last_percent_notify < 200000) + return; + + attachment->priv->last_percent_notify = g_get_monotonic_time (); + + new_percent = (current_num_bytes * 100) / total_num_bytes; + + if (new_percent != attachment->priv->percent) { + attachment->priv->percent = new_percent; + 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_ICON: + g_value_set_object ( + value, e_attachment_get_icon ( + 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->icon != NULL) { + g_object_unref (priv->icon); + priv->icon = 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) { + g_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 (e_attachment_parent_class)->dispose (object); +} + +static void +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 (e_attachment_parent_class)->finalize (object); +} + +static void +e_attachment_class_init (EAttachmentClass *class) +{ + GObjectClass *object_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_ICON, + g_param_spec_object ( + "icon", + "Icon", + NULL, + G_TYPE_ICON, + 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_object ( + "mime-part", + "MIME Part", + NULL, + CAMEL_TYPE_MIME_PART, + 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 +e_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); +} + +EAttachment * +e_attachment_new (void) +{ + return g_object_new (E_TYPE_ATTACHMENT, NULL); +} + +EAttachment * +e_attachment_new_for_path (const gchar *path) +{ + EAttachment *attachment; + GFile *file; + + g_return_val_if_fail (path != NULL, NULL); + + file = g_file_new_for_path (path); + attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL); + g_object_unref (file); + + return attachment; +} + +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; +} + +EAttachment * +e_attachment_new_for_message (CamelMimeMessage *message) +{ + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + EAttachment *attachment; + GString *description; + const gchar *subject; + + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); + + mime_part = camel_mime_part_new (); + camel_mime_part_set_disposition (mime_part, "inline"); + subject = camel_mime_message_get_subject (message); + + /* To Translators: This text is set as a description of an attached + * message when, for example, attaching it to a composer. When the + * message to be attached has also filled Subject, then this text is + * of form "Attached message - Subject", otherwise it's left as is. */ + 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 (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); + g_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 (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; + CamelStream *filtered_stream; + CamelMimeFilter *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 (stream); + filter = camel_mime_filter_bestenc_new ( + CAMEL_BESTENC_GET_ENCODING); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), + CAMEL_MIME_FILTER (filter)); + camel_data_wrapper_decode_to_stream_sync ( + wrapper, filtered_stream, NULL, NULL); + g_object_unref (filtered_stream); + g_object_unref (stream); + + /* Retrieve the best encoding from the filter. */ + encoding = camel_mime_filter_bestenc_get_best_encoding ( + CAMEL_MIME_FILTER_BESTENC (filter), + CAMEL_BESTENC_8BIT); + camel_mime_part_set_encoding (mime_part, encoding); + g_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. */ + } + + 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); + } + + if (attachment->priv->file != NULL) + g_object_unref (attachment->priv->file); + + attachment->priv->file = file; + + 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; +} + +void +e_attachment_set_file_info (EAttachment *attachment, + GFileInfo *file_info) +{ + 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"); + } +} + +/** + * e_attachment_get_mime_type: + * + * Returns mime_type part of the file_info as a newly allocated string, + * which should be freed with g_free(). + * Returns NULL, if mime_type not found or set on the attachment. + **/ +gchar * +e_attachment_get_mime_type (EAttachment *attachment) +{ + GFileInfo *file_info; + const gchar *content_type; + gchar *mime_type; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + file_info = e_attachment_get_file_info (attachment); + if (file_info == NULL) + return NULL; + + content_type = g_file_info_get_content_type (file_info); + if (content_type == NULL) + return NULL; + + mime_type = g_content_type_get_mime_type (content_type); + if (!mime_type) + return NULL; + + camel_strdown (mime_type); + + return mime_type; +} + +GIcon * +e_attachment_get_icon (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->icon; +} + +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)); + g_object_ref (mime_part); + } + + if (attachment->priv->mime_part != NULL) + g_object_unref (attachment->priv->mime_part); + + attachment->priv->mime_part = mime_part; + + g_object_notify (G_OBJECT (attachment), "mime-part"); +} + +gint +e_attachment_get_percent (EAttachment *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); + + 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); + + 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"); +} + +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 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; + + return g_file_info_get_attribute_byte_string (file_info, attribute); +} + +gboolean +e_attachment_is_rfc822 (EAttachment *attachment) +{ + gchar *mime_type; + gboolean is_rfc822; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + mime_type = e_attachment_get_mime_type (attachment); + is_rfc822 = mime_type && 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; + GList *guessed_infos; + GFileInfo *file_info; + const gchar *content_type; + const gchar *display_name; + gboolean type_is_unknown; + 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; + + 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); + type_is_unknown = g_content_type_is_unknown (content_type); + + if (app_info_list != NULL && !type_is_unknown) + goto exit; + + if (display_name == NULL) + goto exit; + + allocated = g_content_type_guess (display_name, NULL, 0, NULL); + guessed_infos = g_app_info_get_all_for_type (allocated); + app_info_list = g_list_concat (guessed_infos, app_info_list); + g_free (allocated); + +exit: + return app_info_list; +} + +/************************* e_attachment_load_async() *************************/ + +typedef struct _LoadContext LoadContext; + +struct _LoadContext { + EAttachment *attachment; + CamelMimePart *mime_part; + 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) +{ + g_object_unref (load_context->attachment); + + if (load_context->mime_part != NULL) + g_object_unref (load_context->mime_part); + + if (load_context->simple) + g_object_unref (load_context->simple); + + 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; + + simple = load_context->simple; + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + + 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; + + simple = load_context->simple; + + 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 (); + + 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_sync ( + wrapper, stream, NULL, NULL); + camel_data_wrapper_set_mime_type (wrapper, mime_type); + camel_stream_close (stream, NULL, NULL); + g_object_unref (stream); + + mime_part = camel_mime_part_new (); + camel_medium_set_content (CAMEL_MEDIUM (mime_part), wrapper); + + g_object_unref (wrapper); + g_free (mime_type); + + display_name = g_file_info_get_display_name (file_info); + if (display_name != NULL) + camel_mime_part_set_filename (mime_part, display_name); + + 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); + + /* Correctly report the size of zero length special files. */ + if (g_file_info_get_size (file_info) == 0) + g_file_info_set_size (file_info, size); + + load_context->mime_part = mime_part; + + g_simple_async_result_set_op_res_gpointer ( + simple, load_context, (GDestroyNotify) attachment_load_context_free); + + g_simple_async_result_complete (simple); + + /* make sure it's freed on operation end */ + load_context->simple = NULL; + g_object_unref (simple); +} + +static void +attachment_load_write_cb (GOutputStream *output_stream, + GAsyncResult *result, + LoadContext *load_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GInputStream *input_stream; + gssize bytes_written; + GError *error = NULL; + + bytes_written = g_output_stream_write_finish ( + output_stream, result, &error); + + 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 +attachment_load_stream_read_cb (GInputStream *input_stream, + GAsyncResult *result, + LoadContext *load_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GOutputStream *output_stream; + gssize bytes_read; + GError *error = NULL; + + bytes_read = g_input_stream_read_finish ( + input_stream, result, &error); + + if (attachment_load_check_for_error (load_context, error)) + return; + + if (bytes_read == 0) { + attachment_load_finish (load_context); + return; + } + + attachment = load_context->attachment; + cancellable = attachment->priv->cancellable; + output_stream = load_context->output_stream; + load_context->bytes_read = bytes_read; + + 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_load_file_read_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GFileInputStream *input_stream; + GOutputStream *output_stream; + GError *error = 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 (attachment_load_check_for_error (load_context, error)) + return; + + /* 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, + 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); + if (attachment_load_check_for_error (load_context, error)) + return; + + e_attachment_set_file_info (attachment, file_info); + load_context->file_info = file_info; + + 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); +} + +#define ATTACHMENT_LOAD_CONTEXT "attachment-load-context-data" + +static void +attachment_load_from_mime_part_thread (GSimpleAsyncResult *simple, + GObject *object, + GCancellable *cancellable) +{ + LoadContext *load_context; + GFileInfo *file_info; + EAttachment *attachment; + CamelContentType *content_type; + CamelMimePart *mime_part; + const gchar *attribute; + const gchar *string; + gchar *allocated; + CamelStream *null; + CamelDataWrapper *dw; + + load_context = g_object_get_data (G_OBJECT (simple), ATTACHMENT_LOAD_CONTEXT); + g_return_if_fail (load_context != NULL); + g_object_set_data (G_OBJECT (simple), ATTACHMENT_LOAD_CONTEXT, NULL); + + 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); + + /* Strip any path components from the filename. */ + string = camel_mime_part_get_filename (mime_part); + if (string == NULL) + /* Translators: Default attachment filename. */ + string = _("attachment.dat"); + allocated = g_path_get_basename (string); + g_file_info_set_display_name (file_info, allocated); + g_free (allocated); + + 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); + + dw = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); + null = camel_stream_null_new (); + /* this actually downloads the part and makes it available later */ + camel_data_wrapper_decode_to_stream_sync (dw, null, attachment->priv->cancellable, NULL); + g_file_info_set_size (file_info, CAMEL_STREAM_NULL (null)->written); + g_object_unref (null); + + load_context->mime_part = g_object_ref (mime_part); + + /* make sure it's freed on operation end */ + g_object_unref (load_context->simple); + load_context->simple = NULL; + + g_simple_async_result_set_op_res_gpointer ( + simple, load_context, + (GDestroyNotify) attachment_load_context_free); +} + +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)); + + 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; + } + + 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); + + 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); + + } else if (mime_part != NULL) { + g_object_set_data (G_OBJECT (load_context->simple), ATTACHMENT_LOAD_CONTEXT, load_context); + + g_simple_async_result_run_in_thread ( + load_context->simple, + attachment_load_from_mime_part_thread, + G_PRIORITY_DEFAULT, + cancellable); + } +} + +gboolean +e_attachment_load_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + const LoadContext *load_context; + + 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); + load_context = g_simple_async_result_get_op_res_gpointer (simple); + if (load_context && load_context->mime_part) { + const gchar *string; + + string = camel_mime_part_get_disposition (load_context->mime_part); + e_attachment_set_disposition (attachment, string); + + e_attachment_set_file_info (attachment, load_context->file_info); + e_attachment_set_mime_part (attachment, load_context->mime_part); + } + + g_simple_async_result_propagate_error (simple, error); + + attachment_set_loading (attachment, FALSE); + + return (load_context != NULL); +} + +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; + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (!parent || GTK_IS_WINDOW (parent)); + + if (e_attachment_load_finish (attachment, result, &error)) + return; + + /* 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); + } + + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free (error); + 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); +} + +gboolean +e_attachment_load (EAttachment *attachment, + GError **error) +{ + EAsyncClosure *closure; + GAsyncResult *result; + gboolean success; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + closure = e_async_closure_new (); + + e_attachment_load_async (attachment, e_async_closure_callback, closure); + + result = e_async_closure_wait (closure); + + success = e_attachment_load_finish (attachment, result, error); + + e_async_closure_free (closure); + + return success; +} + +/************************* 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) +{ + g_object_unref (open_context->attachment); + g_object_unref (open_context->simple); + + if (open_context->app_info != NULL) + g_object_unref (open_context->app_info); + + g_slice_free (OpenContext, open_context); +} + +static gboolean +attachment_open_check_for_error (OpenContext *open_context, + GError *error) +{ + GSimpleAsyncResult *simple; + + if (error == NULL) + return FALSE; + + simple = open_context->simple; + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + + attachment_open_context_free (open_context); + + return TRUE; +} + +static void +attachment_open_file (GFile *file, + OpenContext *open_context) +{ + GdkAppLaunchContext *context; + GSimpleAsyncResult *simple; + GdkDisplay *display; + gboolean success; + GError *error = NULL; + + simple = open_context->simple; + + display = gdk_display_get_default (); + context = gdk_display_get_app_launch_context (display); + + if (open_context->app_info != NULL) { + GList *file_list; + + 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_list_free (file_list); + } else { + gchar *uri; + + uri = g_file_get_uri (file); + success = g_app_info_launch_default_for_uri ( + uri, G_APP_LAUNCH_CONTEXT (context), &error); + g_free (uri); + } + + g_object_unref (context); + + g_simple_async_result_set_op_res_gboolean (simple, success); + + if (error != NULL) + g_simple_async_result_take_error (simple, error); + + g_simple_async_result_complete (simple); + attachment_open_context_free (open_context); +} + +static void +attachment_open_save_finished_cb (EAttachment *attachment, + GAsyncResult *result, + OpenContext *open_context) +{ + GFile *file; + gchar *path; + GError *error = NULL; + + file = e_attachment_save_finish (attachment, result, &error); + + if (attachment_open_check_for_error (open_context, error)) + return; + + /* Make the temporary file read-only. + * + * This step is non-critical, so if an error occurs just + * emit a warning and move on. + * + * XXX I haven't figured out how to do this through GIO. + * Attempting to set the "access::can-write" attribute via + * g_file_set_attribute() returned G_IO_ERROR_NOT_SUPPORTED + * and the only other possibility I see is "unix::mode", + * which is obviously not portable. + */ + path = g_file_get_path (file); +#ifndef G_OS_WIN32 + if (g_chmod (path, S_IRUSR | S_IRGRP | S_IROTH) < 0) + g_warning ("%s", g_strerror (errno)); +#endif + g_free (path); + + attachment_open_file (file, open_context); + g_object_unref (file); +} + +static void +attachment_open_save_temporary (OpenContext *open_context) +{ + GFile *temp_directory; + gchar *template; + gchar *path; + GError *error = NULL; + + errno = 0; + + /* Save the file to a temporary directory. + * We use a directory so the files can retain their basenames. + * XXX This could trigger a blocking temp directory cleanup. */ + template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ()); + path = e_mkdtemp (template); + g_free (template); + + /* 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; + + temp_directory = g_file_new_for_path (path); + + e_attachment_save_async ( + open_context->attachment, + temp_directory, (GAsyncReadyCallback) + attachment_open_save_finished_cb, open_context); + + g_object_unref (temp_directory); + g_free (path); +} + +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)); + + 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); + + 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; + + 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; + + /* 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 open '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not open 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); +} + +gboolean +e_attachment_open (EAttachment *attachment, + GAppInfo *app_info, + GError **error) +{ + EAsyncClosure *closure; + GAsyncResult *result; + gboolean success; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + closure = e_async_closure_new (); + + e_attachment_open_async (attachment, app_info, e_async_closure_callback, closure); + + result = e_async_closure_wait (closure); + + success = e_attachment_open_finish (attachment, result, error); + + e_async_closure_free (closure); + + return success; +} + +/************************* e_attachment_save_async() *************************/ + +typedef struct _SaveContext SaveContext; + +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); + + 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) +{ + g_object_unref (save_context->attachment); + g_object_unref (save_context->simple); + + if (save_context->directory != NULL) + g_object_unref (save_context->directory); + + if (save_context->destination != NULL) + g_object_unref (save_context->destination); + + 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); +} + +static gboolean +attachment_save_check_for_error (SaveContext *save_context, + GError *error) +{ + GSimpleAsyncResult *simple; + + if (error == NULL) + return FALSE; + + simple = save_context->simple; + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + + attachment_save_context_free (save_context); + + return TRUE; +} + +static GFile * +attachment_save_new_candidate (SaveContext *save_context) +{ + GFile *candidate; + GFileInfo *file_info; + EAttachment *attachment; + const gchar *display_name = NULL; + 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 +attachment_save_write_cb (GOutputStream *output_stream, + GAsyncResult *result, + SaveContext *save_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GInputStream *input_stream; + gssize bytes_written; + GError *error = NULL; + + 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 +attachment_save_read_cb (GInputStream *input_stream, + GAsyncResult *result, + SaveContext *save_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GOutputStream *output_stream; + gssize bytes_read; + GError *error = NULL; + + 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 destination. */ + destination = save_context->destination; + save_context->destination = NULL; + + simple = save_context->simple; + 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 +attachment_save_got_output_stream (SaveContext *save_context) +{ + 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 (CAMEL_MEDIUM (mime_part)); + camel_data_wrapper_decode_to_stream_sync (wrapper, stream, NULL, NULL); + g_object_unref (stream); + + /* Load the buffer into a GMemoryInputStream. + * But watch out for zero length MIME parts. */ + input_stream = g_memory_input_stream_new (); + if (buffer->len > 0) + g_memory_input_stream_add_data ( + G_MEMORY_INPUT_STREAM (input_stream), + 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 +attachment_save_create_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) +{ + EAttachment *attachment; + 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); + + g_file_create_async ( + destination, G_FILE_CREATE_NONE, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_create_cb, + save_context); + + 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; + + /* 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 +attachment_save_query_info_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) +{ + EAttachment *attachment; + 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; + } + + 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; + } + +replace: + g_file_replace_async ( + destination, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, + 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)); + + 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 (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); +} + +GFile * +e_attachment_save_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GFile *destination; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL); + + 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); + + attachment_set_saving (attachment, FALSE); + + return destination; +} + +void +e_attachment_save_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent) +{ + 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 (file != NULL) { + g_object_unref (file); + return; + } + + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + file_info = e_attachment_get_file_info (attachment); + + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; + + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not save '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not save the attachment")); + + dialog = gtk_message_dialog_new_with_markup ( + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "<big><b>%s</b></big>", primary_text); + + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s", error->message); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + g_error_free (error); +} + +gboolean +e_attachment_save (EAttachment *attachment, + GFile *in_destination, + GFile **out_destination, + GError **error) +{ + EAsyncClosure *closure; + GAsyncResult *result; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + g_return_val_if_fail (out_destination != NULL, FALSE); + + closure = e_async_closure_new (); + + e_attachment_save_async (attachment, in_destination, e_async_closure_callback, closure); + + result = e_async_closure_wait (closure); + + *out_destination = e_attachment_save_finish (attachment, result, error); + + e_async_closure_free (closure); + + return *out_destination != NULL; +} |