aboutsummaryrefslogblamecommitdiffstats
path: root/plugins/save-attachments/save-attachments.c
blob: 9f28d71c30c9d3a661bb4887042e744ebf493545 (plain) (tree)































                                                                        




                                 


                               
                                 










                                      
                                     






                                     
                           
 






























                                                                              

                                            
































































































































                                                                                                                                            




                                                                                     
                                      
                                                                                                           


                                                             












                                                            
                                                                                            































                                                                                        
                              
                         
 
                                                                              
                        



                                                             

                                    
                                


                                                                                                
                         










































                                                                                                
                                                                                                   




                                                                                  
                                                                                                            
































































                                                                                                                   
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 *  Authors: Michael Zucchi <notzed@ximian.com>
 *
 *  Copyright 2004 Novell, Inc. (www.novell.com)
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of version 2 of the GNU General Public
 *  License as published by the Free Software Foundation.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
 *
 */

/* This is prototype code only, this may, or may not, use undocumented
 * unstable or private internal function calls. */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>

#if !GLIB_CHECK_VERSION (2, 8, 0)
#define g_access access
#endif

#include <gtk/gtkcheckbutton.h>
#include <gtk/gtkdialog.h>
#include <gtk/gtkmessagedialog.h>
#include <gtk/gtktreestore.h>
#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkcellrenderertoggle.h>
#include <gtk/gtkbox.h>
#include <gtk/gtkstock.h>
#include <gtk/gtktreeview.h>
#include <gtk/gtkfilechooser.h>
#include <gtk/gtkframe.h>
#include <gtk/gtklabel.h>
#include <gtk/gtkalignment.h>
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtkfilechooserbutton.h>

#include <camel/camel-folder.h>
#include <camel/camel-exception.h>
#include <camel/camel-mime-message.h>
#include <camel/camel-multipart.h>
#include <camel/camel-utf8.h>

#include "e-util/e-error.h"

#include "mail/em-menu.h"
#include "mail/em-utils.h"

/* these are sort of mail-internal */
#include "mail/mail-mt.h"
#include "mail/mail-ops.h"

void org_gnome_save_attachments_save(EPlugin *ep, EMMenuTargetSelect *target);

struct _save_data {
    CamelFolder *folder;
    char *uid;
    CamelMimeMessage *msg;

    char *path;
    char *base;

    GtkWidget *entry;
    GtkWidget *tree;
    GtkTreeStore *model;
};

static void
free_data(struct _save_data *data)
{
    if (data->msg)
        camel_object_unref(data->msg);
    g_free(data->base);
    g_free(data->path);
    g_free(data->uid);
    camel_object_unref(data->folder);
    if (data->model)
        g_object_unref(data->model);
    g_free(data);
}

static char *
clean_name(const char *s)
{
    GString *out = g_string_new("");
    int c;
    char *r;

    while ( (c = camel_utf8_getc((const unsigned char **)&s)) ) {
        if (!g_unichar_isprint(c) || ( c < 0x7f && strchr(" /'\"`&();|<>$%{}!", c )))
            c = '_';
        g_string_append_u(out, c);
    }

    r = g_strdup(out->str);
    g_string_free(out, TRUE);

    return r;
}

static void
fill_model_rec(CamelMimeMessage *msg, CamelMimePart *part, GtkTreeStore *model, GtkTreeIter *parent, GString *name)
{
    CamelDataWrapper *containee;
    int parts, i;
    char *type;
    GtkTreeIter iter;
    int len = name->len;
    CamelContentType *mime;

    containee = camel_medium_get_content_object((CamelMedium *)part);
    if (containee == NULL)
        return;

    mime = ((CamelDataWrapper *)containee)->mime_type;
    type = camel_content_type_simple(mime);

    if (CAMEL_IS_MULTIPART(containee)) {
        gtk_tree_store_append(model, &iter, parent);
        g_string_append_printf(name, ".multipart");
        gtk_tree_store_set(model, &iter, 0, FALSE, 1, type, 2, name->str, 3, name->str, 4, part, -1);

        parts = camel_multipart_get_number((CamelMultipart *)containee);
        for (i = 0; i < parts; i++) {
            CamelMimePart *mpart = camel_multipart_get_part((CamelMultipart *)containee, i);

            g_string_truncate(name, len);
            g_string_append_printf(name, ".%d", i);
            fill_model_rec(msg, mpart, model, &iter, name);
        }
    } else if (CAMEL_IS_MIME_MESSAGE(containee)) {
        gtk_tree_store_append(model, &iter, parent);
        g_string_append_printf(name, ".msg");
        gtk_tree_store_set(model, &iter, 0, FALSE, 1, type, 2, name->str, 3, name->str, 4, part, -1);
        fill_model_rec(msg, (CamelMimePart *)containee, model, &iter, name);
    } else {
        char *filename = NULL;
        const char *ext = NULL, *tmp;
        int save = FALSE;

        gtk_tree_store_append(model, &iter, parent);
        tmp = camel_mime_part_get_filename(part);
        if (tmp) {
            filename = clean_name(tmp);
            ext = strrchr(filename, '.');
        }
        tmp = camel_mime_part_get_disposition(part);
        if (tmp && !strcmp(tmp, "attachment"))
            save = TRUE;

        if (camel_content_type_is(mime, "text", "*")) {
            if (ext == NULL) {
                if ((ext = mime->subtype) == NULL || !strcmp(ext, "plain"))
                    ext = "text";
            }
        } else if (camel_content_type_is(mime, "image", "*")) {
            if (ext == NULL) {
                if ((ext = mime->subtype) == NULL)
                    ext = "image";
            }
            save = TRUE;
        }

        g_string_append_printf(name, ".%s", ext);
        gtk_tree_store_set(model, &iter, 0, save, 1, type, 2, filename?filename:name->str, 3, filename?NULL:name->str, 4, part, -1);
        g_free(filename);
    }
    g_free(type);

    g_string_truncate(name, len);
}

static void
fill_model(CamelMimeMessage *msg, GtkTreeStore *model)
{
    GString *name = g_string_new("");
    GtkTreeIter iter;

    gtk_tree_store_append(model, &iter, NULL);
    gtk_tree_store_set(model, &iter, 0, FALSE, 1, "message/rfc822", 2, ".msg", 3, ".msg", 4, msg, -1);
    fill_model_rec(msg, (CamelMimePart *)msg, model, &iter, name);
    g_string_free(name, TRUE);
}

static gboolean
save_part(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, void *d)
{
    struct _save_data *data = d;
    char *filename, *ext, *save;
    CamelMimePart *part;
    gboolean doit;

    /* TODO: check for existing file */

    gtk_tree_model_get(model, iter, 0, &doit, -1);
    if (!doit)
        return FALSE;

    gtk_tree_model_get(model, iter, 2, &filename, 3, &ext, 4, &part, -1);
    if (ext == NULL)
        save = g_build_filename(data->path, filename, NULL);
    else
        save = g_strdup_printf("%s%s", data->base, ext);

    /* FIXME: if part == data->msg then we need to save this
     * differently, not using the envelope MimePart */

    /* 
     * The underlying em_utils_save_part_to_file ain't using gnome-vfs. Therefor 
     * the POSIX access-call should suffice for checking the file existence.
     */

    if (g_access(save, F_OK) == 0)
        doit = e_error_run(NULL, E_ERROR_ASK_FILE_EXISTS_OVERWRITE, save, NULL) == GTK_RESPONSE_OK;

    if (doit)
        em_utils_save_part_to_file(NULL, save, part);

    g_free(ext);
    g_free(filename);

    return FALSE;
}

static void
save_response(GtkWidget *d, int id, struct _save_data *data)
{
    if (id == GTK_RESPONSE_OK) {
        char *tmp;

        data->base = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (data->entry));
        data->path = g_strdup(data->base);
        tmp = strrchr(data->path, '/');
        if (tmp)
            *tmp = 0;
        gtk_tree_model_foreach((GtkTreeModel *)data->model, save_part, data);
    }

    gtk_widget_destroy(d);
    free_data(data);
}

static gboolean
entry_changed_update(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, void *d)
{
    const char *name = d;
    char *filename, *ext;

    gtk_tree_model_get(model, iter, 3, &ext, -1);
    if (ext) {
        filename = g_strdup_printf("%s%s", name, ext);
        gtk_tree_store_set((GtkTreeStore *)model, iter, 2, filename, -1);
        g_free(filename);
        g_free(ext);
    }

    return FALSE;
}

static void
entry_changed(GtkWidget *entry, struct _save_data *data)
{
    char *path;
    char *basename = NULL;
    const char *file;

    path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (data->entry));
    if (path == NULL
        || G_IS_DIR_SEPARATOR (path[strlen(path)-1])
        || (basename = g_path_get_basename(path)) == NULL
        || (basename[0] == '.' && basename[1] == '\0')
        || (g_file_test(path, G_FILE_TEST_IS_DIR)))
        file = "attachment";
    else
        file = basename;

    gtk_tree_model_foreach((GtkTreeModel *)data->model, entry_changed_update, (void *)file);
    g_free(path);
    g_free(basename);
}

static void
toggle_changed(GtkWidget *entry, const char *spath, struct _save_data *data)
{
        GtkTreePath *path;
        GtkTreeIter iter;
        
        path = gtk_tree_path_new_from_string(spath);
        if (gtk_tree_model_get_iter((GtkTreeModel *)data->model, &iter, path)) {
        gboolean on;

                gtk_tree_model_get((GtkTreeModel *)data->model, &iter, 0, &on, -1);
        gtk_tree_store_set(data->model, &iter, 0, !on, -1);
        }
        
        gtk_tree_path_free (path);
}

static void
save_got_message(CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *d)
{
    struct _save_data *data = d;
    GtkDialog *dialog;
    GtkWidget *w, *tree;
    GtkTreeStore *model;
    GtkCellRenderer *renderer;

    /* not found, the mailer will show an error box for this */
    if (msg == NULL) {
        free_data(data);
        return;
    }

    data->msg = msg;
    camel_object_ref(msg);

    dialog = (GtkDialog *)gtk_dialog_new_with_buttons("Save attachments",
                              NULL, /* target->parent? */
                              0,
                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                              GTK_STOCK_SAVE, GTK_RESPONSE_OK,
                              NULL);
    w = gtk_file_chooser_button_new (_("Select save base name"), GTK_FILE_CHOOSER_ACTION_OPEN);
    data->entry = w;
    g_object_set(w, "filechooser_action", GTK_FILE_CHOOSER_ACTION_SAVE, NULL);
    gtk_widget_show(w);
    gtk_box_pack_start((GtkBox *)dialog->vbox, w, FALSE, TRUE, 6);

    g_signal_connect(GTK_FILE_CHOOSER_BUTTON (w), "selection-changed", G_CALLBACK(entry_changed), data);

    model = gtk_tree_store_new(5, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
    data->model = model;
    fill_model(msg, model);

    tree = gtk_tree_view_new_with_model((GtkTreeModel *)model);
    data->tree = tree;
    gtk_widget_show(tree);
    gtk_tree_view_expand_all((GtkTreeView *)tree);

    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_insert_column_with_attributes((GtkTreeView *)tree, -1,
                            _("MIME Type"), renderer, "text", 1, NULL);
    gtk_tree_view_set_expander_column((GtkTreeView *)tree, gtk_tree_view_get_column((GtkTreeView *)tree, 0));

    renderer = gtk_cell_renderer_toggle_new();
    g_object_set(renderer, "activatable", TRUE, NULL);
    g_signal_connect(renderer, "toggled", G_CALLBACK(toggle_changed), data);

    gtk_tree_view_insert_column_with_attributes((GtkTreeView *)tree, -1,
                            _("Save"), renderer, "active", 0, NULL);    
    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_insert_column_with_attributes((GtkTreeView *)tree, -1,
                            _("Name"), renderer, "text", 2, NULL);

    w = g_object_new(gtk_frame_get_type(),
             "shadow_type", GTK_SHADOW_NONE,
             "label_widget", g_object_new(gtk_label_get_type(),
                              "label", "<span weight=\"bold\">Attachments</span>",
                              "use_markup", TRUE,
                              "xalign", 0.0, NULL),
             "child", g_object_new(gtk_alignment_get_type(),
                           "left_padding", 12,
                           "top_padding", 6,
                           "child", g_object_new(gtk_scrolled_window_get_type(),
                                     "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
                                     "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
                                     "shadow_type", GTK_SHADOW_IN,
                                     "child", tree,
                                     NULL),
                           NULL),
             NULL);
    gtk_widget_show_all(w);

    gtk_box_pack_start((GtkBox *)dialog->vbox, w, TRUE, TRUE, 0);
    g_signal_connect(dialog, "response", G_CALLBACK(save_response), data);
    gtk_window_set_default_size((GtkWindow *)dialog, 500, 500);
    gtk_widget_show((GtkWidget *)dialog);
}

void
org_gnome_save_attachments_save(EPlugin *ep, EMMenuTargetSelect *target)
{
    struct _save_data *data;

    if (target->uids->len != 1)
        return;

    data = g_malloc0(sizeof(*data));
    data->folder = target->folder;
    camel_object_ref(data->folder);
    data->uid = g_strdup(target->uids->pdata[0]);

    mail_get_message(data->folder, data->uid, save_got_message, data, mail_thread_new);
}