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















                                                                             

                                                        

                                                        
                                       


   



                    

                           
                   
                        
                           
 

                          













                                                
                                 
 



                                                          



                                                          


                                           
                              
                         

  



                    



                                                   




                             











                                                          






















                                                               



                                                                      

                                                           
 




                                                                      

 





                                                           
                              

                                         

                                     
                                                                   
                                                                                   
 
                                                   
                                                       
                                                                                          
 

                                                                       




                                                                      

                                                        

         
                                                                         
                                                           








                                                                     

                                                                            
                                      
 
                                                    
                                    

                                 

                                                 


                                                                                           
                                                         
         






                                                          
                              

                                         
 


                                                                     
 



                                                                           
 

                                                                                   
 

                                                                                          
 
                                                                            
                                                                 
 


                                                                            
 
                                                    
                                    

                                 

                                                


                                                                                           
                                                         
         



                                                   
                                                







                                                
                                                                 

                                                   















                                                                          
                                


                                                   


                                               
                                  

 

                                                           
                                                       




















                                                                     




                                                        
                              
                                  
                           


                             
                                                       
                                                
 




                                                                           

                                 


                                            







                                                                            

                                                                                 







                                                                    
 
                                                     
 

                                                                         











                                                             
                                                                 
 
                                                                               

                                                                    
                               



                                                                
                                                         



                                              
                                                        
                                                                  
 

                              

                            







                                                   



                                                          


                                                             

                                                   




                                                       


                           
                                                                          











                                                   

                                                              
                                                         
                                                      
 
                      
                               

                                     
                              
                               

                                    

                                             





                                                                         



                                                          
                                              
                                                            
 
                                                       
                                                             
 
                                 


                                                     
                               

                                                     


                                                           

                                                                         
 



                                                                    
 
                                                                            
 


                                                                     
 


                                       
                                                                        
                                             
 







                                                                          

                         
         
 
                                                                 
 

                                                                         
 










                                                                            


                                            
                                                                      









                                                                          

                         

         
                                                                 
 
                     


                                                    
                                                                   




                                                              

                                                             
                                                      
 
                      
                               



                                     








                                      







                                                                         
                                              
                                                            



                                                                   






                                                                         
                                     
 
                                                                            
 


                                                                     
 




                                                                              
                                             
 


                                                                      
 



                                                                          

                         





                                                                         
 



                                                                    
                                     






                                                                            




                                                                            
                                             
 


                                                                     
 



                                                                          

                         

         
                                                                 
 















                                                                               


                         

                                                    
                                   
                                                         




                                                              
                                                         



                                                         
                                                           
 


                                          
                          
                         
                                       
 
                                                                  
 

                                                          

                                             
                                                       

                                                  
                            
 


                                                                    
                                                    

                                                                     
 







                                                                             
 

                                 














                                                                            








                                                          





                                                                   





















                                                                


                                                             


                                                                 

                                                               

                                   
                              
                                            


                                                                  


                                                                 




































                                                                            





















                                                                       
                                                   

                                    


                                                           

                                                       



                                               


                                                             




                                                                       

                                       





                                                   

                                                                        




                          
                                              

                                              








                                                      

                                                 






                                                         
                                             
                                                                          
                                   

                                                                
           






                                                           
                                             












                                                     
                                              




                                                                       
           

                                                 
                                                         




                                          
           
                                                     
 
                                                         




                                                                      
           

                                                   
                                                         

                     
                                                                      

 
           
                                               

                                          







































                                                                      









                                                     
                                                                              

 
           

                                          
                                  

                                     
                                      
                                  
 
                                                   



                                                          

                                                           
 

                                                         


                                              
                                                       
 






                                                          









                                                          
                          









                                                       


















                                                                 

                                                                       

                                               

                                                            


                                               

                                                            


                                               
                                                                            
 
                                         
 



                                        
                                          
                                        

                                        
 

                                                                           


           
                                                    



                                                


                                                                       

                                                               
                                                       



                                                             

                                                                                









                                            


           
                                           
 






                                                             

 

                                                  
 
                                                                 
 
                                      

 

                                                     






                                          
                                    
                           

                                                                   

                                                                  
 
                                                                 



                                                          


                                                         
                                                       


                                                                    



                              
                                                          




                                                                               




























                                                                  
 
 
/*
 * e-mail-backend.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/>
 *
 * Authors:
 *   Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 * Copyright (C) 2009 Intel Corporation
 *
 */

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

#include "e-mail-backend.h"

#include <string.h>
#include <glib/gstdio.h>
#include <glib/gi18n-lib.h>

#include <shell/e-shell.h>

#include <libemail-engine/e-mail-folder-utils.h>
#include <libemail-engine/e-mail-session.h>
#include <libemail-engine/e-mail-store-utils.h>
#include <libemail-engine/mail-config.h>
#include <libemail-engine/mail-folder-cache.h>
#include <libemail-engine/mail-ops.h>

#include <mail/e-mail-migrate.h>
#include <mail/e-mail-ui-session.h>
#include <mail/em-event.h>
#include <mail/em-folder-tree-model.h>
#include <mail/em-utils.h>
#include <mail/mail-autofilter.h>
#include <mail/mail-send-recv.h>
#include <mail/mail-vfolder-ui.h>

#define E_MAIL_BACKEND_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_MAIL_BACKEND, EMailBackendPrivate))

#define E_MAIL_BACKEND_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_MAIL_BACKEND, EMailBackendPrivate))

#define QUIT_POLL_INTERVAL 1  /* seconds */

struct _EMailBackendPrivate {
    EMailSession *session;
    GHashTable *jobs;
};

enum {
    PROP_0,
    PROP_SESSION
};

/* FIXME Kill this thing.  It's a horrible hack. */
extern gint camel_application_is_exiting;

G_DEFINE_ABSTRACT_TYPE (
    EMailBackend,
    e_mail_backend,
    E_TYPE_SHELL_BACKEND)

static const gchar *
mail_shell_backend_get_data_dir (EShellBackend *backend)
{
    return mail_session_get_data_dir ();
}

static const gchar *
mail_shell_backend_get_config_dir (EShellBackend *backend)
{
    return mail_session_get_config_dir ();
}

static gchar *
mail_backend_uri_to_evname (const gchar *uri,
                            const gchar *prefix)
{
    const gchar *data_dir;
    gchar *basename;
    gchar *filename;
    gchar *safe;

    /* Converts a folder URI to a GalView filename. */

    data_dir = mail_session_get_data_dir ();

    safe = g_strdup (uri);
    e_filename_make_safe (safe);
    basename = g_strdup_printf ("%s%s.xml", prefix, safe);
    filename = g_build_filename (data_dir, basename, NULL);
    g_free (basename);
    g_free (safe);

    return filename;
}

/* Callback for various asynchronous CamelStore operations where
 * the EActivity's reference count is used as a counting semaphore. */
static void
mail_backend_store_operation_done_cb (CamelStore *store,
                                      GAsyncResult *result,
                                      EActivity *activity)
{
    /* FIXME Not checking result for error.  To fix this, we need
     *       separate callbacks to call different finish functions
     *       and then submit an EAlert on error. */

    g_object_unref (activity);
}

static void
mail_backend_prepare_for_offline_cb (EShell *shell,
                                     EActivity *activity,
                                     EMailBackend *backend)
{
    GtkWindow *window;
    EMailSession *session;
    EMailAccountStore *account_store;
    GQueue queue = G_QUEUE_INIT;
    gboolean synchronize = FALSE;

    if (e_shell_backend_is_started (E_SHELL_BACKEND (backend)))
        e_shell_backend_add_activity (E_SHELL_BACKEND (backend), activity);

    window = e_shell_get_active_window (shell);
    session = e_mail_backend_get_session (backend);
    account_store = e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (session));

    if (e_shell_get_network_available (shell) &&
        e_shell_backend_is_started (E_SHELL_BACKEND (backend)))
        synchronize = em_utils_prompt_user (
            window, NULL, "mail:ask-quick-offline", NULL);

    if (!synchronize) {
        mail_cancel_all ();
        camel_session_set_network_available (
            CAMEL_SESSION (session), FALSE);
    }

    /* Set the cancellable only here, because mail_cancel_all() would
     * cancel the just added CamelOperation as well. */
    if (e_shell_backend_is_started (E_SHELL_BACKEND (backend)) &&
        !e_activity_get_cancellable (activity)) {
        GCancellable *cancellable;

        cancellable = camel_operation_new ();
        e_activity_set_cancellable (activity, cancellable);
        g_object_unref (cancellable);
    }

    e_mail_account_store_queue_enabled_services (account_store, &queue);
    while (!g_queue_is_empty (&queue)) {
        CamelService *service;

        service = g_queue_pop_head (&queue);
        if (service == NULL)
            continue;

        if (CAMEL_IS_STORE (service))
            e_mail_store_go_offline (
                CAMEL_STORE (service), G_PRIORITY_DEFAULT,
                e_activity_get_cancellable (activity),
                (GAsyncReadyCallback) mail_backend_store_operation_done_cb,
                g_object_ref (activity));
    }
}

static void
mail_backend_prepare_for_online_cb (EShell *shell,
                                    EActivity *activity,
                                    EMailBackend *backend)
{
    EMailSession *session;
    EMailAccountStore *account_store;
    GQueue queue = G_QUEUE_INIT;

    if (e_shell_backend_is_started (E_SHELL_BACKEND (backend))) {
        if (!e_activity_get_cancellable (activity)) {
            GCancellable *cancellable;

            cancellable = camel_operation_new ();
            e_activity_set_cancellable (activity, cancellable);
            g_object_unref (cancellable);
        }

        e_shell_backend_add_activity (E_SHELL_BACKEND (backend), activity);
    }

    session = e_mail_backend_get_session (backend);
    account_store = e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (session));

    camel_session_set_network_available (CAMEL_SESSION (session), TRUE);
    camel_session_set_online (CAMEL_SESSION (session), TRUE);

    e_mail_account_store_queue_enabled_services (account_store, &queue);
    while (!g_queue_is_empty (&queue)) {
        CamelService *service;

        service = g_queue_pop_head (&queue);
        if (service == NULL)
            continue;

        if (CAMEL_IS_STORE (service))
            e_mail_store_go_online (
                CAMEL_STORE (service), G_PRIORITY_DEFAULT,
                e_activity_get_cancellable (activity),
                (GAsyncReadyCallback) mail_backend_store_operation_done_cb,
                g_object_ref (activity));
    }
}

/* Helper for mail_backend_prepare_for_quit_cb() */
static void
mail_backend_delete_junk (CamelService *service,
                          EMailBackend *backend)
{
    CamelFolder *folder;
    GPtrArray *uids;
    guint32 flags;
    guint32 mask;
    guint ii;

    /* FIXME camel_store_get_junk_folder_sync() may block. */
    folder = camel_store_get_junk_folder_sync (
        CAMEL_STORE (service), NULL, NULL);
    if (folder == NULL)
        return;

    uids = camel_folder_get_uids (folder);
    flags = mask = CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN;

    camel_folder_freeze (folder);

    for (ii = 0; ii < uids->len; ii++) {
        const gchar *uid = uids->pdata[ii];
        camel_folder_set_message_flags (folder, uid, flags, mask);
    }

    camel_folder_thaw (folder);

    camel_folder_free_uids (folder, uids);
    g_object_unref (folder);
}

/* Helper for mail_backend_prepare_for_quit_cb() */
static gboolean
mail_backend_poll_to_quit (EActivity *activity)
{
    return mail_msg_active ();
}

static gboolean
mail_backend_service_is_enabled (ESourceRegistry *registry,
                                 CamelService *service)
{
    const gchar *uid;
    ESource *source;
    gboolean enabled;

    g_return_val_if_fail (registry != NULL, FALSE);
    g_return_val_if_fail (service != NULL, FALSE);

    uid = camel_service_get_uid (service);
    g_return_val_if_fail (uid != NULL, FALSE);

    source = e_source_registry_ref_source (registry, uid);
    if (!source)
        return FALSE;

    enabled = e_source_registry_check_enabled (registry, source);
    g_object_unref (source);

    return enabled;
}

static void
mail_backend_prepare_for_quit_cb (EShell *shell,
                                  EActivity *activity,
                                  EMailBackend *backend)
{
    EMailSession *session;
    ESourceRegistry *registry;
    GList *list, *link;
    gboolean delete_junk;
    gboolean empty_trash;

    session = e_mail_backend_get_session (backend);
    registry = e_shell_get_registry (shell);

    delete_junk = e_mail_backend_delete_junk_policy_decision (backend);
    empty_trash = e_mail_backend_empty_trash_policy_decision (backend);

    camel_application_is_exiting = TRUE;

    mail_vfolder_shutdown ();

    /* Cancel all pending activities. */
    mail_cancel_all ();

    list = camel_session_list_services (CAMEL_SESSION (session));

    if (delete_junk) {
        for (link = list; link != NULL; link = g_list_next (link)) {
            CamelService *service;

            service = CAMEL_SERVICE (link->data);

            if (!CAMEL_IS_STORE (service) ||
                !mail_backend_service_is_enabled (registry, service))
                continue;

            mail_backend_delete_junk (service, backend);
        }
    }

    for (link = list; link != NULL; link = g_list_next (link)) {
        CamelService *service;

        service = CAMEL_SERVICE (link->data);

        if (!CAMEL_IS_STORE (service) ||
            !mail_backend_service_is_enabled (registry, service))
            continue;

        /* FIXME Not passing a GCancellable. */
        /* FIXME This operation should be queued. */
        camel_store_synchronize (
            CAMEL_STORE (service),
            empty_trash, G_PRIORITY_DEFAULT,
            NULL, (GAsyncReadyCallback)
            mail_backend_store_operation_done_cb,
            g_object_ref (activity));
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);

    /* Now we poll until all activities are actually cancelled or finished.
     * Reffing the activity delays quitting; the reference count
     * acts like a counting semaphore. */
    if (mail_msg_active ())
        g_timeout_add_seconds_full (
            G_PRIORITY_DEFAULT, QUIT_POLL_INTERVAL,
            (GSourceFunc) mail_backend_poll_to_quit,
            g_object_ref (activity),
            (GDestroyNotify) g_object_unref);
}

static void
mail_backend_quit_requested_cb (EShell *shell,
                                EShellQuitReason reason,
                                EShellBackend *mail_shell_backend)
{
    EMailBackend *backend;
    EMailSession *session;
    CamelFolder *folder;
    GtkWindow *window;
    gint response;

    window = e_shell_get_active_window (shell);

    /* We can quit immediately if offline. */
    if (!e_shell_get_online (shell))
        return;

    /* Or if another Evolution process asked us to. */
    if (reason == E_SHELL_QUIT_REMOTE_REQUEST)
        return;

    if (!e_shell_backend_is_started (mail_shell_backend))
        return;

    /* Check Outbox for any unsent messages. */

    backend = E_MAIL_BACKEND (mail_shell_backend);
    session = e_mail_backend_get_session (backend);

    folder = e_mail_session_get_local_folder (
        session, E_MAIL_LOCAL_FOLDER_OUTBOX);
    if (folder == NULL)
        return;

    if (camel_folder_summary_get_visible_count (folder->summary) == 0)
        return;

    response = e_alert_run_dialog_for_args (
        window, "mail:exit-unsaved", NULL);

    if (response == GTK_RESPONSE_YES)
        return;

    e_shell_cancel_quit (shell);
}

static void
mail_backend_folder_deleted_cb (MailFolderCache *folder_cache,
                                CamelStore *store,
                                const gchar *folder_name,
                                EMailBackend *backend)
{
    EShell *shell;
    CamelStoreClass *class;
    ESourceRegistry *registry;
    EShellBackend *shell_backend;
    EMailSession *session;
    EAlertSink *alert_sink;
    GList *list, *link;
    const gchar *extension_name;
    const gchar *local_drafts_folder_uri;
    const gchar *local_sent_folder_uri;
    gchar *uri;

    /* Check whether the deleted folder was a designated Drafts or
     * Sent folder for any mail account, and if so revert the setting
     * to the equivalent local folder, which is always present. */

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

    class = CAMEL_STORE_GET_CLASS (store);
    g_return_if_fail (class->equal_folder_name != NULL);

    session = e_mail_backend_get_session (backend);
    alert_sink = e_mail_backend_get_alert_sink (backend);

    local_drafts_folder_uri =
        e_mail_session_get_local_folder_uri (
        session, E_MAIL_LOCAL_FOLDER_DRAFTS);

    local_sent_folder_uri =
        e_mail_session_get_local_folder_uri (
        session, E_MAIL_LOCAL_FOLDER_SENT);

    uri = e_mail_folder_uri_build (store, folder_name);

    extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
    list = e_source_registry_list_sources (registry, extension_name);

    for (link = list; link != NULL; link = g_list_next (link)) {
        ESource *source = E_SOURCE (link->data);
        ESourceExtension *extension;
        const gchar *drafts_folder_uri;

        extension = e_source_get_extension (source, extension_name);

        drafts_folder_uri =
            e_source_mail_composition_get_drafts_folder (
            E_SOURCE_MAIL_COMPOSITION (extension));

        if (!drafts_folder_uri)
            continue;

        if (class->equal_folder_name (drafts_folder_uri, uri)) {
            GError *error = NULL;

            e_source_mail_composition_set_drafts_folder (
                E_SOURCE_MAIL_COMPOSITION (extension),
                local_drafts_folder_uri);

            /* FIXME This is a blocking D-Bus method call. */
            if (!e_source_write_sync (source, NULL, &error)) {
                g_warning ("%s", error->message);
                g_error_free (error);
            }
        }
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);

    extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
    list = e_source_registry_list_sources (registry, extension_name);

    for (link = list; link != NULL; link = g_list_next (link)) {
        ESource *source = E_SOURCE (link->data);
        ESourceExtension *extension;
        const gchar *sent_folder_uri;

        extension = e_source_get_extension (source, extension_name);

        sent_folder_uri =
            e_source_mail_submission_get_sent_folder (
            E_SOURCE_MAIL_SUBMISSION (extension));

        if (sent_folder_uri == NULL)
            continue;

        if (class->equal_folder_name (sent_folder_uri, uri)) {
            GError *error = NULL;

            e_source_mail_submission_set_sent_folder (
                E_SOURCE_MAIL_SUBMISSION (extension),
                local_sent_folder_uri);

            /* FIXME This is a blocking D-Bus method call. */
            if (!e_source_write_sync (source, NULL, &error)) {
                g_warning ("%s", error->message);
                g_error_free (error);
            }
        }
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);

    g_free (uri);

    /* This does something completely different.
     * XXX Make it a separate signal handler? */
    mail_filter_delete_folder (store, folder_name, alert_sink);
}

static void
mail_backend_folder_renamed_cb (MailFolderCache *folder_cache,
                                CamelStore *store,
                                const gchar *old_folder_name,
                                const gchar *new_folder_name,
                                EMailBackend *backend)
{
    EShell *shell;
    CamelStoreClass *class;
    ESourceRegistry *registry;
    EShellBackend *shell_backend;
    GList *list, *link;
    const gchar *extension_name;
    gchar *old_uri;
    gchar *new_uri;
    gint ii;

    const gchar *cachenames[] = {
        "views/current_view-",
        "views/custom_view-"
    };

    /* Check whether the renamed folder was a designated Drafts or
     * Sent folder for any mail account, and if so update the setting
     * to the new folder name. */

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

    class = CAMEL_STORE_GET_CLASS (store);
    g_return_if_fail (class->equal_folder_name != NULL);

    old_uri = e_mail_folder_uri_build (store, old_folder_name);
    new_uri = e_mail_folder_uri_build (store, new_folder_name);

    extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
    list = e_source_registry_list_sources (registry, extension_name);

    for (link = list; link != NULL; link = g_list_next (link)) {
        ESource *source = E_SOURCE (link->data);
        ESourceExtension *extension;
        const gchar *drafts_folder_uri;
        gboolean need_update;

        extension = e_source_get_extension (source, extension_name);

        drafts_folder_uri =
            e_source_mail_composition_get_drafts_folder (
            E_SOURCE_MAIL_COMPOSITION (extension));

        need_update =
            (drafts_folder_uri != NULL) &&
            class->equal_folder_name (drafts_folder_uri, old_uri);

        if (need_update) {
            GError *error = NULL;

            e_source_mail_composition_set_drafts_folder (
                E_SOURCE_MAIL_COMPOSITION (extension),
                new_uri);

            /* FIXME This is a blocking D-Bus method call. */
            if (!e_source_write_sync (source, NULL, &error)) {
                g_warning ("%s", error->message);
                g_error_free (error);
            }
        }
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);

    extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
    list = e_source_registry_list_sources (registry, extension_name);

    for (link = list; link != NULL; link = g_list_next (link)) {
        ESource *source = E_SOURCE (link->data);
        ESourceExtension *extension;
        const gchar *sent_folder_uri;
        gboolean need_update;

        extension = e_source_get_extension (source, extension_name);

        sent_folder_uri =
            e_source_mail_submission_get_sent_folder (
            E_SOURCE_MAIL_SUBMISSION (extension));

        need_update =
            (sent_folder_uri != NULL) &&
            class->equal_folder_name (sent_folder_uri, old_uri);

        if (need_update) {
            GError *error = NULL;

            e_source_mail_submission_set_sent_folder (
                E_SOURCE_MAIL_SUBMISSION (extension),
                new_uri);

            /* FIXME This is a blocking D-Bus method call. */
            if (!e_source_write_sync (source, NULL, &error)) {
                g_warning ("%s", error->message);
                g_error_free (error);
            }
        }
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);

    /* Rename GalView files. */

    for (ii = 0; ii < G_N_ELEMENTS (cachenames); ii++) {
        gchar *oldname;
        gchar *newname;

        oldname = mail_backend_uri_to_evname (old_uri, cachenames[ii]);
        newname = mail_backend_uri_to_evname (new_uri, cachenames[ii]);

        /* Ignore errors; doesn't matter. */
        g_rename (oldname, newname);

        g_free (oldname);
        g_free (newname);
    }

    g_free (old_uri);
    g_free (new_uri);

    /* This does something completely different.
     * XXX Make it a separate signal handler? */
    mail_filter_rename_folder (
        store, old_folder_name, new_folder_name);
}

static void
mail_backend_folder_changed_cb (MailFolderCache *folder_cache,
                                CamelStore *store,
                                const gchar *folder_name,
                                gint new_messages,
                                const gchar *msg_uid,
                                const gchar *msg_sender,
                                const gchar *msg_subject,
                                EMailBackend *mail_backend)
{
    EMEvent *event = em_event_peek ();
    EMEventTargetFolder *target;
    EMFolderTreeModel *model;
    gchar *folder_uri;
    gint folder_type;
    CamelFolderInfoFlags flags = 0;

    folder_uri = e_mail_folder_uri_build (store, folder_name);

    mail_folder_cache_get_folder_info_flags (
        folder_cache, store, folder_name, &flags);

    target = em_event_target_new_folder (
        event, store, folder_uri, new_messages,
        msg_uid, msg_sender, msg_subject);

    g_free (folder_uri);

    folder_type = (flags & CAMEL_FOLDER_TYPE_MASK);
    target->is_inbox = (folder_type == CAMEL_FOLDER_TYPE_INBOX);

    model = em_folder_tree_model_get_default ();
    target->display_name = em_folder_tree_model_get_folder_name (
        model, store, folder_name);

    if (target->new > 0) {
        EShell *shell;
        EShellBackend *shell_backend;

        shell_backend = E_SHELL_BACKEND (mail_backend);
        shell = e_shell_backend_get_shell (shell_backend);
        e_shell_event (shell, "mail-icon", (gpointer) "mail-unread");
    }

    /**
     * @Event: folder.changed
     * @Title: Folder changed
     * @Target: EMEventTargetFolder
     *
     * folder.changed is emitted whenever a folder changes.  There is no
     * detail on how the folder has changed.
     *
     * UPDATE: We tell the number of new UIDs added rather than the new
     * mails received.
     */
    e_event_emit (
        (EEvent *) event, "folder.changed",
        (EEventTarget *) target);
}

static void
mail_backend_job_started_cb (CamelSession *session,
                             GCancellable *cancellable,
                             EShellBackend *shell_backend)
{
    EMailBackendPrivate *priv;
    EActivity *activity;

    priv = E_MAIL_BACKEND_GET_PRIVATE (shell_backend);

    /* Make sure this operation shows up in the user interface.
     * This message should get overridden, if not it's a bug in
     * whatever CamelService submitted this. */
    camel_operation_push_message (
        cancellable, _("Unknown background operation"));

    activity = e_activity_new ();
    e_activity_set_cancellable (activity, cancellable);
    e_shell_backend_add_activity (shell_backend, activity);

    /* The hash table takes ownership of the activity. */
    g_hash_table_insert (priv->jobs, cancellable, activity);
}

static void
mail_backend_job_finished_cb (CamelSession *session,
                              GCancellable *cancellable,
                              const GError *error,
                              EShellBackend *shell_backend)
{
    EMailBackendPrivate *priv;
    EShellBackendClass *class;
    EActivity *activity;
    const gchar *description;

    priv = E_MAIL_BACKEND_GET_PRIVATE (shell_backend);
    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);

    /* Pop the generic "background operation" message. */
    camel_operation_pop_message (cancellable);

    activity = g_hash_table_lookup (priv->jobs, cancellable);
    description = e_activity_get_text (activity);

    if (e_activity_handle_cancellation (activity, error)) {
        /* nothing to do */

    } else if (error != NULL) {
        EShell *shell;
        GtkApplication *application;
        GList *list, *iter;

        shell = e_shell_backend_get_shell (shell_backend);

        application = GTK_APPLICATION (shell);
        list = gtk_application_get_windows (application);

        /* Submit the error to an appropriate EAlertSink. */
        for (iter = list; iter != NULL; iter = g_list_next (iter)) {
            EShellView *shell_view;
            EShellContent *shell_content;

            if (!E_IS_SHELL_WINDOW (iter->data))
                continue;

            shell_view = e_shell_window_peek_shell_view (
                E_SHELL_WINDOW (iter->data), class->name);

            if (!E_IS_SHELL_VIEW (shell_view))
                continue;

            shell_content =
                e_shell_view_get_shell_content (shell_view);

            if (description != NULL && *description != '\0')
                e_alert_submit (
                    E_ALERT_SINK (shell_content),
                    "mail:async-error", description,
                    error->message, NULL);
            else
                e_alert_submit (
                    E_ALERT_SINK (shell_content),
                    "mail:async-error-nodescribe",
                    error->message, NULL);

            break;
        }
    }

    g_hash_table_remove (priv->jobs, cancellable);
}

static void
mail_backend_get_property (GObject *object,
                           guint property_id,
                           GValue *value,
                           GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_SESSION:
            g_value_set_object (
                value,
                e_mail_backend_get_session (
                E_MAIL_BACKEND (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
mail_backend_dispose (GObject *object)
{
    EMailBackendPrivate *priv;

    priv = E_MAIL_BACKEND_GET_PRIVATE (object);

    if (priv->session != NULL) {
        g_signal_handlers_disconnect_matched (
            priv->session, G_SIGNAL_MATCH_DATA,
            0, 0, NULL, NULL, object);
        camel_session_remove_services (
            CAMEL_SESSION (priv->session));
        g_object_unref (priv->session);
        priv->session = NULL;
    }

    /* There should be no unfinished jobs left. */
    g_warn_if_fail (g_hash_table_size (priv->jobs) == 0);

    /* Chain up to parent's dispose() method. */
    G_OBJECT_CLASS (e_mail_backend_parent_class)->dispose (object);
}

static void
mail_backend_finalize (GObject *object)
{
    EMailBackendPrivate *priv;

    priv = E_MAIL_BACKEND_GET_PRIVATE (object);

    g_hash_table_destroy (priv->jobs);

    /* Chain up to parent's finalize() method. */
    G_OBJECT_CLASS (e_mail_backend_parent_class)->finalize (object);

    camel_shutdown ();
}

static void
mail_backend_add_store (EMailSession *session,
                        CamelStore *store,
                        EMailBackend *backend)
{
    EMFolderTreeModel *model;

    model = em_folder_tree_model_get_default ();
    em_folder_tree_model_add_store (model, store);
}

static void
mail_backend_remove_store (EMailSession *session,
                           CamelStore *store,
                           EMailBackend *backend)
{
    EMFolderTreeModel *model;

    model = em_folder_tree_model_get_default ();
    em_folder_tree_model_remove_store (model, store);
}

#define SET_ACTIVITY(cancellable, activity) \
    g_object_set_data (G_OBJECT (cancellable), "e-activity", activity)
#define GET_ACTIVITY(cancellable) \
        g_object_get_data (G_OBJECT (cancellable), "e-activity")

static void
mail_mt_create_activity (GCancellable *cancellable)
{
    EActivity *activity;

    activity = e_activity_new ();
    e_activity_set_percent (activity, 0.0);
    e_activity_set_cancellable (activity, cancellable);
    SET_ACTIVITY (cancellable, activity);
}

static void
mail_mt_submit_activity (GCancellable *cancellable)
{
    EShell *shell;
    EShellBackend *shell_backend;
    EActivity *activity;

    shell = e_shell_get_default ();
    shell_backend = e_shell_get_backend_by_name (
        shell, "mail");

    activity = GET_ACTIVITY (cancellable);
    if (activity)
        e_shell_backend_add_activity (shell_backend, activity);

}

static void
mail_mt_free_activity (GCancellable *cancellable)
{
    EActivity *activity = GET_ACTIVITY (cancellable);

    if (activity)
        g_object_unref (activity);
}

static void
mail_mt_complete_activity (GCancellable *cancellable)
{
    EActivity *activity = GET_ACTIVITY (cancellable);

    if (activity)
        e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
}

static void
mail_mt_cancel_activity (GCancellable *cancellable)
{
    EActivity *activity = GET_ACTIVITY (cancellable);

    if (activity)
        e_activity_set_state (activity, E_ACTIVITY_CANCELLED);
}

static void
mail_mt_alert_error (GCancellable *cancellable,
                     const gchar *what,
                     const gchar *message)
{
    EShell *shell;
    EShellView *shell_view;
    EShellWindow *shell_window = NULL;
    EShellContent *shell_content;
    GList *list, *iter;
    GtkApplication *application;

    shell = e_shell_get_default ();
    application = GTK_APPLICATION (shell);
    list = gtk_application_get_windows (application);

    /* Find the most recently used EShellWindow. */
    for (iter = list; iter != NULL; iter = g_list_next (iter)) {
        if (E_IS_SHELL_WINDOW (iter->data)) {
            shell_window = E_SHELL_WINDOW (iter->data);
            break;
        }
    }

    /* If we can't find an EShellWindow then... well, screw it. */
    if (shell_window == NULL)
        return;

    shell_view = e_shell_window_get_shell_view (
        shell_window, "mail");
    shell_content = e_shell_view_get_shell_content (shell_view);

    if (what) {
        e_alert_submit (
            E_ALERT_SINK (shell_content),
            "mail:async-error", what,
            message, NULL);
    } else
        e_alert_submit (
            E_ALERT_SINK (shell_content),
            "mail:async-error-nodescribe",
            message, NULL);
}

static EAlertSink *
mail_mt_get_alert_sink ()
{
    EShell *shell;
    EShellBackend *shell_backend;

    shell = e_shell_get_default ();
    shell_backend = e_shell_get_backend_by_name (
        shell, "mail");

    return e_mail_backend_get_alert_sink (E_MAIL_BACKEND (shell_backend));
}

static void
mail_backend_constructed (GObject *object)
{
    EMailBackendPrivate *priv;
    EShell *shell;
    EShellBackend *shell_backend;
    MailFolderCache *folder_cache;
    ESourceRegistry *registry;

    priv = E_MAIL_BACKEND_GET_PRIVATE (object);

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

    if (camel_init (e_get_user_data_dir (), TRUE) != 0)
        exit (0);

    registry = e_shell_get_registry (shell);
    priv->session = e_mail_ui_session_new (registry);

    g_signal_connect (
        priv->session, "flush-outbox",
        G_CALLBACK (mail_send), priv->session);

    /* Propagate "activity-added" signals from
     * the mail session to the shell backend. */
    g_signal_connect_swapped (
        priv->session, "activity-added",
        G_CALLBACK (e_shell_backend_add_activity),
        shell_backend);

    g_signal_connect (
        priv->session, "job-started",
        G_CALLBACK (mail_backend_job_started_cb),
        shell_backend);

    g_signal_connect (
        priv->session, "job-finished",
        G_CALLBACK (mail_backend_job_finished_cb),
        shell_backend);

    g_signal_connect (
        priv->session, "store-added",
        G_CALLBACK (mail_backend_add_store),
        shell_backend);

    g_signal_connect (
        priv->session, "store-removed",
        G_CALLBACK (mail_backend_remove_store),
        shell_backend);

    g_signal_connect (
        shell, "prepare-for-offline",
        G_CALLBACK (mail_backend_prepare_for_offline_cb),
        shell_backend);

    g_signal_connect (
        shell, "prepare-for-online",
        G_CALLBACK (mail_backend_prepare_for_online_cb),
        shell_backend);

    g_signal_connect (
        shell, "prepare-for-quit",
        G_CALLBACK (mail_backend_prepare_for_quit_cb),
        shell_backend);

    g_signal_connect (
        shell, "quit-requested",
        G_CALLBACK (mail_backend_quit_requested_cb),
        shell_backend);

    folder_cache = e_mail_session_get_folder_cache (priv->session);

    g_signal_connect (
        folder_cache, "folder-deleted",
        G_CALLBACK (mail_backend_folder_deleted_cb),
        shell_backend);

    g_signal_connect (
        folder_cache, "folder-renamed",
        G_CALLBACK (mail_backend_folder_renamed_cb),
        shell_backend);

    g_signal_connect (
        folder_cache, "folder-changed",
        G_CALLBACK (mail_backend_folder_changed_cb), shell_backend);

    mail_config_init (priv->session);

    mail_msg_register_activities (
        mail_mt_create_activity,
        mail_mt_submit_activity,
        mail_mt_free_activity,
        mail_mt_complete_activity,
        mail_mt_cancel_activity,
        mail_mt_alert_error,
        mail_mt_get_alert_sink);

    /* Chain up to parent's constructed() method. */
    G_OBJECT_CLASS (e_mail_backend_parent_class)->constructed (object);
}

static void
e_mail_backend_class_init (EMailBackendClass *class)
{
    GObjectClass *object_class;
    EShellBackendClass *shell_backend_class;

    g_type_class_add_private (class, sizeof (EMailBackendPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->get_property = mail_backend_get_property;
    object_class->dispose = mail_backend_dispose;
    object_class->finalize = mail_backend_finalize;
    object_class->constructed = mail_backend_constructed;

    shell_backend_class = E_SHELL_BACKEND_CLASS (class);
    shell_backend_class->migrate = e_mail_migrate;
    shell_backend_class->get_data_dir = mail_shell_backend_get_data_dir;
    shell_backend_class->get_config_dir = mail_shell_backend_get_config_dir;

    g_object_class_install_property (
        object_class,
        PROP_SESSION,
        g_param_spec_object (
            "session",
            NULL,
            NULL,
            E_TYPE_MAIL_SESSION,
            G_PARAM_READABLE));
}

static void
e_mail_backend_init (EMailBackend *backend)
{
    backend->priv = E_MAIL_BACKEND_GET_PRIVATE (backend);

    backend->priv->jobs = g_hash_table_new_full (
        (GHashFunc) g_direct_hash,
        (GEqualFunc) g_direct_equal,
        (GDestroyNotify) NULL,
        (GDestroyNotify) g_object_unref);
}

EMailSession *
e_mail_backend_get_session (EMailBackend *backend)
{
    g_return_val_if_fail (E_IS_MAIL_BACKEND (backend), NULL);

    return backend->priv->session;
}

EAlertSink *
e_mail_backend_get_alert_sink (EMailBackend *backend)
{
    EShell *shell;
    EShellView *shell_view;
    EShellBackend *shell_backend;
    EShellContent *shell_content;
    EShellWindow *shell_window = NULL;
    EShellBackendClass *class;
    GtkApplication *application;
    GList *list, *link;

    /* XXX This is meant to be a convenient but temporary hack.
     *     It digs through the list of available EShellWindows
     *     to find a suitable EAlertSink. */

    g_return_val_if_fail (E_IS_MAIL_BACKEND (backend), NULL);

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

    application = GTK_APPLICATION (shell);
    list = gtk_application_get_windows (application);

    /* Find the most recently used EShellWindow. */
    for (link = list; link != NULL; link = g_list_next (link)) {
        if (E_IS_SHELL_WINDOW (link->data)) {
            shell_window = E_SHELL_WINDOW (link->data);
            break;
        }
    }

    g_return_val_if_fail (shell_window != NULL, NULL);

    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
    shell_view = e_shell_window_get_shell_view (shell_window, class->name);
    shell_content = e_shell_view_get_shell_content (shell_view);

    return E_ALERT_SINK (shell_content);
}

gboolean
e_mail_backend_delete_junk_policy_decision (EMailBackend *backend)
{
    EMailBackendClass *class;

    g_return_val_if_fail (E_IS_MAIL_BACKEND (backend), FALSE);

    class = E_MAIL_BACKEND_GET_CLASS (backend);
    if (class->delete_junk_policy_decision == NULL)
        return FALSE;

    return class->delete_junk_policy_decision (backend);
}

gboolean
e_mail_backend_empty_trash_policy_decision (EMailBackend *backend)
{
    EMailBackendClass *class;

    g_return_val_if_fail (E_IS_MAIL_BACKEND (backend), FALSE);

    class = E_MAIL_BACKEND_GET_CLASS (backend);
    if (class->empty_trash_policy_decision == NULL)
        return FALSE;

    return class->empty_trash_policy_decision (backend);
}