diff options
Diffstat (limited to 'e-util/e-attachment-store.c')
-rw-r--r-- | e-util/e-attachment-store.c | 1280 |
1 files changed, 1280 insertions, 0 deletions
diff --git a/e-util/e-attachment-store.c b/e-util/e-attachment-store.c new file mode 100644 index 0000000000..f434f5e81c --- /dev/null +++ b/e-util/e-attachment-store.c @@ -0,0 +1,1280 @@ +/* + * e-attachment-store.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-store.h" + +#include <errno.h> +#include <glib/gi18n.h> + +#include "e-mktemp.h" + +#define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate)) + +struct _EAttachmentStorePrivate { + GHashTable *attachment_index; + + guint ignore_row_changed : 1; +}; + +enum { + PROP_0, + PROP_NUM_ATTACHMENTS, + PROP_NUM_LOADING, + PROP_TOTAL_SIZE +}; + +G_DEFINE_TYPE ( + EAttachmentStore, + e_attachment_store, + GTK_TYPE_LIST_STORE) + +static void +attachment_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_NUM_ATTACHMENTS: + g_value_set_uint ( + value, + e_attachment_store_get_num_attachments ( + E_ATTACHMENT_STORE (object))); + return; + + case PROP_NUM_LOADING: + g_value_set_uint ( + value, + e_attachment_store_get_num_loading ( + E_ATTACHMENT_STORE (object))); + return; + + case PROP_TOTAL_SIZE: + g_value_set_uint64 ( + value, + e_attachment_store_get_total_size ( + E_ATTACHMENT_STORE (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +attachment_store_dispose (GObject *object) +{ + e_attachment_store_remove_all (E_ATTACHMENT_STORE (object)); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_attachment_store_parent_class)->dispose (object); +} + +static void +attachment_store_finalize (GObject *object) +{ + EAttachmentStorePrivate *priv; + + priv = E_ATTACHMENT_STORE_GET_PRIVATE (object); + + g_hash_table_destroy (priv->attachment_index); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_attachment_store_parent_class)->finalize (object); +} + +static void +e_attachment_store_class_init (EAttachmentStoreClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (EAttachmentStorePrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->get_property = attachment_store_get_property; + object_class->dispose = attachment_store_dispose; + object_class->finalize = attachment_store_finalize; + + g_object_class_install_property ( + object_class, + PROP_NUM_ATTACHMENTS, + g_param_spec_uint ( + "num-attachments", + "Num Attachments", + NULL, + 0, + G_MAXUINT, + 0, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_NUM_LOADING, + g_param_spec_uint ( + "num-loading", + "Num Loading", + NULL, + 0, + G_MAXUINT, + 0, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_TOTAL_SIZE, + g_param_spec_uint64 ( + "total-size", + "Total Size", + NULL, + 0, + G_MAXUINT64, + 0, + G_PARAM_READABLE)); +} + +static void +e_attachment_store_init (EAttachmentStore *store) +{ + GType types[E_ATTACHMENT_STORE_NUM_COLUMNS]; + GHashTable *attachment_index; + gint column = 0; + + attachment_index = g_hash_table_new_full ( + g_direct_hash, g_direct_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) gtk_tree_row_reference_free); + + store->priv = E_ATTACHMENT_STORE_GET_PRIVATE (store); + store->priv->attachment_index = attachment_index; + + types[column++] = E_TYPE_ATTACHMENT; /* COLUMN_ATTACHMENT */ + types[column++] = G_TYPE_STRING; /* COLUMN_CAPTION */ + types[column++] = G_TYPE_STRING; /* COLUMN_CONTENT_TYPE */ + types[column++] = G_TYPE_STRING; /* COLUMN_DESCRIPTION */ + types[column++] = G_TYPE_ICON; /* COLUMN_ICON */ + types[column++] = G_TYPE_BOOLEAN; /* COLUMN_LOADING */ + types[column++] = G_TYPE_INT; /* COLUMN_PERCENT */ + types[column++] = G_TYPE_BOOLEAN; /* COLUMN_SAVING */ + types[column++] = G_TYPE_UINT64; /* COLUMN_SIZE */ + + g_assert (column == E_ATTACHMENT_STORE_NUM_COLUMNS); + + gtk_list_store_set_column_types ( + GTK_LIST_STORE (store), G_N_ELEMENTS (types), types); +} + +GtkTreeModel * +e_attachment_store_new (void) +{ + return g_object_new (E_TYPE_ATTACHMENT_STORE, NULL); +} + +void +e_attachment_store_add_attachment (EAttachmentStore *store, + EAttachment *attachment) +{ + GtkTreeRowReference *reference; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + + g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + gtk_list_store_append (GTK_LIST_STORE (store), &iter); + + gtk_list_store_set ( + GTK_LIST_STORE (store), &iter, + E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, attachment, -1); + + model = GTK_TREE_MODEL (store); + path = gtk_tree_model_get_path (model, &iter); + reference = gtk_tree_row_reference_new (model, path); + gtk_tree_path_free (path); + + g_hash_table_insert ( + store->priv->attachment_index, + g_object_ref (attachment), reference); + + /* This lets the attachment tell us when to update. */ + e_attachment_set_reference (attachment, reference); + + g_object_freeze_notify (G_OBJECT (store)); + g_object_notify (G_OBJECT (store), "num-attachments"); + g_object_notify (G_OBJECT (store), "total-size"); + g_object_thaw_notify (G_OBJECT (store)); +} + +gboolean +e_attachment_store_remove_attachment (EAttachmentStore *store, + EAttachment *attachment) +{ + GtkTreeRowReference *reference; + GHashTable *hash_table; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE); + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + hash_table = store->priv->attachment_index; + reference = g_hash_table_lookup (hash_table, attachment); + + if (reference == NULL) + return FALSE; + + if (!gtk_tree_row_reference_valid (reference)) { + g_hash_table_remove (hash_table, attachment); + return FALSE; + } + + e_attachment_cancel (attachment); + e_attachment_set_reference (attachment, NULL); + + model = gtk_tree_row_reference_get_model (reference); + path = gtk_tree_row_reference_get_path (reference); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_list_store_remove (GTK_LIST_STORE (store), &iter); + g_hash_table_remove (hash_table, attachment); + + g_object_freeze_notify (G_OBJECT (store)); + g_object_notify (G_OBJECT (store), "num-attachments"); + g_object_notify (G_OBJECT (store), "total-size"); + g_object_thaw_notify (G_OBJECT (store)); + + return TRUE; +} + +void +e_attachment_store_remove_all (EAttachmentStore *store) +{ + GList *list, *iter; + + g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); + + if (!g_hash_table_size (store->priv->attachment_index)) + return; + + g_object_freeze_notify (G_OBJECT (store)); + + list = e_attachment_store_get_attachments (store); + for (iter = list; iter; iter = iter->next) { + EAttachment *attachment = iter->data; + + e_attachment_cancel (attachment); + g_hash_table_remove (store->priv->attachment_index, iter->data); + } + + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); + + gtk_list_store_clear (GTK_LIST_STORE (store)); + + g_object_notify (G_OBJECT (store), "num-attachments"); + g_object_notify (G_OBJECT (store), "total-size"); + g_object_thaw_notify (G_OBJECT (store)); +} + +void +e_attachment_store_add_to_multipart (EAttachmentStore *store, + CamelMultipart *multipart, + const gchar *default_charset) +{ + GList *list, *iter; + + g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); + g_return_if_fail (CAMEL_MULTIPART (multipart)); + + list = e_attachment_store_get_attachments (store); + + for (iter = list; iter != NULL; iter = iter->next) { + EAttachment *attachment = iter->data; + + /* Skip the attachment if it's still loading. */ + if (!e_attachment_get_loading (attachment)) + e_attachment_add_to_multipart ( + attachment, multipart, default_charset); + } + + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); +} + +GList * +e_attachment_store_get_attachments (EAttachmentStore *store) +{ + GList *list = NULL; + GtkTreeModel *model; + GtkTreeIter iter; + gboolean valid; + + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL); + + model = GTK_TREE_MODEL (store); + valid = gtk_tree_model_get_iter_first (model, &iter); + + while (valid) { + EAttachment *attachment; + gint column_id; + + column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT; + gtk_tree_model_get (model, &iter, column_id, &attachment, -1); + + list = g_list_prepend (list, attachment); + + valid = gtk_tree_model_iter_next (model, &iter); + } + + return g_list_reverse (list); +} + +guint +e_attachment_store_get_num_attachments (EAttachmentStore *store) +{ + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0); + + return g_hash_table_size (store->priv->attachment_index); +} + +guint +e_attachment_store_get_num_loading (EAttachmentStore *store) +{ + GList *list, *iter; + guint num_loading = 0; + + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0); + + list = e_attachment_store_get_attachments (store); + + for (iter = list; iter != NULL; iter = iter->next) { + EAttachment *attachment = iter->data; + + if (e_attachment_get_loading (attachment)) + num_loading++; + } + + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); + + return num_loading; +} + +goffset +e_attachment_store_get_total_size (EAttachmentStore *store) +{ + GList *list, *iter; + goffset total_size = 0; + + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0); + + list = e_attachment_store_get_attachments (store); + + for (iter = list; iter != NULL; iter = iter->next) { + EAttachment *attachment = iter->data; + GFileInfo *file_info; + + file_info = e_attachment_get_file_info (attachment); + if (file_info != NULL) + total_size += g_file_info_get_size (file_info); + } + + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); + + return total_size; +} + +void +e_attachment_store_run_load_dialog (EAttachmentStore *store, + GtkWindow *parent) +{ + GtkFileChooser *file_chooser; + GtkWidget *dialog; + GtkWidget *option; + GSList *files, *iter; + const gchar *disposition; + gboolean active; + gint response; + + g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); + g_return_if_fail (GTK_IS_WINDOW (parent)); + + dialog = gtk_file_chooser_dialog_new ( + _("Add Attachment"), parent, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + _("A_ttach"), GTK_RESPONSE_OK, NULL); + + file_chooser = GTK_FILE_CHOOSER (dialog); + gtk_file_chooser_set_local_only (file_chooser, FALSE); + gtk_file_chooser_set_select_multiple (file_chooser, TRUE); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment"); + + option = gtk_check_button_new_with_mnemonic ( + _("_Suggest automatic display of attachment")); + gtk_file_chooser_set_extra_widget (file_chooser, option); + gtk_widget_show (option); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + if (response != GTK_RESPONSE_OK) + goto exit; + + files = gtk_file_chooser_get_files (file_chooser); + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (option)); + disposition = active ? "inline" : "attachment"; + + for (iter = files; iter != NULL; iter = g_slist_next (iter)) { + EAttachment *attachment; + GFile *file = iter->data; + + attachment = e_attachment_new (); + e_attachment_set_file (attachment, file); + e_attachment_set_disposition (attachment, disposition); + e_attachment_store_add_attachment (store, attachment); + e_attachment_load_async ( + attachment, (GAsyncReadyCallback) + e_attachment_load_handle_error, parent); + g_object_unref (attachment); + } + + g_slist_foreach (files, (GFunc) g_object_unref, NULL); + g_slist_free (files); + +exit: + gtk_widget_destroy (dialog); +} + +GFile * +e_attachment_store_run_save_dialog (EAttachmentStore *store, + GList *attachment_list, + GtkWindow *parent) +{ + GtkFileChooser *file_chooser; + GtkFileChooserAction action; + GtkWidget *dialog; + GFile *destination; + const gchar *title; + gint response; + guint length; + + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL); + + length = g_list_length (attachment_list); + + if (length == 0) + return NULL; + + title = ngettext ("Save Attachment", "Save Attachments", length); + + if (length == 1) + action = GTK_FILE_CHOOSER_ACTION_SAVE; + else + action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + + dialog = gtk_file_chooser_dialog_new ( + title, parent, action, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL); + + file_chooser = GTK_FILE_CHOOSER (dialog); + gtk_file_chooser_set_local_only (file_chooser, FALSE); + gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment"); + + if (action == GTK_FILE_CHOOSER_ACTION_SAVE) { + EAttachment *attachment; + GFileInfo *file_info; + const gchar *name = NULL; + + attachment = attachment_list->data; + file_info = e_attachment_get_file_info (attachment); + if (file_info != NULL) + name = g_file_info_get_display_name (file_info); + if (name == NULL) + /* Translators: Default attachment filename. */ + name = _("attachment.dat"); + gtk_file_chooser_set_current_name (file_chooser, name); + } + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + if (response == GTK_RESPONSE_OK) + destination = gtk_file_chooser_get_file (file_chooser); + else + destination = NULL; + + gtk_widget_destroy (dialog); + + return destination; +} + +/******************** e_attachment_store_get_uris_async() ********************/ + +typedef struct _UriContext UriContext; + +struct _UriContext { + GSimpleAsyncResult *simple; + GList *attachment_list; + GError *error; + gchar **uris; + gint index; +}; + +static UriContext * +attachment_store_uri_context_new (EAttachmentStore *store, + GList *attachment_list, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UriContext *uri_context; + GSimpleAsyncResult *simple; + guint length; + gchar **uris; + + simple = g_simple_async_result_new ( + G_OBJECT (store), callback, user_data, + e_attachment_store_get_uris_async); + + /* Add one for NULL terminator. */ + length = g_list_length (attachment_list) + 1; + uris = g_malloc0 (sizeof (gchar *) * length); + + uri_context = g_slice_new0 (UriContext); + uri_context->simple = simple; + uri_context->attachment_list = g_list_copy (attachment_list); + uri_context->uris = uris; + + g_list_foreach ( + uri_context->attachment_list, + (GFunc) g_object_ref, NULL); + + return uri_context; +} + +static void +attachment_store_uri_context_free (UriContext *uri_context) +{ + g_object_unref (uri_context->simple); + + /* The attachment list should be empty now. */ + g_warn_if_fail (uri_context->attachment_list == NULL); + + /* So should the error. */ + g_warn_if_fail (uri_context->error == NULL); + + g_strfreev (uri_context->uris); + + g_slice_free (UriContext, uri_context); +} + +static void +attachment_store_get_uris_save_cb (EAttachment *attachment, + GAsyncResult *result, + UriContext *uri_context) +{ + GSimpleAsyncResult *simple; + GFile *file; + gchar **uris; + gchar *uri; + GError *error = NULL; + + file = e_attachment_save_finish (attachment, result, &error); + + /* Remove the attachment from the list. */ + uri_context->attachment_list = g_list_remove ( + uri_context->attachment_list, attachment); + g_object_unref (attachment); + + if (file != NULL) { + uri = g_file_get_uri (file); + uri_context->uris[uri_context->index++] = uri; + g_object_unref (file); + + } else if (error != NULL) { + /* If this is the first error, cancel the other jobs. */ + if (uri_context->error == NULL) { + g_propagate_error (&uri_context->error, error); + g_list_foreach ( + uri_context->attachment_list, + (GFunc) e_attachment_cancel, NULL); + error = NULL; + + /* Otherwise, we can only report back one error. So if + * this is something other than cancellation, dump it to + * the terminal. */ + } else if (!g_error_matches ( + error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("%s", error->message); + } + + if (error != NULL) + g_error_free (error); + + /* If there's still jobs running, let them finish. */ + if (uri_context->attachment_list != NULL) + return; + + /* Steal the URI list. */ + uris = uri_context->uris; + uri_context->uris = NULL; + + /* And the error. */ + error = uri_context->error; + uri_context->error = NULL; + + simple = uri_context->simple; + + if (error == NULL) + g_simple_async_result_set_op_res_gpointer (simple, uris, NULL); + else + g_simple_async_result_take_error (simple, error); + + g_simple_async_result_complete (simple); + + attachment_store_uri_context_free (uri_context); +} + +void +e_attachment_store_get_uris_async (EAttachmentStore *store, + GList *attachment_list, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFile *temp_directory; + UriContext *uri_context; + GList *iter, *trash = NULL; + gchar *template; + gchar *path; + + g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); + + uri_context = attachment_store_uri_context_new ( + store, attachment_list, callback, user_data); + + /* Grab the copied attachment list. */ + attachment_list = uri_context->attachment_list; + + /* First scan the list for attachments with a GFile. */ + for (iter = attachment_list; iter != NULL; iter = iter->next) { + EAttachment *attachment = iter->data; + GFile *file; + gchar *uri; + + file = e_attachment_get_file (attachment); + if (file == NULL) + continue; + + uri = g_file_get_uri (file); + uri_context->uris[uri_context->index++] = uri; + + /* Mark the list node for deletion. */ + trash = g_list_prepend (trash, iter); + g_object_unref (attachment); + } + + /* Expunge the list. */ + for (iter = trash; iter != NULL; iter = iter->next) { + GList *link = iter->data; + attachment_list = g_list_delete_link (attachment_list, link); + } + g_list_free (trash); + + uri_context->attachment_list = attachment_list; + + /* If we got them all then we're done. */ + if (attachment_list == NULL) { + GSimpleAsyncResult *simple; + gchar **uris; + + /* Steal the URI list. */ + uris = uri_context->uris; + uri_context->uris = NULL; + + simple = uri_context->simple; + g_simple_async_result_set_op_res_gpointer (simple, uris, NULL); + g_simple_async_result_complete (simple); + + attachment_store_uri_context_free (uri_context); + return; + } + + /* Any remaining attachments in the list should have MIME parts + * only, so we need to save them all 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) { + GSimpleAsyncResult *simple; + + simple = uri_context->simple; + g_simple_async_result_set_error ( + simple, G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); + g_simple_async_result_complete (simple); + + attachment_store_uri_context_free (uri_context); + return; + } + + temp_directory = g_file_new_for_path (path); + + for (iter = attachment_list; iter != NULL; iter = iter->next) + e_attachment_save_async ( + E_ATTACHMENT (iter->data), + temp_directory, (GAsyncReadyCallback) + attachment_store_get_uris_save_cb, + uri_context); + + g_object_unref (temp_directory); + g_free (path); +} + +gchar ** +e_attachment_store_get_uris_finish (EAttachmentStore *store, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gchar **uris; + + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL); + + simple = G_SIMPLE_ASYNC_RESULT (result); + uris = g_simple_async_result_get_op_res_gpointer (simple); + g_simple_async_result_propagate_error (simple, error); + + return uris; +} + +/********************** e_attachment_store_load_async() **********************/ + +typedef struct _LoadContext LoadContext; + +struct _LoadContext { + GSimpleAsyncResult *simple; + GList *attachment_list; + GError *error; +}; + +static LoadContext * +attachment_store_load_context_new (EAttachmentStore *store, + GList *attachment_list, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadContext *load_context; + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new ( + G_OBJECT (store), callback, user_data, + e_attachment_store_load_async); + + load_context = g_slice_new0 (LoadContext); + load_context->simple = simple; + load_context->attachment_list = g_list_copy (attachment_list); + + g_list_foreach ( + load_context->attachment_list, + (GFunc) g_object_ref, NULL); + + return load_context; +} + +static void +attachment_store_load_context_free (LoadContext *load_context) +{ + g_object_unref (load_context->simple); + + /* The attachment list should be empty now. */ + g_warn_if_fail (load_context->attachment_list == NULL); + + /* So should the error. */ + g_warn_if_fail (load_context->error == NULL); + + g_slice_free (LoadContext, load_context); +} + +static void +attachment_store_load_ready_cb (EAttachment *attachment, + GAsyncResult *result, + LoadContext *load_context) +{ + GSimpleAsyncResult *simple; + GError *error = NULL; + + e_attachment_load_finish (attachment, result, &error); + + /* Remove the attachment from the list. */ + load_context->attachment_list = g_list_remove ( + load_context->attachment_list, attachment); + g_object_unref (attachment); + + if (error != NULL) { + /* If this is the first error, cancel the other jobs. */ + if (load_context->error == NULL) { + g_propagate_error (&load_context->error, error); + g_list_foreach ( + load_context->attachment_list, + (GFunc) e_attachment_cancel, NULL); + error = NULL; + + /* Otherwise, we can only report back one error. So if + * this is something other than cancellation, dump it to + * the terminal. */ + } else if (!g_error_matches ( + error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("%s", error->message); + } + + if (error != NULL) + g_error_free (error); + + /* If there's still jobs running, let them finish. */ + if (load_context->attachment_list != NULL) + return; + + /* Steal the error. */ + error = load_context->error; + load_context->error = NULL; + + simple = load_context->simple; + + if (error == NULL) + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + else + g_simple_async_result_take_error (simple, error); + + g_simple_async_result_complete (simple); + + attachment_store_load_context_free (load_context); +} + +void +e_attachment_store_load_async (EAttachmentStore *store, + GList *attachment_list, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadContext *load_context; + GList *iter; + + g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); + + load_context = attachment_store_load_context_new ( + store, attachment_list, callback, user_data); + + if (attachment_list == NULL) { + GSimpleAsyncResult *simple; + + simple = load_context->simple; + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + g_simple_async_result_complete (simple); + + attachment_store_load_context_free (load_context); + return; + } + + for (iter = attachment_list; iter != NULL; iter = iter->next) { + EAttachment *attachment = E_ATTACHMENT (iter->data); + + e_attachment_store_add_attachment (store, attachment); + + e_attachment_load_async ( + attachment, (GAsyncReadyCallback) + attachment_store_load_ready_cb, + load_context); + } +} + +gboolean +e_attachment_store_load_finish (EAttachmentStore *store, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gboolean success; + + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 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; +} + +/********************** e_attachment_store_save_async() **********************/ + +typedef struct _SaveContext SaveContext; + +struct _SaveContext { + GSimpleAsyncResult *simple; + GFile *destination; + gchar *filename_prefix; + GFile *fresh_directory; + GFile *trash_directory; + GList *attachment_list; + GError *error; + gchar **uris; + gint index; +}; + +static SaveContext * +attachment_store_save_context_new (EAttachmentStore *store, + GFile *destination, + const gchar *filename_prefix, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SaveContext *save_context; + GSimpleAsyncResult *simple; + GList *attachment_list; + guint length; + gchar **uris; + + simple = g_simple_async_result_new ( + G_OBJECT (store), callback, user_data, + e_attachment_store_save_async); + + attachment_list = e_attachment_store_get_attachments (store); + + /* Add one for NULL terminator. */ + length = g_list_length (attachment_list) + 1; + uris = g_malloc0 (sizeof (gchar *) * length); + + save_context = g_slice_new0 (SaveContext); + save_context->simple = simple; + save_context->destination = g_object_ref (destination); + save_context->filename_prefix = g_strdup (filename_prefix); + save_context->attachment_list = attachment_list; + save_context->uris = uris; + + return save_context; +} + +static void +attachment_store_save_context_free (SaveContext *save_context) +{ + g_object_unref (save_context->simple); + + /* The attachment list should be empty now. */ + g_warn_if_fail (save_context->attachment_list == NULL); + + /* So should the error. */ + g_warn_if_fail (save_context->error == NULL); + + if (save_context->destination) { + g_object_unref (save_context->destination); + save_context->destination = NULL; + } + + g_free (save_context->filename_prefix); + save_context->filename_prefix = NULL; + + if (save_context->fresh_directory) { + g_object_unref (save_context->fresh_directory); + save_context->fresh_directory = NULL; + } + + if (save_context->trash_directory) { + g_object_unref (save_context->trash_directory); + save_context->trash_directory = NULL; + } + + g_strfreev (save_context->uris); + + g_slice_free (SaveContext, save_context); +} + +static void +attachment_store_move_file (SaveContext *save_context, + GFile *source, + GFile *destination, + GError **error) +{ + gchar *tmpl; + gchar *path; + + g_return_if_fail (save_context != NULL); + g_return_if_fail (source != NULL); + g_return_if_fail (destination != NULL); + g_return_if_fail (error != NULL); + + /* Attachments are all saved to a temporary directory. + * Now we need to move the existing destination directory + * out of the way (if it exists). Instead of testing for + * existence we'll just attempt the move and ignore any + * G_IO_ERROR_NOT_FOUND errors. */ + + /* First, however, we need another temporary directory to + * move the existing destination directory to. Note we're + * not actually creating the directory yet, just picking a + * name for it. The usual raciness with this approach + * applies here (read up on mktemp(3)), but worst case is + * we get a spurious G_IO_ERROR_WOULD_MERGE error and the + * user has to try saving attachments again. */ + tmpl = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ()); + path = e_mktemp (tmpl); + g_free (tmpl); + + save_context->trash_directory = g_file_new_for_path (path); + g_free (path); + + /* XXX No asynchronous move operation in GIO? */ + g_file_move ( + destination, + save_context->trash_directory, + G_FILE_COPY_NONE, NULL, NULL, NULL, error); + + if (*error != NULL && !g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + return; + + g_clear_error (error); + + /* Now we can move the file from the temporary directory + * to the user-specified destination. */ + g_file_move ( + source, + destination, + G_FILE_COPY_NONE, NULL, NULL, NULL, error); +} + +static void +attachment_store_save_cb (EAttachment *attachment, + GAsyncResult *result, + SaveContext *save_context) +{ + GSimpleAsyncResult *simple; + GFile *file; + gchar **uris; + GError *error = NULL; + + file = e_attachment_save_finish (attachment, result, &error); + + /* Remove the attachment from the list. */ + save_context->attachment_list = g_list_remove ( + save_context->attachment_list, attachment); + g_object_unref (attachment); + + if (file != NULL) { + /* Assemble the file's final URI from its basename. */ + gchar *basename; + gchar *uri; + GFile *source = NULL, *destination = NULL; + + basename = g_file_get_basename (file); + g_object_unref (file); + + source = g_file_get_child (save_context->fresh_directory, basename); + + if (save_context->filename_prefix && *save_context->filename_prefix) { + gchar *tmp = basename; + + basename = g_strconcat (save_context->filename_prefix, basename, NULL); + g_free (tmp); + } + + file = save_context->destination; + destination = g_file_get_child (file, basename); + uri = g_file_get_uri (destination); + + /* move them file-by-file */ + attachment_store_move_file (save_context, source, destination, &error); + + if (!error) + save_context->uris[save_context->index++] = uri; + + g_object_unref (source); + g_object_unref (destination); + } + + if (error != NULL) { + /* If this is the first error, cancel the other jobs. */ + if (save_context->error == NULL) { + g_propagate_error (&save_context->error, error); + g_list_foreach ( + save_context->attachment_list, + (GFunc) e_attachment_cancel, NULL); + error = NULL; + + /* Otherwise, we can only report back one error. So if + * this is something other than cancellation, dump it to + * the terminal. */ + } else if (!g_error_matches ( + error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("%s", error->message); + } + + g_clear_error (&error); + + /* If there's still jobs running, let them finish. */ + if (save_context->attachment_list != NULL) + return; + + /* If an error occurred while saving, we're done. */ + if (save_context->error != NULL) { + + /* Steal the error. */ + error = save_context->error; + save_context->error = NULL; + + simple = save_context->simple; + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + + attachment_store_save_context_free (save_context); + return; + } + + if (error != NULL) { + simple = save_context->simple; + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + + attachment_store_save_context_free (save_context); + return; + } + + /* clean-up left directory */ + g_file_delete (save_context->fresh_directory, NULL, NULL); + + /* And the URI list. */ + uris = save_context->uris; + save_context->uris = NULL; + + simple = save_context->simple; + g_simple_async_result_set_op_res_gpointer (simple, uris, NULL); + g_simple_async_result_complete (simple); + + attachment_store_save_context_free (save_context); +} +/* + * @filename_prefix: prefix to use for a file name; can be %NULL for none + **/ +void +e_attachment_store_save_async (EAttachmentStore *store, + GFile *destination, + const gchar *filename_prefix, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SaveContext *save_context; + GList *attachment_list, *iter; + GFile *temp_directory; + gchar *template; + gchar *path; + + g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); + g_return_if_fail (G_IS_FILE (destination)); + + save_context = attachment_store_save_context_new ( + store, destination, filename_prefix, callback, user_data); + + attachment_list = save_context->attachment_list; + + /* Deal with an empty attachment store. The caller will get + * an empty NULL-terminated list as opposed to NULL, to help + * distinguish it from an error. */ + if (attachment_list == NULL) { + GSimpleAsyncResult *simple; + gchar **uris; + + /* Steal the URI list. */ + uris = save_context->uris; + save_context->uris = NULL; + + simple = save_context->simple; + g_simple_async_result_set_op_res_gpointer (simple, uris, NULL); + g_simple_async_result_complete (simple); + + attachment_store_save_context_free (save_context); + return; + } + + /* Save all attachments to a temporary directory, which we'll + * then move to its proper location. We use a directory so + * 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) { + GSimpleAsyncResult *simple; + + simple = save_context->simple; + g_simple_async_result_set_error ( + simple, G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); + g_simple_async_result_complete (simple); + + attachment_store_save_context_free (save_context); + return; + } + + temp_directory = g_file_new_for_path (path); + save_context->fresh_directory = temp_directory; + g_free (path); + + for (iter = attachment_list; iter != NULL; iter = iter->next) + e_attachment_save_async ( + E_ATTACHMENT (iter->data), + temp_directory, (GAsyncReadyCallback) + attachment_store_save_cb, save_context); +} + +gchar ** +e_attachment_store_save_finish (EAttachmentStore *store, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gchar **uris; + + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL); + + simple = G_SIMPLE_ASYNC_RESULT (result); + uris = g_simple_async_result_get_op_res_gpointer (simple); + g_simple_async_result_propagate_error (simple, error); + + return uris; +} |