aboutsummaryrefslogblamecommitdiffstats
path: root/mail/e-mail-reader-utils.c
blob: 2a3c4c6a8c9a14419e7f25209c8ea18be66ba10f (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                                                                    
                                                                             







                                                                  



                    


                                
                        
                            
                        
 
                                
 



                                                
 

                                        
 
                                        
 








                              


            



                                          
                            

                                  
                            
                                      
                        
                           

                           

                                    
                                        
                                             

                                   

                                


           
                                                
 





                                                   
 


                                                        

                                            
 
                                                   

 









                                                       


                                                  
                            
                                 
                                
                             

                          
                            

                                          
                                        




                                                                   
                                                   

                                                   
                                                               
 

                                                           
 


                                                              
                          

                                      
                          
 
                                              
                                                      
                                                           
 
                                                                              
 
                                          
                                                               
                                                                              
                                       


                                                        
                                                  


                                                      
                                                       
                                                           


                                    
     

                                 


                                             
           
                                                     
                                                   
                                                 
 

                            
                               

                                    
 

                                                   
 

                                                          
 
                                                                   
 



                                                                     


                                                            

                                                    

                
                                                                      

         
                                           





                                                 

                              
                      
                               
                                 
                                


                                                             
                                
                                  







                                                        
                                                              
                                                              
                                                                             
 
                                                                          
 


                                                       


                                                                 
                             
                                                                  


                                                                    


                       

                                                                      
                                                           






                                                            





                                                                    
                                            






                                                                  
                                                    


                                                                 
                                                    



                                                                          
                                                    


                                                                         
                                                    



                                                                      
                                          
                                            



                                                                    


                                                                  









                                                                     







                                                     
                                            
         

 




                                                          
                            
                            
                               

                                    
 
                                                   
 

                                                          

                                                                   






                                                                   
 

                                                                     
 
                                         

                                                            


                                                    

                

                                                                            

         
                                           







                                                           
                                  
                                    







                                                            



                                                            





                                                  


                                  

 






                                                      
                            
                               

                                    

                                              



                                                          
 
                                                                    
 

                                                                     
 
                                         


                                                               

                                                    

                
                                                                      

         
                                           





















                                                              
                                          
                                            



                                                                    


                                                                  

                                       





                                                        


         





                                                           
                            
                            
                               

                                    
 
                                                   
 

                                                          

                                                                   

                                                                   
 

                                                                     
 
                                         

                                                             


                                                    

                

                                                                             

         
                                           







                                                            
                                  
                                    







                                                            



                                                            





                                                   


                                  

 






                                                      
                            
                               

                                    

                                              
                                                   
 

                                                          
 

                                                                     
 
                                         


                                                               

                                                    

                
                                                                      

         
                                           






                                                  
                                  
                                    






                                                            


                                                          

                                   





                                                

 





                                                           
                            
                            
                               

                                    
 
                                                   
 

                                                          

                                                                   

                                                                   
 

                                                                     
 
                                         

                                                             


                                                    

                

                                                                             

         
                                           







                                                            
                                  
                                    







                                                            



                                                            





                                                   

                               
                                  

 





                                                               
                            
                               

                                    
 
                                                   
 

                                                          

                                                      
                                                                          
 

                                                                     
 
                                         

                                                              


                                                    

                
                                                                      

         
                                           







                                                                
                                  
                                    







                                                            



                                                            




                                                        


                                  

 




                                                 
                            
                     


                                                            
                                                   
 

                                
 
                                             
 
                                                                
 


                                                                    
 
                                         




                                           


                  
 
           

                                         

                                         
                           











                                                                


                                                             

                                                                                             
 



                                                 
                      
                              
                                  



                            
                     


                                                            
                                                     


                                                                      
                                                   
                                                        

                                                   

                                                        
 


                                                                  
 
                                                                               
 


                               








                                                                              













                                                                   
 

                                                                            



                                                          
                                                     
                                                                        
                                                  




                                                                
                                                              




                                                    
                                
 


                                                      
 
                                                                           
                                                                         
 

                                                                   

                                         
                                                                  


                                                                     

                                       


                                          
                                                          

                                       

                                 
                                 



                  
           


                                                     
 

                               



                                                   
 
                                           
                                                          
 
                                     
                                                                      
 

                                                                     
 
                                         

                                                           

                                                    
 




                                                                             
         
 
                                           


           


                                                           
 
                            
                                   
                                  


                                  
                                 
                                    
 



                                                   

                                                            

                                                                        
                                                 
 
                                                               
                                                                
 

                                                      
                              





                                             
 
                                 


           


                                                         
 

                               
                                  




                                                   
 






















                                                                     


                       
                                           

                                     








                                                   





                                                    
                            
                                  
                                  
                                    
 
                                                     

                                                                              
 




                                                            
                                                                  


                                                                         
 
                                  






                                                 

 
           
                                                          
                                                        
                                                      
 
                            
                               

                                    
 
                                                   
 

                                                          
 

                                                                    
 



                                                                     


                                                  

                                                    

         
                                           





                                                      
                                    





                                                     


                                                        




                                                            


                                                          
 

                                                   
                                          


                                   
                                                  

                               

                                
                                  




                                 
                                                         
                                                       
                                                     
 
                            
                               
                            


                                 

                                    
 






                                                                         

                                                                   
                                              
 







                                                                     

                       
                                         


                                                       


                                                    


                       

                                                                     

                                                              















































                                                                          
                                           




                                                     
                            
                                  
                                    




                                                     


                                                        

                                                     


                                                            


                                                          
 

                                                   
                                               


                                   
                                                 

                               

                                
                                  
 


                                 
















































































































                                                                      








































































































































































































                                                                     










































                                                                         
           
                                                  
                                                       



                                                     

                                                     
                               
                                  


                                                   
 
                                                                        
                                                           
 
                                                                    

                                                                      
                                              
                               





                                           
 

                                                                   
                                   

                                           


           
                                                         
                                                       
                                                     
 
                            
                               
                                  
                                  

                                    
 
                                                   
 


                                                            
 










                                                                     
                       
 
                                         

                                                               


                                                    
                       

         
                                     








                                                 

 
    

                                                              
                                                          
 
                      
                              
                                     
                              
                                        
                                
                                      
                                             
                            
                                    
                           



                                         
                        
                                 
                               

                                                      






                                                                    
                                                     
                                                          
                                                               
                                                             

                                                  

                                                          
                                        
 











                                                                        


                                                                        









                                                                 
                                                      

                                       

                                                   


                                                            
                                                    
                                                                   
                                                              

                          
                         
                                   
                


                                                                       
 

                                                                    
                                           
 
                                                                          
 

                                                                                    
 






                                                                                             
                         
 
                                              

                 
 
                                  
                                                                       

                                                   
 
                                           
                                 
 
                                                       

                                           
                                 
         
 
                                                       

                                   
                                                             


                                                    
                                    


                                                             








                                                                           

                                      









                                                

                                     
                                              
                                                
                                                        
                                                               

























                                                                                                        
 

                                                                       

                                     

                           
                  

              

                                    
                                          
                                            
 


                                                                    









                                                                        

                                          





                                                         
 
                                          
 
                
                                                      

                                                                     

                                                                               
         
 
     
                                  
                                 

 
           
                                                     
                                                   
                                                 
 
                            
                               

                                    
 
                                                   
 




                                                                    
 

                                                                     
 
                                         


                                             

                                                    

         
                                           






                                                 

                                  
                                    








                                     
                                                   



                                                         

                            
                                        
 

                                                                           

         

                                                  































                                                                          

                                





                                                            


                                                          


                                     






                                             

                                     


                                 


                                 



                                                             
                                








                                                               

                                                


                                                           

                                                            



                                                            
                                                     
                                                   
                                                 
 
                            
                              
                              

                                  

                                    
 
                                                   
 

                                                          
 










                                                                     

                       
                                         
                                
                                                               


                                                    


                       

                                                                 

                                                              
 
                                                                    
                                                       
 
                                                                                      
                                                                                   
                                                                         
                                                                        
 
                                     
                                 

                                             


                                 
                                           





                                                               
                      
                            
                              
                                    
                                  
                                  
                            
                        

                                   
 

                                                     



                                                                      

                                                   
 

                                                         
                                                         
            
                                                         
 
                                                        

                                                          
 

                                                            
 




                                                          
 
                                  






                                             
 
                                 

                                



                                                             
                                                      
                                                    
                                                  
 
                            



                                  
                                






                                                          
 

                                                                    
 



                                                               
 


                                                                     

                       
                                         
                                
                                                               


                                                    


                       

                                                                  

                                                              
 
                                                                    
                                                       
 
                                           










                                                                                                       
                                                                     


                 

                                      
                                           
                            


                                 
                                           





                                                                
                            
                                  
                                    
                        
                                 
 

                                                     
                                                        

                                                          
 

                                                            
 

                                                          
                                                                  


                                                            
 
                                  

                                           





                                              
 
                                 
 
 
           
                                                          

                                                         
 


                                                     
                                    
                        
 
                                                                           
 
                                                    
 
                                          

                                                        
 
                                                                  
                                


                                           
 

                                                                         
 
                                                                          
 
                                                      




                                                   
 
                                        
 
                                      
                                                                    

                                                                             
         
 
                          
 
                                             






                                                       
                                                       
                                                          

                                                
                                   

                                    

                                                     


                                                           
 




                                                             
                                                          


                                                            

                                            
                                                       
                                             




                                                                            


                                                      
                                                 

                                
                                  



                                                        
                                                         
 

                                    
 



                                                    
 

                                                                           
 

                                                        
 
                                        
 
/*
 * e-mail-reader-utils.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)
 *
 */

/* Miscellaneous utility functions used by EMailReader actions. */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "e-mail-reader-utils.h"

#include <glib/gi18n.h>
#include <libxml/tree.h>
#include <gtkhtml/gtkhtml.h>
#include <camel/camel.h>

#include "shell/e-shell-utils.h"

#include "libemail-engine/e-mail-folder-utils.h"
#include "libemail-engine/e-mail-utils.h"
#include "libemail-engine/mail-ops.h"
#include "libemail-engine/mail-tools.h"

#include "em-format/e-mail-parser.h"
#include "em-format/e-mail-part-utils.h"

#include "composer/e-composer-actions.h"

#include "e-mail-backend.h"
#include "e-mail-browser.h"
#include "e-mail-printer.h"
#include "e-mail-display.h"
#include "em-composer-utils.h"
#include "em-utils.h"
#include "mail-autofilter.h"
#include "mail-vfolder-ui.h"
#include "message-list.h"

#define d(x)

typedef struct _AsyncContext AsyncContext;

struct _AsyncContext {
    EActivity *activity;
    CamelFolder *folder;
    CamelMimeMessage *message;
    EMailPartList *part_list;
    EMailReader *reader;
    CamelInternetAddress *address;
    GPtrArray *uids;
    gchar *folder_name;
    gchar *message_uid;

    EMailReplyType reply_type;
    EMailReplyStyle reply_style;
    EMailForwardStyle forward_style;
    GtkPrintOperationAction print_action;
    const gchar *filter_source;
    gint filter_type;
    gboolean replace;
    gboolean keep_signature;
};

static void
async_context_free (AsyncContext *async_context)
{
    g_clear_object (&async_context->activity);
    g_clear_object (&async_context->folder);
    g_clear_object (&async_context->message);
    g_clear_object (&async_context->part_list);
    g_clear_object (&async_context->reader);
    g_clear_object (&async_context->address);

    if (async_context->uids != NULL)
        g_ptr_array_unref (async_context->uids);

    g_free (async_context->folder_name);
    g_free (async_context->message_uid);

    g_slice_free (AsyncContext, async_context);
}

static gboolean
mail_reader_is_special_local_folder (const gchar *name)
{
    return (strcmp (name, "Drafts") == 0 ||
        strcmp (name, "Inbox") == 0 ||
        strcmp (name, "Outbox") == 0 ||
        strcmp (name, "Sent") == 0 ||
        strcmp (name, "Templates") == 0);
}

gboolean
e_mail_reader_confirm_delete (EMailReader *reader)
{
    CamelFolder *folder;
    CamelStore *parent_store;
    GtkWidget *check_button;
    GtkWidget *container;
    GtkWidget *dialog;
    GtkWindow *window;
    GSettings *settings;
    const gchar *label;
    gboolean prompt_delete_in_vfolder;
    gint response = GTK_RESPONSE_OK;

    /* Remind users what deleting from a search folder does. */

    g_return_val_if_fail (E_IS_MAIL_READER (reader), FALSE);

    folder = e_mail_reader_ref_folder (reader);
    window = e_mail_reader_get_window (reader);

    settings = g_settings_new ("org.gnome.evolution.mail");

    prompt_delete_in_vfolder = g_settings_get_boolean (
        settings, "prompt-on-delete-in-vfolder");

    parent_store = camel_folder_get_parent_store (folder);

    if (!CAMEL_IS_VEE_STORE (parent_store))
        goto exit;

    if (!prompt_delete_in_vfolder)
        goto exit;

    dialog = e_alert_dialog_new_for_args (
        window, "mail:ask-delete-vfolder-msg",
        camel_folder_get_full_name (folder), NULL);

    container = e_alert_dialog_get_content_area (E_ALERT_DIALOG (dialog));

    label = _("Do not warn me again");
    check_button = gtk_check_button_new_with_label (label);
    gtk_box_pack_start (GTK_BOX (container), check_button, TRUE, TRUE, 6);
    gtk_widget_show (check_button);

    response = gtk_dialog_run (GTK_DIALOG (dialog));

    if (response != GTK_RESPONSE_DELETE_EVENT)
        g_settings_set_boolean (
            settings,
            "prompt-on-delete-in-vfolder",
            !gtk_toggle_button_get_active (
            GTK_TOGGLE_BUTTON (check_button)));

    gtk_widget_destroy (dialog);

exit:
    g_clear_object (&folder);

    return (response == GTK_RESPONSE_OK);
}

static void
mail_reader_delete_folder_cb (GObject *source_object,
                              GAsyncResult *result,
                              gpointer user_data)
{
    CamelFolder *folder;
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    folder = CAMEL_FOLDER (source_object);
    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    e_mail_folder_remove_finish (folder, result, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-delete-folder",
            camel_folder_get_full_name (folder),
            local_error->message, NULL);
        g_error_free (local_error);

    } else {
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
    }

    async_context_free (async_context);
}

void
e_mail_reader_delete_folder (EMailReader *reader,
                             CamelFolder *folder)
{
    EMailBackend *backend;
    EMailSession *session;
    EShell *shell;
    EAlertSink *alert_sink;
    CamelStore *parent_store;
    CamelProvider *provider;
    MailFolderCache *folder_cache;
    GtkWindow *parent = e_shell_get_active_window (NULL);
    GtkWidget *dialog;
    gboolean store_is_local;
    const gchar *display_name;
    const gchar *full_name;
    CamelFolderInfoFlags flags = 0;
    gboolean have_flags;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_FOLDER (folder));

    full_name = camel_folder_get_full_name (folder);
    display_name = camel_folder_get_display_name (folder);
    parent_store = camel_folder_get_parent_store (folder);
    provider = camel_service_get_provider (CAMEL_SERVICE (parent_store));

    store_is_local = (provider->flags & CAMEL_PROVIDER_IS_LOCAL) != 0;

    backend = e_mail_reader_get_backend (reader);
    session = e_mail_backend_get_session (backend);

    alert_sink = e_mail_reader_get_alert_sink (reader);
    folder_cache = e_mail_session_get_folder_cache (session);

    if (store_is_local &&
        mail_reader_is_special_local_folder (full_name)) {
        e_alert_submit (
            alert_sink, "mail:no-delete-special-folder",
            display_name, NULL);
        return;
    }

    shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));

    if (!store_is_local && !e_shell_get_online (shell))
    {
        e_alert_submit (
            alert_sink, "mail:online-operation",
            display_name, NULL);
        return;
    }

    have_flags = mail_folder_cache_get_folder_info_flags (
        folder_cache, folder, &flags);

    if (have_flags && (flags & CAMEL_FOLDER_SYSTEM)) {
        e_alert_submit (
            alert_sink, "mail:no-delete-special-folder",
            display_name, NULL);
        return;
    }

    if (have_flags && (flags & CAMEL_FOLDER_CHILDREN)) {
        if (CAMEL_IS_VEE_STORE (parent_store))
            dialog = e_alert_dialog_new_for_args (
                parent, "mail:ask-delete-vfolder",
                display_name, NULL);
        else
            dialog = e_alert_dialog_new_for_args (
                parent, "mail:ask-delete-folder",
                display_name, NULL);
    } else {
        if (CAMEL_IS_VEE_STORE (parent_store))
            dialog = e_alert_dialog_new_for_args (
                parent, "mail:ask-delete-vfolder-nochild",
                display_name, NULL);
        else
            dialog = e_alert_dialog_new_for_args (
                parent, "mail:ask-delete-folder-nochild",
                display_name, NULL);
    }

    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
        EActivity *activity;
        GCancellable *cancellable;
        AsyncContext *async_context;

        activity = e_mail_reader_new_activity (reader);
        cancellable = e_activity_get_cancellable (activity);

        async_context = g_slice_new0 (AsyncContext);
        async_context->activity = g_object_ref (activity);
        async_context->reader = g_object_ref (reader);

        /* Disable the dialog until the activity finishes. */
        gtk_widget_set_sensitive (dialog, FALSE);

        /* Destroy the dialog once the activity finishes. */
        g_object_set_data_full (
            G_OBJECT (activity), "delete-dialog",
            dialog, (GDestroyNotify) gtk_widget_destroy);

        e_mail_folder_remove (
            folder,
            G_PRIORITY_DEFAULT,
            cancellable,
            mail_reader_delete_folder_cb,
            async_context);

        g_object_unref (activity);
    } else {
        gtk_widget_destroy (dialog);
    }
}

static void
mail_reader_delete_folder_name_cb (GObject *source_object,
                                   GAsyncResult *result,
                                   gpointer user_data)
{
    CamelFolder *folder;
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    /* XXX The returned CamelFolder is a borrowed reference. */
    folder = camel_store_get_folder_finish (
        CAMEL_STORE (source_object), result, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((folder != NULL) && (local_error == NULL)) ||
        ((folder == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-delete-folder",
            async_context->folder_name,
            local_error->message, NULL);
        g_error_free (local_error);

    } else {
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
        e_mail_reader_delete_folder (async_context->reader, folder);
    }

    async_context_free (async_context);
}

void
e_mail_reader_delete_folder_name (EMailReader *reader,
                                  CamelStore *store,
                                  const gchar *folder_name)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_STORE (store));
    g_return_if_fail (folder_name != NULL);

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);
    async_context->folder_name = g_strdup (folder_name);

    camel_store_get_folder (
        store, folder_name,
        CAMEL_STORE_FOLDER_INFO_FAST,
        G_PRIORITY_DEFAULT, cancellable,
        mail_reader_delete_folder_name_cb,
        async_context);

    g_object_unref (activity);
}

/* Helper for e_mail_reader_expunge_folder() */
static void
mail_reader_expunge_folder_cb (GObject *source_object,
                               GAsyncResult *result,
                               gpointer user_data)
{
    CamelFolder *folder;
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    folder = CAMEL_FOLDER (source_object);
    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    e_mail_folder_expunge_finish (folder, result, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-expunge-folder",
            camel_folder_get_display_name (folder),
            local_error->message, NULL);
        g_error_free (local_error);

    } else {
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
    }

    async_context_free (async_context);
}

void
e_mail_reader_expunge_folder (EMailReader *reader,
                              CamelFolder *folder)
{
    GtkWindow *window;
    const gchar *display_name;
    gboolean proceed;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_FOLDER (folder));

    window = e_mail_reader_get_window (reader);
    display_name = camel_folder_get_display_name (folder);

    proceed = em_utils_prompt_user (
        window, "prompt-on-expunge",
        "mail:ask-expunge", display_name, NULL);

    if (proceed) {
        EActivity *activity;
        GCancellable *cancellable;
        AsyncContext *async_context;

        activity = e_mail_reader_new_activity (reader);
        cancellable = e_activity_get_cancellable (activity);

        async_context = g_slice_new0 (AsyncContext);
        async_context->activity = g_object_ref (activity);
        async_context->reader = g_object_ref (reader);

        e_mail_folder_expunge (
            folder,
            G_PRIORITY_DEFAULT, cancellable,
            mail_reader_expunge_folder_cb,
            async_context);

        g_object_unref (activity);
    }
}

/* Helper for e_mail_reader_expunge_folder_name() */
static void
mail_reader_expunge_folder_name_cb (GObject *source_object,
                                    GAsyncResult *result,
                                    gpointer user_data)
{
    CamelFolder *folder;
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    /* XXX The returned CamelFolder is a borrowed reference. */
    folder = camel_store_get_folder_finish (
        CAMEL_STORE (source_object), result, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-expunge-folder",
            async_context->folder_name,
            local_error->message, NULL);
        g_error_free (local_error);

    } else {
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
        e_mail_reader_expunge_folder (async_context->reader, folder);
    }

    async_context_free (async_context);
}

void
e_mail_reader_expunge_folder_name (EMailReader *reader,
                                   CamelStore *store,
                                   const gchar *folder_name)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_STORE (store));
    g_return_if_fail (folder_name != NULL);

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);
    async_context->folder_name = g_strdup (folder_name);

    camel_store_get_folder (
        store, folder_name,
        CAMEL_STORE_FOLDER_INFO_FAST,
        G_PRIORITY_DEFAULT, cancellable,
        mail_reader_expunge_folder_name_cb,
        async_context);

    g_object_unref (activity);
}

/* Helper for e_mail_reader_refresh_folder() */
static void
mail_reader_refresh_folder_cb (GObject *source_object,
                               GAsyncResult *result,
                               gpointer user_data)
{
    CamelFolder *folder;
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    folder = CAMEL_FOLDER (source_object);
    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-refresh-folder",
            camel_folder_get_display_name (folder),
            local_error->message, NULL);
        g_error_free (local_error);

    } else {
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
    }

    async_context_free (async_context);
}

void
e_mail_reader_refresh_folder (EMailReader *reader,
                              CamelFolder *folder)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_FOLDER (folder));

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);

    camel_folder_refresh_info (
        folder,
        G_PRIORITY_DEFAULT, cancellable,
        mail_reader_refresh_folder_cb,
        async_context);

    g_object_unref (activity);
}

/* Helper for e_mail_reader_refresh_folder_name() */
static void
mail_reader_refresh_folder_name_cb (GObject *source_object,
                                    GAsyncResult *result,
                                    gpointer user_data)
{
    CamelFolder *folder;
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    /* XXX The returned CamelFolder is a borrowed reference. */
    folder = camel_store_get_folder_finish (
        CAMEL_STORE (source_object), result, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-refresh-folder",
            async_context->folder_name,
            local_error->message, NULL);
        g_error_free (local_error);

    } else {
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
        e_mail_reader_refresh_folder (async_context->reader, folder);
    }

    async_context_free (async_context);
}

void
e_mail_reader_refresh_folder_name (EMailReader *reader,
                                   CamelStore *store,
                                   const gchar *folder_name)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_STORE (store));
    g_return_if_fail (folder_name != NULL);

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);
    async_context->folder_name = g_strdup (folder_name);

    camel_store_get_folder (
        store, folder_name,
        CAMEL_STORE_FOLDER_INFO_FAST,
        G_PRIORITY_DEFAULT, cancellable,
        mail_reader_refresh_folder_name_cb,
        async_context);

    g_object_unref (activity);
}

/* Helper for e_mail_reader_unsubscribe_folder_name() */
static void
mail_reader_unsubscribe_folder_name_cb (GObject *source_object,
                                        GAsyncResult *result,
                                        gpointer user_data)
{
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    camel_subscribable_unsubscribe_folder_finish (
        CAMEL_SUBSCRIBABLE (source_object), result, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:folder-unsubscribe",
            async_context->folder_name,
            local_error->message, NULL);
        g_error_free (local_error);

    } else {
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
    }

    async_context_free (async_context);
}

void
e_mail_reader_unsubscribe_folder_name (EMailReader *reader,
                                       CamelStore *store,
                                       const gchar *folder_name)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (store));
    g_return_if_fail (folder_name != NULL);

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);
    async_context->folder_name = g_strdup (folder_name);

    camel_subscribable_unsubscribe_folder (
        CAMEL_SUBSCRIBABLE (store), folder_name,
        G_PRIORITY_DEFAULT, cancellable,
        mail_reader_unsubscribe_folder_name_cb,
        async_context);

    g_object_unref (activity);
}

guint
e_mail_reader_mark_selected (EMailReader *reader,
                             guint32 mask,
                             guint32 set)
{
    CamelFolder *folder;
    guint ii = 0;

    g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);

    folder = e_mail_reader_ref_folder (reader);

    if (folder != NULL) {
        GPtrArray *uids;

        camel_folder_freeze (folder);

        uids = e_mail_reader_get_selected_uids (reader);

        for (ii = 0; ii < uids->len; ii++)
            camel_folder_set_message_flags (
                folder, uids->pdata[ii], mask, set);

        g_ptr_array_unref (uids);

        camel_folder_thaw (folder);

        g_object_unref (folder);
    }

    return ii;
}

static void
copy_tree_state (EMailReader *src_reader,
                 EMailReader *des_reader)
{
    GtkWidget *src_mlist, *des_mlist;
    ETableState *state;

    g_return_if_fail (src_reader != NULL);
    g_return_if_fail (des_reader != NULL);

    src_mlist = e_mail_reader_get_message_list (src_reader);
    if (!src_mlist)
        return;

    des_mlist = e_mail_reader_get_message_list (des_reader);
    if (!des_mlist)
        return;

    state = e_tree_get_state_object (E_TREE (src_mlist));
    e_tree_set_state_object (E_TREE (des_mlist), state);
    g_object_unref (state);

    message_list_set_search (MESSAGE_LIST (des_mlist), MESSAGE_LIST (src_mlist)->search);
}

guint
e_mail_reader_open_selected (EMailReader *reader)
{
    EShell *shell;
    EMailBackend *backend;
    ESourceRegistry *registry;
    CamelFolder *folder;
    GtkWindow *window;
    GPtrArray *views;
    GPtrArray *uids;
    guint ii = 0;

    g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);

    backend = e_mail_reader_get_backend (reader);
    shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
    registry = e_shell_get_registry (shell);

    folder = e_mail_reader_ref_folder (reader);
    uids = e_mail_reader_get_selected_uids (reader);
    window = e_mail_reader_get_window (reader);

    if (!em_utils_ask_open_many (window, uids->len))
        goto exit;

    if (em_utils_folder_is_drafts (registry, folder) ||
        em_utils_folder_is_outbox (registry, folder) ||
        em_utils_folder_is_templates (registry, folder)) {

        e_mail_reader_edit_messages (reader, folder, uids, TRUE, TRUE);

        ii = uids->len;

        goto exit;
    }

    views = g_ptr_array_new ();

    /* For vfolders we need to edit the original, not the vfolder copy. */
    for (ii = 0; ii < uids->len; ii++) {
        const gchar *uid = uids->pdata[ii];
        CamelFolder *real_folder;
        CamelMessageInfo *info;
        gchar *real_uid;

        if (!CAMEL_IS_VEE_FOLDER (folder)) {
            g_ptr_array_add (views, g_strdup (uid));
            continue;
        }

        info = camel_folder_get_message_info (folder, uid);
        if (info == NULL)
            continue;

        real_folder = camel_vee_folder_get_location (
            CAMEL_VEE_FOLDER (folder),
            (CamelVeeMessageInfo *) info, &real_uid);

        if (em_utils_folder_is_drafts (registry, real_folder) ||
            em_utils_folder_is_outbox (registry, real_folder)) {
            GPtrArray *edits;

            edits = g_ptr_array_new ();
            g_ptr_array_add (edits, real_uid);
            e_mail_reader_edit_messages (
                reader, real_folder, edits, TRUE, TRUE);
            g_ptr_array_unref (edits);
        } else {
            g_free (real_uid);
            g_ptr_array_add (views, g_strdup (uid));
        }

        camel_folder_free_message_info (folder, info);
    }

    for (ii = 0; ii < views->len; ii++) {
        const gchar *uid = views->pdata[ii];
        GtkWidget *browser;
        MessageList *ml;

        browser = e_mail_browser_new (
            backend, folder, uid,
            E_MAIL_FORMATTER_MODE_NORMAL);

        e_mail_reader_set_folder (E_MAIL_READER (browser), folder);
        e_mail_reader_set_message (E_MAIL_READER (browser), uid);

        ml = MESSAGE_LIST (e_mail_reader_get_message_list (
            E_MAIL_READER (browser)));
        message_list_freeze (ml);

        copy_tree_state (reader, E_MAIL_READER (browser));
        e_mail_reader_set_group_by_threads (
            E_MAIL_READER (browser),
            e_mail_reader_get_group_by_threads (reader));

        message_list_thaw (ml);
        gtk_widget_show (browser);
    }

    g_ptr_array_foreach (views, (GFunc) g_free, NULL);
    g_ptr_array_free (views, TRUE);

exit:
    g_clear_object (&folder);
    g_ptr_array_unref (uids);

    return ii;
}

static void
mail_reader_print_message_cb (GObject *source_object,
                              GAsyncResult *result,
                              gpointer user_data)
{
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    e_mail_printer_print_finish (
        E_MAIL_PRINTER (source_object), result, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:printing-failed",
            local_error->message, NULL);
        g_error_free (local_error);

    } else {
        /* Set activity as completed, and keep it displayed for a few
         * seconds so that user can actually see the the printing was
         * successfully finished. */
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
    }

    async_context_free (async_context);
}

static void
mail_reader_print_parse_message_cb (GObject *source_object,
                                    GAsyncResult *result,
                                    gpointer user_data)
{
    EMailReader *reader;
    EMailDisplay *mail_display;
    EMailFormatter *formatter;
    EActivity *activity;
    GCancellable *cancellable;
    EMailPrinter *printer;
    EMailPartList *part_list;
    AsyncContext *async_context;

    reader = E_MAIL_READER (source_object);
    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    cancellable = e_activity_get_cancellable (activity);

    part_list = e_mail_reader_parse_message_finish (reader, result);

    printer = e_mail_printer_new (part_list);

    mail_display = e_mail_reader_get_mail_display (reader);
    formatter = e_mail_display_get_formatter (mail_display);

    e_activity_set_text (activity, _("Printing"));

    e_mail_printer_print (
        printer,
        async_context->print_action,
        formatter,
        cancellable,
        mail_reader_print_message_cb,
        async_context);

    g_object_unref (printer);
}

static void
mail_reader_print_get_message_cb (GObject *source_object,
                                  GAsyncResult *result,
                                  gpointer user_data)
{
    EActivity *activity;
    EAlertSink *alert_sink;
    CamelMimeMessage *message;
    GCancellable *cancellable;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);
    cancellable = e_activity_get_cancellable (activity);

    message = camel_folder_get_message_finish (
        CAMEL_FOLDER (source_object), result, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((message != NULL) && (local_error == NULL)) ||
        ((message == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        async_context_free (async_context);
        g_error_free (local_error);
        return;

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-retrieve-message",
            local_error->message, NULL);
        async_context_free (async_context);
        g_error_free (local_error);
        return;
    }

    e_activity_set_text (activity, "");

    e_mail_reader_parse_message (
        async_context->reader,
        async_context->folder,
        async_context->message_uid,
        message,
        cancellable,
        mail_reader_print_parse_message_cb,
        async_context);

    g_object_unref (message);
}

void
e_mail_reader_print (EMailReader *reader,
                     GtkPrintOperationAction action)
{
    EActivity *activity;
    GCancellable *cancellable;
    MessageList *message_list;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_MAIL_READER (reader));

    message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->folder = e_mail_reader_ref_folder (reader);
    async_context->reader = g_object_ref (reader);
    async_context->message_uid = g_strdup (message_list->cursor_uid);
    async_context->print_action = action;

    camel_folder_get_message (
        async_context->folder,
        async_context->message_uid,
        G_PRIORITY_DEFAULT, cancellable,
        mail_reader_print_get_message_cb,
        async_context);

    g_object_unref (activity);
}

static void
mail_reader_remove_attachments_cb (GObject *source_object,
                                   GAsyncResult *result,
                                   gpointer user_data)
{
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    e_mail_folder_remove_attachments_finish (
        CAMEL_FOLDER (source_object), result, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink,
            "mail:remove-attachments",
            local_error->message, NULL);
        g_error_free (local_error);
    }

    async_context_free (async_context);
}

void
e_mail_reader_remove_attachments (EMailReader *reader)
{
    EActivity *activity;
    AsyncContext *async_context;
    GCancellable *cancellable;
    CamelFolder *folder;
    GPtrArray *uids;

    g_return_if_fail (E_IS_MAIL_READER (reader));

    uids = e_mail_reader_get_selected_uids (reader);
    g_return_if_fail (uids != NULL);

    /* Remove attachments asynchronously. */

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);

    folder = e_mail_reader_ref_folder (reader);

    e_mail_folder_remove_attachments (
        folder, uids,
        G_PRIORITY_DEFAULT,
        cancellable,
        mail_reader_remove_attachments_cb,
        async_context);

    g_object_unref (folder);

    g_object_unref (activity);

    g_ptr_array_unref (uids);
}

static void
mail_reader_remove_duplicates_cb (GObject *source_object,
                                  GAsyncResult *result,
                                  gpointer user_data)
{
    EActivity *activity;
    EAlertSink *alert_sink;
    CamelFolder *folder;
    GHashTable *duplicates;
    GtkWindow *parent_window;
    guint n_duplicates;
    AsyncContext *async_context;
    GError *local_error = NULL;

    folder = CAMEL_FOLDER (source_object);
    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    parent_window = e_mail_reader_get_window (async_context->reader);

    duplicates = e_mail_folder_find_duplicate_messages_finish (
        folder, result, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((duplicates != NULL) && (local_error == NULL)) ||
        ((duplicates == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        async_context_free (async_context);
        g_error_free (local_error);
        return;

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink,
            "mail:find-duplicate-messages",
            local_error->message, NULL);
        async_context_free (async_context);
        g_error_free (local_error);
        return;
    }

    /* Finalize the activity here so we don't leave a message in
     * the task bar while prompting the user for confirmation. */
    e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
    g_clear_object (&async_context->activity);

    n_duplicates = g_hash_table_size (duplicates);

    if (n_duplicates == 0) {
        em_utils_prompt_user (
            parent_window, NULL,
            "mail:info-no-remove-duplicates",
            camel_folder_get_display_name (folder), NULL);
    } else {
        gchar *confirmation;
        gboolean proceed;

        confirmation = g_strdup_printf (ngettext (
            /* Translators: %s is replaced with a folder
             * name %u with count of duplicate messages. */
            "Folder '%s' contains %u duplicate message. "
            "Are you sure you want to delete it?",
            "Folder '%s' contains %u duplicate messages. "
            "Are you sure you want to delete them?",
            n_duplicates),
            camel_folder_get_display_name (folder),
            n_duplicates);

        proceed = em_utils_prompt_user (
            parent_window, NULL,
            "mail:ask-remove-duplicates",
            confirmation, NULL);

        if (proceed) {
            GHashTableIter iter;
            gpointer key;

            camel_folder_freeze (folder);

            g_hash_table_iter_init (&iter, duplicates);

            /* Mark duplicate messages for deletion. */
            while (g_hash_table_iter_next (&iter, &key, NULL))
                camel_folder_delete_message (folder, key);

            camel_folder_thaw (folder);
        }

        g_free (confirmation);
    }

    g_hash_table_destroy (duplicates);

    async_context_free (async_context);
}

void
e_mail_reader_remove_duplicates (EMailReader *reader)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;
    CamelFolder *folder;
    GPtrArray *uids;

    g_return_if_fail (E_IS_MAIL_READER (reader));

    uids = e_mail_reader_get_selected_uids (reader);
    g_return_if_fail (uids != NULL);

    /* Find duplicate messages asynchronously. */

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);

    folder = e_mail_reader_ref_folder (reader);

    e_mail_folder_find_duplicate_messages (
        folder, uids,
        G_PRIORITY_DEFAULT,
        cancellable,
        mail_reader_remove_duplicates_cb,
        async_context);

    g_object_unref (folder);

    g_object_unref (activity);

    g_ptr_array_unref (uids);
}

static void
mail_reader_edit_messages_cb (GObject *source_object,
                              GAsyncResult *result,
                              gpointer user_data)
{
    CamelFolder *folder;
    EShell *shell;
    EMailBackend *backend;
    EActivity *activity;
    EAlertSink *alert_sink;
    GHashTable *hash_table;
    GHashTableIter iter;
    gpointer key, value;
    AsyncContext *async_context;
    GError *local_error = NULL;

    folder = CAMEL_FOLDER (source_object);
    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    hash_table = e_mail_folder_get_multiple_messages_finish (
        folder, result, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((hash_table != NULL) && (local_error == NULL)) ||
        ((hash_table == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);
        goto exit;

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink,
            "mail:get-multiple-messages",
            local_error->message, NULL);
        g_error_free (local_error);
        goto exit;
    }

    backend = e_mail_reader_get_backend (async_context->reader);
    shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));

    /* Open each message in its own composer window. */

    g_hash_table_iter_init (&iter, hash_table);

    while (g_hash_table_iter_next (&iter, &key, &value)) {
        EMsgComposer *composer;
        CamelMimeMessage *message;
        const gchar *message_uid = NULL;

        if (async_context->replace)
            message_uid = (const gchar *) key;

        message = CAMEL_MIME_MESSAGE (value);

        camel_medium_remove_header (
            CAMEL_MEDIUM (message), "X-Mailer");

        composer = em_utils_edit_message (
            shell, folder, message, message_uid,
            async_context->keep_signature);

        e_mail_reader_composer_created (
            async_context->reader, composer, message);
    }

    g_hash_table_unref (hash_table);

    e_activity_set_state (activity, E_ACTIVITY_COMPLETED);

exit:
    async_context_free (async_context);
}

void
e_mail_reader_edit_messages (EMailReader *reader,
                             CamelFolder *folder,
                             GPtrArray *uids,
                             gboolean replace,
                             gboolean keep_signature)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uids != NULL);

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);
    async_context->replace = replace;
    async_context->keep_signature = keep_signature;

    e_mail_folder_get_multiple_messages (
        folder, uids,
        G_PRIORITY_DEFAULT,
        cancellable,
        mail_reader_edit_messages_cb,
        async_context);

    g_object_unref (activity);
}

static void
mail_reader_forward_attachment_cb (GObject *source_object,
                                   GAsyncResult *result,
                                   gpointer user_data)
{
    CamelFolder *folder;
    EMailBackend *backend;
    EActivity *activity;
    EAlertSink *alert_sink;
    CamelMimePart *part;
    CamelDataWrapper *content;
    EMsgComposer *composer;
    gchar *subject = NULL;
    AsyncContext *async_context;
    GError *local_error = NULL;

    folder = CAMEL_FOLDER (source_object);
    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    part = e_mail_folder_build_attachment_finish (
        folder, result, &subject, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((part != NULL) && (local_error == NULL)) ||
        ((part == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_warn_if_fail (subject == NULL);
        g_error_free (local_error);
        goto exit;

    } else if (local_error != NULL) {
        g_warn_if_fail (subject == NULL);
        e_alert_submit (
            alert_sink,
            "mail:get-multiple-messages",
            local_error->message, NULL);
        g_error_free (local_error);
        goto exit;
    }

    backend = e_mail_reader_get_backend (async_context->reader);

    composer = em_utils_forward_attachment (
        backend, part, subject, folder, async_context->uids);

    content = camel_medium_get_content (CAMEL_MEDIUM (part));
    if (CAMEL_IS_MIME_MESSAGE (content)) {
        e_mail_reader_composer_created (
            async_context->reader, composer,
            CAMEL_MIME_MESSAGE (content));
    } else {
        /* XXX What to do for the multipart/digest case?
         *     Extract the first message from the digest, or
         *     change the argument type to CamelMimePart and
         *     just pass the whole digest through?
         *
         *     This signal is primarily serving EMailBrowser,
         *     which can only forward one message at a time.
         *     So for the moment it doesn't matter, but still
         *     something to consider. */
        e_mail_reader_composer_created (
            async_context->reader, composer, NULL);
    }

    e_activity_set_state (activity, E_ACTIVITY_COMPLETED);

    g_object_unref (part);
    g_free (subject);

exit:
    async_context_free (async_context);
}

static void
mail_reader_forward_messages_cb (GObject *source_object,
                                 GAsyncResult *result,
                                 gpointer user_data)
{
    CamelFolder *folder;
    EMailBackend *backend;
    EActivity *activity;
    EAlertSink *alert_sink;
    GHashTable *hash_table;
    GHashTableIter iter;
    gpointer key, value;
    AsyncContext *async_context;
    GError *local_error = NULL;

    folder = CAMEL_FOLDER (source_object);
    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    backend = e_mail_reader_get_backend (async_context->reader);

    hash_table = e_mail_folder_get_multiple_messages_finish (
        folder, result, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((hash_table != NULL) && (local_error == NULL)) ||
        ((hash_table == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);
        goto exit;

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink,
            "mail:get-multiple-messages",
            local_error->message, NULL);
        g_error_free (local_error);
        goto exit;
    }

    /* Create a new composer window for each message. */

    g_hash_table_iter_init (&iter, hash_table);

    while (g_hash_table_iter_next (&iter, &key, &value)) {
        EMsgComposer *composer;
        CamelMimeMessage *message;
        const gchar *message_uid;

        message_uid = (const gchar *) key;
        message = CAMEL_MIME_MESSAGE (value);

        composer = em_utils_forward_message (
            backend, message,
            async_context->forward_style,
            folder, message_uid);

        e_mail_reader_composer_created (
            async_context->reader, composer, message);
    }

    g_hash_table_unref (hash_table);

    e_activity_set_state (activity, E_ACTIVITY_COMPLETED);

exit:
    async_context_free (async_context);
}

void
e_mail_reader_forward_messages (EMailReader *reader,
                                CamelFolder *folder,
                                GPtrArray *uids,
                                EMailForwardStyle style)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uids != NULL);

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);
    async_context->uids = g_ptr_array_ref (uids);
    async_context->forward_style = style;

    switch (style) {
        case E_MAIL_FORWARD_STYLE_ATTACHED:
            e_mail_folder_build_attachment (
                folder, uids,
                G_PRIORITY_DEFAULT,
                cancellable,
                mail_reader_forward_attachment_cb,
                async_context);
            break;

        case E_MAIL_FORWARD_STYLE_INLINE:
        case E_MAIL_FORWARD_STYLE_QUOTED:
            e_mail_folder_get_multiple_messages (
                folder, uids,
                G_PRIORITY_DEFAULT,
                cancellable,
                mail_reader_forward_messages_cb,
                async_context);
            break;

        default:
            g_warn_if_reached ();
    }

    g_object_unref (activity);
}

/* Helper for e_mail_reader_reply_to_message()
 * XXX This function belongs in e-html-utils.c */
static gboolean
html_contains_nonwhitespace (const gchar *html,
                             gint len)
{
    const gchar *cp;
    gunichar uc = 0;

    if (html == NULL || len <= 0)
        return FALSE;

    cp = html;

    while (cp != NULL && cp - html < len) {
        uc = g_utf8_get_char (cp);
        if (uc == 0)
            break;

        if (uc == '<') {
            /* skip until next '>' */
            uc = g_utf8_get_char (cp);
            while (uc != 0 && uc != '>' && cp - html < len) {
                cp = g_utf8_next_char (cp);
                uc = g_utf8_get_char (cp);
            }
            if (uc == 0)
                break;
        } else if (uc == '&') {
            /* sequence '&nbsp;' is a space */
            if (g_ascii_strncasecmp (cp, "&nbsp;", 6) == 0)
                cp = cp + 5;
            else
                break;
        } else if (!g_unichar_isspace (uc))
            break;

        cp = g_utf8_next_char (cp);
    }

    return cp - html < len - 1 && uc != 0;
}

static void
mail_reader_reply_message_parsed (GObject *object,
                                  GAsyncResult *result,
                                  gpointer user_data)
{
    EShell *shell;
    EMailBackend *backend;
    EMailReader *reader = E_MAIL_READER (object);
    EMailPartList *part_list;
    EMsgComposer *composer;
    CamelMimeMessage *message;
    AsyncContext *async_context;

    async_context = (AsyncContext *) user_data;

    part_list = e_mail_reader_parse_message_finish (reader, result);
    message = e_mail_part_list_get_message (part_list);

    backend = e_mail_reader_get_backend (async_context->reader);
    shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));

    composer = em_utils_reply_to_message (
        shell, message,
        async_context->folder,
        async_context->message_uid,
        async_context->reply_type,
        async_context->reply_style,
        part_list,
        async_context->address);

    e_mail_reader_composer_created (reader, composer, message);

    g_object_unref (part_list);

    async_context_free (async_context);
}

static void
mail_reader_get_message_ready_cb (GObject *source_object,
                                  GAsyncResult *result,
                                  gpointer user_data)
{
    EActivity *activity;
    EAlertSink *alert_sink;
    GCancellable *cancellable;
    CamelMimeMessage *message;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);
    cancellable = e_activity_get_cancellable (activity);

    message = camel_folder_get_message_finish (
        CAMEL_FOLDER (source_object), result, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((message != NULL) && (local_error == NULL)) ||
        ((message == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        async_context_free (async_context);
        g_error_free (local_error);
        return;

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-retrieve-message",
            local_error->message, NULL);
        async_context_free (async_context);
        g_error_free (local_error);
        return;
    }

    e_mail_reader_parse_message (
        async_context->reader,
        async_context->folder,
        async_context->message_uid,
        message,
        cancellable,
        mail_reader_reply_message_parsed,
        async_context);

    g_object_unref (message);
}

void
e_mail_reader_reply_to_message (EMailReader *reader,
                                CamelMimeMessage *src_message,
                                EMailReplyType reply_type)
{
    EShell *shell;
    EMailBackend *backend;
    EShellBackend *shell_backend;
    EMailDisplay *display;
    EMailPartList *part_list = NULL;
    GtkWidget *message_list;
    CamelMimeMessage *new_message;
    CamelInternetAddress *address = NULL;
    CamelFolder *folder;
    EMailReplyStyle reply_style;
    EWebView *web_view;
    struct _camel_header_raw *header;
    const gchar *uid;
    gchar *selection = NULL;
    gint length;
    gchar *mail_uri;
    CamelObjectBag *registry;
    EMsgComposer *composer;
    EMailPartValidityFlags validity_pgp_sum = 0;
    EMailPartValidityFlags validity_smime_sum = 0;

    /* This handles quoting only selected text in the reply.  If
     * nothing is selected or only whitespace is selected, fall
     * back to the normal em_utils_reply_to_message(). */

    g_return_if_fail (E_IS_MAIL_READER (reader));

    backend = e_mail_reader_get_backend (reader);
    display = e_mail_reader_get_mail_display (reader);
    message_list = e_mail_reader_get_message_list (reader);
    reply_style = e_mail_reader_get_reply_style (reader);

    shell_backend = E_SHELL_BACKEND (backend);
    shell = e_shell_backend_get_shell (shell_backend);

    web_view = E_WEB_VIEW (display);

    if (reply_type == E_MAIL_REPLY_TO_RECIPIENT) {
        const gchar *uri;

        uri = e_web_view_get_selected_uri (web_view);

        if (uri) {
            CamelURL *curl;

            curl = camel_url_new (uri, NULL);

            if (curl && curl->path && *curl->path) {
                address = camel_internet_address_new ();
                if (camel_address_decode (
                        CAMEL_ADDRESS (address),
                        curl->path) < 0) {
                    g_object_unref (address);
                    address = NULL;
                }
            }

            if (curl)
                camel_url_free (curl);
        }
    }

    uid = MESSAGE_LIST (message_list)->cursor_uid;
    g_return_if_fail (uid != NULL);

    folder = e_mail_reader_ref_folder (reader);

    if (!gtk_widget_get_visible (GTK_WIDGET (web_view)))
        goto whole_message;

    registry = e_mail_part_list_get_registry ();
    mail_uri = e_mail_part_build_uri (folder, uid, NULL, NULL);
    part_list = camel_object_bag_get (registry, mail_uri);
    g_free (mail_uri);

    if (!part_list) {
        goto whole_message;
    } else {
        GQueue queue = G_QUEUE_INIT;

        e_mail_part_list_queue_parts (part_list, NULL, &queue);

        while (!g_queue_is_empty (&queue)) {
            EMailPart *part = g_queue_pop_head (&queue);
            GList *head, *link;

            head = g_queue_peek_head_link (&part->validities);

            for (link = head; link != NULL; link = g_list_next (link)) {
                EMailPartValidityPair *vpair = link->data;

                if (vpair == NULL)
                    continue;

                if ((vpair->validity_type & E_MAIL_PART_VALIDITY_PGP) != 0)
                    validity_pgp_sum |= vpair->validity_type;
                if ((vpair->validity_type & E_MAIL_PART_VALIDITY_SMIME) != 0)
                    validity_smime_sum |= vpair->validity_type;
            }

            g_object_unref (part);
        }
    }

    if (src_message == NULL) {
        src_message = e_mail_part_list_get_message (part_list);
        if (src_message != NULL)
            g_object_ref (src_message);

        g_object_unref (part_list);
        part_list = NULL;

        g_return_if_fail (src_message != NULL);
    } else {
        g_object_unref (part_list);
        part_list = NULL;
    }

    if (!e_web_view_is_selection_active (web_view))
        goto whole_message;

    selection = e_web_view_get_selection_html (web_view);
    if (selection == NULL || *selection == '\0')
        goto whole_message;

    length = strlen (selection);
    if (!html_contains_nonwhitespace (selection, length))
        goto whole_message;

    new_message = camel_mime_message_new ();

    /* Filter out "content-*" headers. */
    header = CAMEL_MIME_PART (src_message)->headers;
    while (header != NULL) {
        if (g_ascii_strncasecmp (header->name, "content-", 8) != 0)
            camel_medium_add_header (
                CAMEL_MEDIUM (new_message),
                header->name, header->value);

        header = header->next;
    }

    camel_mime_part_set_encoding (
        CAMEL_MIME_PART (new_message),
        CAMEL_TRANSFER_ENCODING_8BIT);

    camel_mime_part_set_content (
        CAMEL_MIME_PART (new_message),
        selection, length, "text/html");

    g_object_unref (src_message);

    composer = em_utils_reply_to_message (
        shell, new_message, folder, uid,
        reply_type, reply_style, NULL, address);
    if (validity_pgp_sum != 0 || validity_smime_sum != 0) {
        GtkToggleAction *action;

        if ((validity_pgp_sum & E_MAIL_PART_VALIDITY_PGP) != 0) {
            if ((validity_pgp_sum & E_MAIL_PART_VALIDITY_SIGNED) != 0) {
                action = GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_SIGN (composer));
                gtk_toggle_action_set_active (action, TRUE);
            }

            if ((validity_pgp_sum & E_MAIL_PART_VALIDITY_ENCRYPTED) != 0) {
                action = GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_ENCRYPT (composer));
                gtk_toggle_action_set_active (action, TRUE);
            }
        }

        if ((validity_smime_sum & E_MAIL_PART_VALIDITY_SMIME) != 0) {
            if ((validity_smime_sum & E_MAIL_PART_VALIDITY_SIGNED) != 0) {
                action = GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_SIGN (composer));
                gtk_toggle_action_set_active (action, TRUE);
            }

            if ((validity_smime_sum & E_MAIL_PART_VALIDITY_ENCRYPTED) != 0) {
                action = GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_ENCRYPT (composer));
                gtk_toggle_action_set_active (action, TRUE);
            }
        }
    }

    e_mail_reader_composer_created (reader, composer, new_message);

    g_object_unref (new_message);

    g_free (selection);

    goto exit;

whole_message:
    if (src_message == NULL) {
        EActivity *activity;
        GCancellable *cancellable;
        AsyncContext *async_context;

        activity = e_mail_reader_new_activity (reader);
        cancellable = e_activity_get_cancellable (activity);

        async_context = g_slice_new0 (AsyncContext);
        async_context->activity = g_object_ref (activity);
        async_context->folder = g_object_ref (folder);
        async_context->reader = g_object_ref (reader);
        async_context->message_uid = g_strdup (uid);
        async_context->reply_type = reply_type;
        async_context->reply_style = reply_style;

        if (address != NULL)
            async_context->address = g_object_ref (address);

        camel_folder_get_message (
            async_context->folder,
            async_context->message_uid,
            G_PRIORITY_DEFAULT,
            cancellable,
            mail_reader_get_message_ready_cb,
            async_context);

        g_object_unref (activity);

    } else {
        composer = em_utils_reply_to_message (
            shell, src_message, folder, uid,
            reply_type, reply_style, part_list, address);

        e_mail_reader_composer_created (reader, composer, src_message);
    }

exit:
    g_clear_object (&address);
    g_clear_object (&folder);
}

static void
mail_reader_save_messages_cb (GObject *source_object,
                              GAsyncResult *result,
                              gpointer user_data)
{
    EActivity *activity;
    EAlertSink *alert_sink;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    e_mail_folder_save_messages_finish (
        CAMEL_FOLDER (source_object), result, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink,
            "mail:save-messages",
            local_error->message, NULL);
        g_error_free (local_error);
    }

    async_context_free (async_context);
}

void
e_mail_reader_save_messages (EMailReader *reader)
{
    EShell *shell;
    EActivity *activity;
    EMailBackend *backend;
    GCancellable *cancellable;
    AsyncContext *async_context;
    EShellBackend *shell_backend;
    CamelMessageInfo *info;
    CamelFolder *folder;
    GFile *destination;
    GPtrArray *uids;
    const gchar *message_uid;
    const gchar *title;
    gchar *suggestion = NULL;

    folder = e_mail_reader_ref_folder (reader);
    backend = e_mail_reader_get_backend (reader);

    uids = e_mail_reader_get_selected_uids (reader);
    g_return_if_fail (uids != NULL && uids->len > 0);

    if (uids->len > 1) {
        GtkWidget *message_list;

        message_list = e_mail_reader_get_message_list (reader);
        message_list_sort_uids (MESSAGE_LIST (message_list), uids);
    }

    message_uid = g_ptr_array_index (uids, 0);

    title = ngettext ("Save Message", "Save Messages", uids->len);

    /* Suggest as a filename the subject of the first message. */
    info = camel_folder_get_message_info (folder, message_uid);
    if (info != NULL) {
        const gchar *subject;

        subject = camel_message_info_subject (info);
        if (subject != NULL)
            suggestion = g_strconcat (subject, ".mbox", NULL);
        camel_folder_free_message_info (folder, info);
    }

    if (suggestion == NULL) {
        const gchar *basename;

        /* Translators: This is part of a suggested file name
         * used when saving a message or multiple messages to
         * mbox format, when the first message doesn't have a
         * subject.  The extension ".mbox" is appended to the
         * string; for example "Message.mbox". */
        basename = ngettext ("Message", "Messages", uids->len);
        suggestion = g_strconcat (basename, ".mbox", NULL);
    }

    shell_backend = E_SHELL_BACKEND (backend);
    shell = e_shell_backend_get_shell (shell_backend);

    destination = e_shell_run_save_dialog (
        shell, title, suggestion,
        "*.mbox:application/mbox,message/rfc822", NULL, NULL);

    if (destination == NULL)
        goto exit;

    /* Save messages asynchronously. */

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);

    e_mail_folder_save_messages (
        folder, uids,
        destination,
        G_PRIORITY_DEFAULT,
        cancellable,
        mail_reader_save_messages_cb,
        async_context);

    g_object_unref (activity);

    g_object_unref (destination);

exit:
    g_clear_object (&folder);
    g_ptr_array_unref (uids);
}

void
e_mail_reader_select_next_message (EMailReader *reader,
                                   gboolean or_else_previous)
{
    GtkWidget *message_list;
    gboolean hide_deleted;
    gboolean success;

    g_return_if_fail (E_IS_MAIL_READER (reader));

    hide_deleted = e_mail_reader_get_hide_deleted (reader);
    message_list = e_mail_reader_get_message_list (reader);

    success = message_list_select (
        MESSAGE_LIST (message_list),
        MESSAGE_LIST_SELECT_NEXT, 0, 0);

    if (!success && (hide_deleted || or_else_previous))
        message_list_select (
            MESSAGE_LIST (message_list),
            MESSAGE_LIST_SELECT_PREVIOUS, 0, 0);
}

/* Helper for e_mail_reader_create_filter_from_selected() */
static void
mail_reader_create_filter_cb (GObject *source_object,
                              GAsyncResult *result,
                              gpointer user_data)
{
    EActivity *activity;
    EMailBackend *backend;
    EMailSession *session;
    EAlertSink *alert_sink;
    CamelMimeMessage *message;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    message = camel_folder_get_message_finish (
        CAMEL_FOLDER (source_object), result, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((message != NULL) && (local_error == NULL)) ||
        ((message == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        async_context_free (async_context);
        g_error_free (local_error);
        return;

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-retrieve-message",
            local_error->message, NULL);
        async_context_free (async_context);
        g_error_free (local_error);
        return;
    }

    /* Finalize the activity here so we don't leave a message
     * in the task bar while displaying the filter editor. */
    e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
    g_clear_object (&async_context->activity);

    backend = e_mail_reader_get_backend (async_context->reader);
    session = e_mail_backend_get_session (backend);

    /* Switch to Incoming filter in case the message contains a Received header */
    if (g_str_equal (async_context->filter_source, E_FILTER_SOURCE_OUTGOING) &&
        camel_medium_get_header (CAMEL_MEDIUM (message), "received"))
        async_context->filter_source = E_FILTER_SOURCE_INCOMING;

    filter_gui_add_from_message (
        session, message,
        async_context->filter_source,
        async_context->filter_type);

    g_object_unref (message);

    async_context_free (async_context);
}

void
e_mail_reader_create_filter_from_selected (EMailReader *reader,
                                           gint filter_type)
{
    EShell *shell;
    EActivity *activity;
    EMailBackend *backend;
    AsyncContext *async_context;
    GCancellable *cancellable;
    ESourceRegistry *registry;
    CamelFolder *folder;
    GPtrArray *uids;
    const gchar *filter_source;
    const gchar *message_uid;

    g_return_if_fail (E_IS_MAIL_READER (reader));

    backend = e_mail_reader_get_backend (reader);
    shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
    registry = e_shell_get_registry (shell);

    folder = e_mail_reader_ref_folder (reader);
    g_return_if_fail (folder != NULL);

    if (em_utils_folder_is_sent (registry, folder) ||
        em_utils_folder_is_outbox (registry, folder))
        filter_source = E_FILTER_SOURCE_OUTGOING;
    else
        filter_source = E_FILTER_SOURCE_INCOMING;

    uids = e_mail_reader_get_selected_uids (reader);
    g_return_if_fail (uids != NULL && uids->len == 1);
    message_uid = g_ptr_array_index (uids, 0);

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->reader = g_object_ref (reader);
    async_context->filter_source = filter_source;
    async_context->filter_type = filter_type;

    camel_folder_get_message (
        folder, message_uid,
        G_PRIORITY_DEFAULT,
        cancellable,
        mail_reader_create_filter_cb,
        async_context);

    g_object_unref (activity);

    g_ptr_array_unref (uids);

    g_object_unref (folder);
}

/* Helper for e_mail_reader_create_vfolder_from_selected() */
static void
mail_reader_create_vfolder_cb (GObject *source_object,
                               GAsyncResult *result,
                               gpointer user_data)
{
    EActivity *activity;
    EMailBackend *backend;
    EMailSession *session;
    EAlertSink *alert_sink;
    CamelMimeMessage *message;
    CamelFolder *use_folder;
    AsyncContext *async_context;
    GError *local_error = NULL;

    async_context = (AsyncContext *) user_data;

    activity = async_context->activity;
    alert_sink = e_activity_get_alert_sink (activity);

    message = camel_folder_get_message_finish (
        CAMEL_FOLDER (source_object), result, &local_error);

    /* Sanity check. */
    g_return_if_fail (
        ((message != NULL) && (local_error == NULL)) ||
        ((message == NULL) && (local_error != NULL)));

    if (e_activity_handle_cancellation (activity, local_error)) {
        async_context_free (async_context);
        g_error_free (local_error);
        return;

    } else if (local_error != NULL) {
        e_alert_submit (
            alert_sink, "mail:no-retrieve-message",
            local_error->message, NULL);
        async_context_free (async_context);
        g_error_free (local_error);
        return;
    }

    /* Finalize the activity here so we don't leave a message
     * in the task bar while displaying the vfolder editor. */
    e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
    g_clear_object (&async_context->activity);

    backend = e_mail_reader_get_backend (async_context->reader);
    session = e_mail_backend_get_session (backend);

    use_folder = async_context->folder;
    if (CAMEL_IS_VEE_FOLDER (use_folder)) {
        CamelStore *parent_store;
        CamelVeeFolder *vfolder;

        parent_store = camel_folder_get_parent_store (use_folder);
        vfolder = CAMEL_VEE_FOLDER (use_folder);

        if (CAMEL_IS_VEE_STORE (parent_store) &&
            vfolder == camel_vee_store_get_unmatched_folder (CAMEL_VEE_STORE (parent_store))) {
            /* use source folder instead of the Unmatched folder */
            use_folder = camel_vee_folder_get_vee_uid_folder (
                vfolder, async_context->message_uid);
        }
    }

    vfolder_gui_add_from_message (
        session, message,
        async_context->filter_type,
        use_folder);

    g_object_unref (message);

    async_context_free (async_context);
}

void
e_mail_reader_create_vfolder_from_selected (EMailReader *reader,
                                            gint vfolder_type)
{
    EActivity *activity;
    GCancellable *cancellable;
    AsyncContext *async_context;
    GPtrArray *uids;
    const gchar *message_uid;

    g_return_if_fail (E_IS_MAIL_READER (reader));

    uids = e_mail_reader_get_selected_uids (reader);
    g_return_if_fail (uids != NULL && uids->len == 1);
    message_uid = g_ptr_array_index (uids, 0);

    activity = e_mail_reader_new_activity (reader);
    cancellable = e_activity_get_cancellable (activity);

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->folder = e_mail_reader_ref_folder (reader);
    async_context->reader = g_object_ref (reader);
    async_context->message_uid = g_strdup (message_uid);
    async_context->filter_type = vfolder_type;

    camel_folder_get_message (
        async_context->folder,
        async_context->message_uid,
        G_PRIORITY_DEFAULT,
        cancellable,
        mail_reader_create_vfolder_cb,
        async_context);

    g_object_unref (activity);

    g_ptr_array_unref (uids);
}

static void
mail_reader_parse_message_run (GSimpleAsyncResult *simple,
                               GObject *object,
                               GCancellable *cancellable)
{
    EMailReader *reader = E_MAIL_READER (object);
    CamelObjectBag *registry;
    EMailPartList *part_list;
    AsyncContext *async_context;
    gchar *mail_uri;

    async_context = g_simple_async_result_get_op_res_gpointer (simple);

    registry = e_mail_part_list_get_registry ();

    mail_uri = e_mail_part_build_uri (
        async_context->folder,
        async_context->message_uid, NULL, NULL);

    part_list = camel_object_bag_reserve (registry, mail_uri);
    if (part_list == NULL) {
        EMailBackend *mail_backend;
        EMailSession *mail_session;
        EMailParser *parser;

        mail_backend = e_mail_reader_get_backend (reader);
        mail_session = e_mail_backend_get_session (mail_backend);

        parser = e_mail_parser_new (CAMEL_SESSION (mail_session));

        part_list = e_mail_parser_parse_sync (
            parser,
            async_context->folder,
            async_context->message_uid,
            async_context->message,
            cancellable);

        g_object_unref (parser);

        if (part_list == NULL)
            camel_object_bag_abort (registry, mail_uri);
        else
            camel_object_bag_add (registry, mail_uri, part_list);
    }

    g_free (mail_uri);

    async_context->part_list = part_list;
}

void
e_mail_reader_parse_message (EMailReader *reader,
                             CamelFolder *folder,
                             const gchar *message_uid,
                             CamelMimeMessage *message,
                             GCancellable *cancellable,
                             GAsyncReadyCallback callback,
                             gpointer user_data)
{
    GSimpleAsyncResult *simple;
    AsyncContext *async_context;
    EActivity *activity;

    g_return_if_fail (E_IS_MAIL_READER (reader));
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (message_uid != NULL);
    g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

    activity = e_mail_reader_new_activity (reader);
    e_activity_set_cancellable (activity, cancellable);
    e_activity_set_text (activity, _("Parsing message"));

    async_context = g_slice_new0 (AsyncContext);
    async_context->activity = g_object_ref (activity);
    async_context->folder = g_object_ref (folder);
    async_context->message_uid = g_strdup (message_uid);
    async_context->message = g_object_ref (message);

    simple = g_simple_async_result_new (
        G_OBJECT (reader), callback, user_data,
        e_mail_reader_parse_message);

    g_simple_async_result_set_check_cancellable (simple, cancellable);

    g_simple_async_result_set_op_res_gpointer (
        simple, async_context, (GDestroyNotify) async_context_free);

    g_simple_async_result_run_in_thread (
        simple, mail_reader_parse_message_run,
        G_PRIORITY_DEFAULT, cancellable);

    g_object_unref (simple);
    g_object_unref (activity);
}

EMailPartList *
e_mail_reader_parse_message_finish (EMailReader *reader,
                                    GAsyncResult *result)
{
    GSimpleAsyncResult *simple;
    AsyncContext *async_context;

    g_return_val_if_fail (
        g_simple_async_result_is_valid (
        result, G_OBJECT (reader),
        e_mail_reader_parse_message), NULL);

    simple = G_SIMPLE_ASYNC_RESULT (result);
    async_context = g_simple_async_result_get_op_res_gpointer (simple);

    if (async_context->part_list != NULL)
        g_object_ref (async_context->part_list);

    return async_context->part_list;
}