/* * 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 * * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #include "e-attachment-store.h" #include #include "e-util/e-util.h" #include "e-util/gconf-bridge.h" #define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate)) struct _EAttachmentStorePrivate { GHashTable *attachment_index; gchar *background_filename; gchar *background_options; gchar *current_folder; guint ignore_row_changed : 1; }; enum { PROP_0, PROP_BACKGROUND_FILENAME, PROP_BACKGROUND_OPTIONS, PROP_CURRENT_FOLDER, PROP_NUM_ATTACHMENTS, PROP_NUM_LOADING, PROP_TOTAL_SIZE }; static gpointer parent_class; static const gchar * attachment_store_get_background_filename (EAttachmentStore *store) { return store->priv->background_filename; } static void attachment_store_set_background_filename (EAttachmentStore *store, const gchar *background_filename) { if (background_filename == NULL) background_filename = ""; g_free (store->priv->background_filename); store->priv->background_filename = g_strdup (background_filename); g_object_notify (G_OBJECT (store), "background-filename"); } static const gchar * attachment_store_get_background_options (EAttachmentStore *store) { return store->priv->background_options; } static void attachment_store_set_background_options (EAttachmentStore *store, const gchar *background_options) { if (background_options == NULL) background_options = ""; g_free (store->priv->background_options); store->priv->background_options = g_strdup (background_options); g_object_notify (G_OBJECT (store), "background-options"); } static void attachment_store_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_BACKGROUND_FILENAME: attachment_store_set_background_filename ( E_ATTACHMENT_STORE (object), g_value_get_string (value)); return; case PROP_BACKGROUND_OPTIONS: attachment_store_set_background_options ( E_ATTACHMENT_STORE (object), g_value_get_string (value)); return; case PROP_CURRENT_FOLDER: e_attachment_store_set_current_folder ( E_ATTACHMENT_STORE (object), g_value_get_string (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void attachment_store_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_BACKGROUND_FILENAME: g_value_set_string ( value, attachment_store_get_background_filename ( E_ATTACHMENT_STORE (object))); return; case PROP_BACKGROUND_OPTIONS: g_value_set_string ( value, attachment_store_get_background_options ( E_ATTACHMENT_STORE (object))); return; case PROP_CURRENT_FOLDER: g_value_set_string ( value, e_attachment_store_get_current_folder ( E_ATTACHMENT_STORE (object))); return; 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) { EAttachmentStorePrivate *priv; priv = E_ATTACHMENT_STORE_GET_PRIVATE (object); g_hash_table_remove_all (priv->attachment_index); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (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); g_free (priv->background_filename); g_free (priv->background_options); g_free (priv->current_folder); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (parent_class)->finalize (object); } static void attachment_store_constructed (GObject *object) { EAttachmentStorePrivate *priv; GConfBridge *bridge; const gchar *prop; const gchar *key; priv = E_ATTACHMENT_STORE_GET_PRIVATE (object); bridge = gconf_bridge_get (); prop = "background-filename"; key = "/desktop/gnome/background/picture_filename"; gconf_bridge_bind_property (bridge, key, object, prop); prop = "background-options"; key = "/desktop/gnome/background/picture_options"; gconf_bridge_bind_property (bridge, key, object, prop); } static void attachment_store_class_init (EAttachmentStoreClass *class) { GObjectClass *object_class; parent_class = g_type_class_peek_parent (class); g_type_class_add_private (class, sizeof (EAttachmentStorePrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = attachment_store_set_property; object_class->get_property = attachment_store_get_property; object_class->dispose = attachment_store_dispose; object_class->finalize = attachment_store_finalize; object_class->constructed = attachment_store_constructed; g_object_class_install_property ( object_class, PROP_BACKGROUND_FILENAME, g_param_spec_string ( "background-filename", "Background Filename", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property ( object_class, PROP_BACKGROUND_OPTIONS, g_param_spec_string ( "background-options", "Background Options", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property ( object_class, PROP_CURRENT_FOLDER, g_param_spec_string ( "current-folder", "Current Folder", NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); 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 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_DISPLAY_NAME */ types[column++] = G_TYPE_ICON; /* COLUMN_ICON */ types[column++] = G_TYPE_BOOLEAN; /* COLUMN_LOADING */ types[column++] = G_TYPE_INT; /* COLUMN_PERCENT */ types[column++] = G_TYPE_BOOLEAN; /* COLUMN_SAVING */ types[column++] = G_TYPE_UINT64; /* COLUMN_SIZE */ g_assert (column == E_ATTACHMENT_STORE_NUM_COLUMNS); gtk_list_store_set_column_types ( GTK_LIST_STORE (store), G_N_ELEMENTS (types), types); } GType e_attachment_store_get_type (void) { static GType type = 0; if (G_UNLIKELY (type == 0)) { static const GTypeInfo type_info = { sizeof (EAttachmentStoreClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) attachment_store_class_init, (GClassFinalizeFunc) NULL, NULL, /* class_data */ sizeof (EAttachmentStore), 0, /* n_preallocs */ (GInstanceInitFunc) attachment_store_init, NULL /* value_table */ }; type = g_type_register_static ( GTK_TYPE_LIST_STORE, "EAttachmentStore", &type_info, 0); } return type; } 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; GFile *file; 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); file = e_attachment_get_file (attachment); /* 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_add_to_multipart (EAttachmentStore *store, CamelMultipart *multipart, const gchar *default_charset) { GtkTreeModel *model; GtkTreeIter iter; gboolean valid; g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); g_return_if_fail (CAMEL_MULTIPART (multipart)); 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); /* Skip the attachment if it's still loading. */ if (!e_attachment_get_loading (attachment)) e_attachment_add_to_multipart ( attachment, multipart, default_charset); g_object_unref (attachment); valid = gtk_tree_model_iter_next (model, &iter); } } const gchar * e_attachment_store_get_current_folder (EAttachmentStore *store) { g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL); return store->priv->current_folder; } void e_attachment_store_set_current_folder (EAttachmentStore *store, const gchar *current_folder) { g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); if (current_folder == NULL) current_folder = g_get_home_dir (); g_free (store->priv->current_folder); store->priv->current_folder = g_strdup (current_folder); g_object_notify (G_OBJECT (store), "current-folder"); } 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) { GtkTreeModel *model; GtkTreeIter iter; guint num_loading = 0; gboolean valid; g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0); model = GTK_TREE_MODEL (store); valid = gtk_tree_model_get_iter_first (model, &iter); while (valid) { EAttachment *attachment; gint column_id; column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT; gtk_tree_model_get (model, &iter, column_id, &attachment, -1); if (e_attachment_get_loading (attachment)) num_loading++; g_object_unref (attachment); valid = gtk_tree_model_iter_next (model, &iter); } return num_loading; } goffset e_attachment_store_get_total_size (EAttachmentStore *store) { GtkTreeModel *model; GtkTreeIter iter; goffset total_size = 0; gboolean valid; g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0); model = GTK_TREE_MODEL (store); valid = gtk_tree_model_get_iter_first (model, &iter); while (valid) { EAttachment *attachment; GFileInfo *file_info; gint column_id; column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT; gtk_tree_model_get (model, &iter, column_id, &attachment, -1); file_info = e_attachment_get_file_info (attachment); if (file_info != NULL) total_size += g_file_info_get_size (file_info); g_object_unref (attachment); valid = gtk_tree_model_iter_next (model, &iter); } return total_size; } gint e_attachment_store_run_file_chooser_dialog (EAttachmentStore *store, GtkWidget *dialog) { GtkFileChooser *file_chooser; gint response = GTK_RESPONSE_NONE; const gchar *current_folder; gboolean update_folder; g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), response); g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), response); file_chooser = GTK_FILE_CHOOSER (dialog); current_folder = e_attachment_store_get_current_folder (store); gtk_file_chooser_set_current_folder (file_chooser, current_folder); response = gtk_dialog_run (GTK_DIALOG (dialog)); update_folder = (response == GTK_RESPONSE_ACCEPT) || (response == GTK_RESPONSE_OK) || (response == GTK_RESPONSE_YES) || (response == GTK_RESPONSE_APPLY); if (update_folder) { gchar *folder; folder = gtk_file_chooser_get_current_folder (file_chooser); e_attachment_store_set_current_folder (store, folder); g_free (folder); } return response; } 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 = e_attachment_store_run_file_chooser_dialog (store, 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_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 = e_attachment_store_run_file_chooser_dialog (store, 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_save_list_async() ********************/ typedef struct _SaveContext SaveContext; struct _SaveContext { GSimpleAsyncResult *simple; GList *attachment_list; GError *error; }; static SaveContext * attachment_store_save_context_new (EAttachmentStore *store, GList *attachment_list, GAsyncReadyCallback callback, gpointer user_data) { SaveContext *save_context; GSimpleAsyncResult *simple; simple = g_simple_async_result_new ( G_OBJECT (store), callback, user_data, e_attachment_store_save_list_async); save_context = g_slice_new0 (SaveContext); save_context->simple = simple; save_context->attachment_list = g_list_copy (attachment_list); g_list_foreach ( save_context->attachment_list, (GFunc) g_object_ref, NULL); return save_context; } static void attachment_store_save_context_free (SaveContext *save_context) { /* Do not free the GSimpleAsyncResult. */ /* 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); g_slice_free (SaveContext, save_context); } static void attachment_store_save_list_finished_cb (EAttachment *attachment, GAsyncResult *result, SaveContext *save_context) { GFile *file; GSimpleAsyncResult *simple; GError *error = NULL; file = e_attachment_save_finish (attachment, result, &error); if (file != NULL) g_object_unref (file); /* Remove the attachment from the list. */ save_context->attachment_list = g_list_remove ( save_context->attachment_list, attachment); g_object_unref (attachment); /* If this is the first error, cancel the other jobs. */ if (error != NULL && save_context->error == NULL) { g_propagate_error (&save_context->error, error); g_list_foreach ( save_context->attachment_list, (GFunc) e_attachment_cancel, 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 (save_context->attachment_list != NULL) return; /* Steal the result. */ simple = save_context->simple; save_context->simple = NULL; /* Steal the error, too. */ error = save_context->error; save_context->error = NULL; if (error == NULL) g_simple_async_result_set_op_res_gboolean (simple, TRUE); else { g_simple_async_result_set_from_error (simple, error); g_error_free (error); } g_simple_async_result_complete (simple); attachment_store_save_context_free (save_context); } void e_attachment_store_save_list_async (EAttachmentStore *store, GList *attachment_list, GFile *destination, GAsyncReadyCallback callback, gpointer user_data) { SaveContext *save_context; g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); g_return_if_fail (G_IS_FILE (destination)); g_return_if_fail (callback != NULL); /* Passing an empty list is silly, but we'll handle it. */ if (attachment_list == NULL) { GSimpleAsyncResult *simple; simple = g_simple_async_result_new ( G_OBJECT (store), callback, user_data, e_attachment_store_save_list_async); g_simple_async_result_set_op_res_gboolean (simple, TRUE); g_simple_async_result_complete_in_idle (simple); return; } save_context = attachment_store_save_context_new ( store, attachment_list, callback, user_data); while (attachment_list != NULL) { e_attachment_save_async ( E_ATTACHMENT (attachment_list->data), destination, (GAsyncReadyCallback) attachment_store_save_list_finished_cb, save_context); attachment_list = g_list_next (attachment_list); } } gboolean e_attachment_store_save_list_finish (EAttachmentStore *store, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; gboolean success; g_return_val_if_fail ( g_simple_async_result_is_valid (result, G_OBJECT (store), e_attachment_store_save_list_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); success = g_simple_async_result_get_op_res_gboolean (simple); g_simple_async_result_propagate_error (simple, error); g_object_unref (simple); return success; }