aboutsummaryrefslogblamecommitdiffstats
path: root/widgets/misc/e-attachment-store.c
blob: bd6cb1848110d67d180b00e083acb57414386303 (plain) (tree)



































                                                                               















                                     
                         











































































                                                                           
                                                          











































                                                                            
































































                                                                     
                                                 























                                                                             
                                                          













































































                                                                          
                                      

                                          
                                                                    








































































                                                                       
                    
                    
                    




                                    


                         

                       





                                                      






                                                                      

                                                        







                                                                            
                                           

                                                                
                                                  

                                                  



                                                                
 


                                                              
 




















                                                                         

                 

                                      









                                                                            



                                                           




                                                     
                              

































































                                                                           
                                 
                                   

                                      
















































                                                                       
                                                                    

                                                                         


                                                                    


















































































                                                                         
                                                     

























































































































                                                                              
                                                            












































































































































































                                                                               
/*
 * 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)
 *
 */

#include "e-attachment-store.h"

#include <glib/gi18n.h>

#include "e-util/e-util.h"
#include "e-util/gconf-bridge.h"

#include "e-file-activity.h"

#define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate))

#define DEFAULT_ICON_NAME   "mail-attachment"

struct _EAttachmentStorePrivate {
    GHashTable *activity_index;
    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
};

enum {
    NEW_ACTIVITY,
    LAST_SIGNAL
};

static gpointer parent_class;
static guint signals[LAST_SIGNAL];

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_remove_activity (EAttachmentStore *store,
                                  EActivity *activity)
{
    GtkTreeRowReference *reference;
    GHashTable *hash_table;

    hash_table = store->priv->activity_index;
    reference = g_hash_table_lookup (hash_table, activity);

    if (gtk_tree_row_reference_valid (reference)) {
        GtkTreeModel *model;
        GtkTreePath *path;
        GtkTreeIter iter;

        model = gtk_tree_row_reference_get_model (reference);
        path = gtk_tree_row_reference_get_path (reference);
        gtk_tree_model_get_iter (model, &iter, path);
        gtk_tree_path_free (path);

        gtk_list_store_set (
            GTK_LIST_STORE (store), &iter,
            E_ATTACHMENT_STORE_COLUMN_ACTIVITY, NULL, -1);
    }

    g_hash_table_remove (hash_table, activity);

    g_object_notify (G_OBJECT (store), "num-loading");
}

static void
attachment_store_copy_ready (GFile *source,
                             GAsyncResult *result,
                             GtkTreeRowReference *reference)
{
    EAttachmentStore *store;
    EAttachment *attachment;
    EActivity *activity;
    GFile *destination;
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;
    gboolean valid;
    GError *error = NULL;

    model = gtk_tree_row_reference_get_model (reference);
    path = gtk_tree_row_reference_get_path (reference);
    valid = gtk_tree_model_get_iter (model, &iter, path);
    gtk_tree_path_free (path);
    g_return_if_fail (valid);

    gtk_tree_model_get (
        model, &iter,
        E_ATTACHMENT_STORE_COLUMN_ACTIVITY, &activity,
        E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, &attachment, -1);

    gtk_tree_row_reference_free (reference);

    store = E_ATTACHMENT_STORE (model);

    if (!g_file_copy_finish (source, result, &error))
        goto fail;

    gtk_list_store_set (
        GTK_LIST_STORE (store), &iter,
        E_ATTACHMENT_STORE_COLUMN_ACTIVITY, NULL, -1);

    destination = e_file_activity_get_file (E_FILE_ACTIVITY (activity));
    e_attachment_set_file (attachment, destination);

    e_activity_complete (activity);

    g_object_unref (attachment);
    g_object_unref (activity);

    return;

fail:
    e_attachment_store_remove_attachment (store, attachment);

    g_object_unref (attachment);
    g_object_unref (activity);

    /* XXX Do something more useful with the error. */
    g_warning ("%s", error->message);
    g_error_free (error);
}

static void
attachment_store_copy_async (EAttachmentStore *store,
                             EAttachment *attachment)
{
    EActivity *activity;
    GCancellable *cancellable;
    GtkTreeRowReference *reference;
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;
    GHashTable *hash_table;
    GFile *destination;
    GFile *source;
    gboolean valid;
    gchar *filename;
    gchar *uri;
    gint fd;
    GError *error = NULL;

    hash_table = store->priv->attachment_index;
    reference = g_hash_table_lookup (hash_table, attachment);
    g_return_if_fail (reference != NULL);

    fd = e_file_open_tmp (&filename, &error);
    if (error != NULL)
        goto fail;

    source = e_attachment_get_file (attachment);
    destination = g_file_new_for_path (filename);

    g_free (filename);
    close (fd);

    model = gtk_tree_row_reference_get_model (reference);
    path = gtk_tree_row_reference_get_path (reference);
    valid = gtk_tree_model_get_iter (model, &iter, path);
    gtk_tree_path_free (path);
    g_return_if_fail (valid);

    uri = g_file_get_uri (source);
    activity = e_file_activity_newv (_("Downloading '%s'"), uri);
    g_free (uri);

    gtk_list_store_set (
        GTK_LIST_STORE (store), &iter,
        E_ATTACHMENT_STORE_COLUMN_ACTIVITY, activity, -1);

    reference = gtk_tree_row_reference_copy (reference);

    hash_table = store->priv->activity_index;
    g_hash_table_insert (hash_table, g_object_ref (activity), reference);

    g_signal_connect_swapped (
        activity, "cancelled",
        G_CALLBACK (attachment_store_remove_activity), store);

    g_signal_connect_swapped (
        activity, "completed",
        G_CALLBACK (attachment_store_remove_activity), store);

    reference = gtk_tree_row_reference_copy (reference);

    cancellable = e_file_activity_get_cancellable (
        E_FILE_ACTIVITY (activity));

    g_file_copy_async (
        source, destination, G_FILE_COPY_OVERWRITE,
        G_PRIORITY_DEFAULT, cancellable, (GFileProgressCallback)
        e_file_activity_progress, activity, (GAsyncReadyCallback)
        attachment_store_copy_ready, reference);

    e_file_activity_set_file (E_FILE_ACTIVITY (activity), destination);
    g_signal_emit (store, signals[NEW_ACTIVITY], 0, activity);

    g_object_notify (G_OBJECT (store), "num-loading");

    g_object_unref (activity);
    g_object_unref (destination);

    return;

fail:
    e_attachment_store_remove_attachment (store, attachment);

    /* XXX Do something more useful with the error. */
    g_warning ("%s", error->message);
    g_error_free (error);
}

static void
attachment_store_set_property (GObject *object,
                               guint property_id,
                               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->activity_index);
    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->activity_index);
    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_row_changed (GtkTreeModel *model,
                              GtkTreePath *path,
                              GtkTreeIter *iter)
{
    EAttachmentStorePrivate *priv;
    EAttachment *attachment;
    GFile *file;
    GIcon *icon;
    GList *list;
    const gchar *content_type;
    const gchar *display_name;
    const gchar *thumbnail_path;
    gchar *content_description;
    gchar *display_size;
    gchar *caption;
    gboolean loading;
    gboolean saving;
    guint64 size;
    gint column_id;

    priv = E_ATTACHMENT_STORE_GET_PRIVATE (model);

    if (priv->ignore_row_changed)
        return;

    column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
    gtk_tree_model_get (model, iter, column_id, &attachment, -1);
    g_return_if_fail (E_IS_ATTACHMENT (attachment));

    content_type = e_attachment_get_content_type (attachment);
    display_name = e_attachment_get_display_name (attachment);
    thumbnail_path = e_attachment_get_thumbnail_path (attachment);
    loading = e_attachment_get_loading (attachment);
    saving = e_attachment_get_saving (attachment);
    icon = e_attachment_get_icon (attachment);
    size = e_attachment_get_size (attachment);

    content_type = (content_type != NULL) ? content_type : "";
    content_description = g_content_type_get_description (content_type);
    display_size = g_format_size_for_display ((goffset) size);

    if (size > 0)
        caption = g_strdup_printf (
            "%s\n(%s)", display_name, display_size);
    else
        caption = g_strdup (display_name);

    /* Prefer the thumbnail if we have one. */
    if (thumbnail_path != NULL && *thumbnail_path != '\0') {
        file = g_file_new_for_path (thumbnail_path);
        icon = g_file_icon_new (file);
        g_object_unref (file);

    /* Else use the standard icon for the content type. */
    } else if (icon != NULL)
        g_object_ref (icon);

    /* Last ditch fallback.  (GFileInfo not yet loaded?) */
    else
        icon = g_themed_icon_new (DEFAULT_ICON_NAME);

    /* Apply emblems. */
    list = e_attachment_list_emblems (attachment);
    if (list != NULL) {
        GIcon *emblemed_icon;
        GEmblem *emblem;

        emblem = G_EMBLEM (list->data);
        emblemed_icon = g_emblemed_icon_new (icon, emblem);
        list = g_list_delete_link (list, list);
        g_object_unref (emblem);

        while (list != NULL) {
            emblem = G_EMBLEM (list->data);
            g_emblemed_icon_add_emblem (
                G_EMBLEMED_ICON (emblemed_icon), emblem);
            list = g_list_delete_link (list, list);
            g_object_unref (emblem);
        }

        g_object_unref (icon);
        icon = emblemed_icon;
    }

    /* We're about to trigger another "row-changed"
     * signal, so this prevents infinite recursion. */
    priv->ignore_row_changed = TRUE;

    gtk_list_store_set (
        GTK_LIST_STORE (model), iter,
        E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, content_description,
        E_ATTACHMENT_STORE_COLUMN_DISPLAY_NAME, display_name,
        E_ATTACHMENT_STORE_COLUMN_CAPTION, caption,
        E_ATTACHMENT_STORE_COLUMN_ICON, icon,
        E_ATTACHMENT_STORE_COLUMN_LOADING, loading,
        E_ATTACHMENT_STORE_COLUMN_SAVING, saving,
        E_ATTACHMENT_STORE_COLUMN_SIZE, size,
        -1);

    priv->ignore_row_changed = FALSE;

    g_object_unref (icon);
    g_free (content_description);
    g_free (display_size);
}

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_iface_init (GtkTreeModelIface *iface)
{
    iface->row_changed = attachment_store_row_changed;
}

static void
attachment_store_init (EAttachmentStore *store)
{
    GType types[E_ATTACHMENT_STORE_NUM_COLUMNS];
    GHashTable *activity_index;
    GHashTable *attachment_index;
    gint column = 0;

    activity_index = g_hash_table_new_full (
        g_direct_hash, g_direct_equal,
        (GDestroyNotify) g_object_unref,
        (GDestroyNotify) gtk_tree_row_reference_free);

    attachment_index = g_hash_table_new_full (
        g_direct_hash, g_direct_equal,
        (GDestroyNotify) g_object_unref,
        (GDestroyNotify) gtk_tree_row_reference_free);

    store->priv = E_ATTACHMENT_STORE_GET_PRIVATE (store);
    store->priv->activity_index = activity_index;
    store->priv->attachment_index = attachment_index;

    types[column++] = E_TYPE_ACTIVITY;  /* COLUMN_ACTIVITY */
    types[column++] = E_TYPE_ATTACHMENT;    /* COLUMN_ATTACHMENT */
    types[column++] = G_TYPE_STRING;    /* COLUMN_CAPTION */
    types[column++] = G_TYPE_STRING;    /* COLUMN_CONTENT_TYPE */
    types[column++] = G_TYPE_STRING;    /* COLUMN_DISPLAY_NAME */
    types[column++] = G_TYPE_ICON;      /* COLUMN_ICON */
    types[column++] = G_TYPE_BOOLEAN;   /* COLUMN_LOADING */
    types[column++] = G_TYPE_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 */
        };

        static const GInterfaceInfo iface_info = {
            (GInterfaceInitFunc) attachment_store_iface_init,
            (GInterfaceFinalizeFunc) NULL,
            NULL   /* interface_data */
        };

        type = g_type_register_static (
            GTK_TYPE_LIST_STORE, "EAttachmentStore",
            &type_info, 0);

        g_type_add_interface_static (
            type, GTK_TYPE_TREE_MODEL, &iface_info);
    }

    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);

    if (file != NULL && !g_file_is_native (file))
        attachment_store_copy_async (store, 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));
}

gboolean
e_attachment_store_remove_attachment (EAttachmentStore *store,
                                      EAttachment *attachment)
{
    GtkTreeRowReference *reference;
    GHashTable *hash_table;
    EActivity *activity;
    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;
    }

    model = gtk_tree_row_reference_get_model (reference);
    path = gtk_tree_row_reference_get_path (reference);
    gtk_tree_model_get_iter (model, &iter, path);
    gtk_tree_path_free (path);

    gtk_tree_model_get (
        model, &iter,
        E_ATTACHMENT_STORE_COLUMN_ACTIVITY, &activity, -1);

    if (activity != NULL) {
        /* Cancel the file transfer. */
        e_activity_cancel (activity);
        g_object_unref (activity);
    }

    gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
    g_hash_table_remove (hash_table, attachment);

    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);

        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)
{
    g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);

    return g_hash_table_size (store->priv->activity_index);
}

guint64
e_attachment_store_get_total_size (EAttachmentStore *store)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    guint64 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;
        gint column_id;

        column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
        gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
        total_size += e_attachment_get_size (attachment);
        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));

    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);
        g_object_unref (attachment);
    }

    g_slist_foreach (files, (GFunc) g_object_unref, NULL);
    g_slist_free (files);

exit:
    gtk_widget_destroy (dialog);
}

void
e_attachment_store_run_save_dialog (EAttachmentStore *store,
                                    EAttachment *attachment,
                                    GtkWindow *parent)
{
    GtkFileChooser *file_chooser;
    GtkWidget *dialog;
    GFile *file;
    EActivity *activity;
    const gchar *display_name;
    gint response;

    g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
    g_return_if_fail (E_IS_ATTACHMENT (attachment));

    dialog = gtk_file_chooser_dialog_new (
        _("Save Attachment"), parent,
        GTK_FILE_CHOOSER_ACTION_SAVE,
        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");

    display_name = e_attachment_get_display_name (attachment);
    if (display_name != NULL)
        gtk_file_chooser_set_current_name (file_chooser, display_name);

    response = e_attachment_store_run_file_chooser_dialog (store, dialog);

    if (response != GTK_RESPONSE_OK)
        goto exit;

    file = gtk_file_chooser_get_file (file_chooser);
    activity = e_file_activity_new (_("Saving attachment"));
    e_attachment_save_async (
        attachment, E_FILE_ACTIVITY (activity), file);
    g_signal_emit (store, signals[NEW_ACTIVITY], 0, activity);
    g_object_unref (activity);
    g_object_unref (file);

exit:
    gtk_widget_destroy (dialog);
}