aboutsummaryrefslogblamecommitdiffstats
path: root/mail/e-mail-ui-session.c
blob: e7b2d8704f48623ae983d41a21211fab0fab11a5 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11


                      


                                                                           
  



                                                                             
  

                                                                           
























                                                                               



                         
                                    



                                 






                                  


                              
                                      

                           
                           




                                                               

                                            

                               
                                  

                                         
                                 
                            




                           
                        

                         














                                                        




                                






























































                                                                               
                               


































                                                                                        



                                                               
 
                            











































                                                                            










                                                  
               
                                                       
 









                                                                       
















                                                                       












                                                                     






                                                                  





                                                                   






                                                                   



                                                                       
 



                                         
 
                                                      
 
                                     


                                                
 



                                                                 

         




                                                   




                                                   

                                                                          






                                             
                                  
                                   
                              
                      

                                          






                                                                                
 
                                                      

                                                                 



                                                                 
 


                                                             









                                                                     
                                                                      


                                                                              
 
 






















                                                                        
                                                       
                                 
                                             

















                                                                       

 

                                                      


                                                             

                                               
 

                                                                     



                                                         

                                                     





                                                                       





































                                                                             
           







































                                                                           

                                                       
 

                                                               

 





                                                                            




                                                         
                                              



                                                                         
                                                                  
                                                                  



                                                                

                                                                       
                                                                             
                                                                               
                                                               
 


                                                                                            


                                         











                                                              








                                                     










                                                 


















                                                                      
                                                 



                                    

                                                                     




                                                       
                                     




                                                 







                                                                    




































                                                                             







                                                                    







                                                                    









                                                                      






















































































































                                                                               
/*
 * e-mail-ui-session.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.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this 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
 *
 */

/* mail-session.c: handles the session information and resource manipulation */

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

#include <errno.h>
#include <stdlib.h>
#include <string.h>

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

#include <gtk/gtk.h>

#ifdef HAVE_CANBERRA
#include <canberra-gtk.h>
#endif

#include <libebackend/libebackend.h>

#include "e-mail-account-store.h"

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

#include "shell/e-shell.h"
#include "shell/e-shell-view.h"
#include "shell/e-shell-content.h"
#include "shell/e-shell-window.h"

#include "e-mail-ui-session.h"
#include "em-composer-utils.h"
#include "em-filter-context.h"
#include "em-vfolder-editor-context.h"
#include "em-filter-rule.h"
#include "em-utils.h"
#include "mail-send-recv.h"

#define E_MAIL_UI_SESSION_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_MAIL_UI_SESSION, EMailUISessionPrivate))

typedef struct _SourceContext SourceContext;

struct _EMailUISessionPrivate {
    FILE *filter_logfile;
    ESourceRegistry *registry;
    EMailAccountStore *account_store;
    EMailLabelListStore *label_store;
    EPhotoCache *photo_cache;
    gboolean check_junk;
};

enum {
    PROP_0,
    PROP_ACCOUNT_STORE,
    PROP_CHECK_JUNK,
    PROP_LABEL_STORE,
    PROP_PHOTO_CACHE
};

enum {
    ACTIVITY_ADDED,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

G_DEFINE_TYPE_WITH_CODE (
    EMailUISession,
    e_mail_ui_session,
    E_TYPE_MAIL_SESSION,
    G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))

struct _SourceContext {
    EMailUISession *session;
    CamelService *service;
};

/* Support for CamelSession.get_filter_driver () *****************************/

static CamelFolder *
get_folder (CamelFilterDriver *d,
            const gchar *uri,
            gpointer user_data,
            GError **error)
{
    EMailSession *session = E_MAIL_SESSION (user_data);

    /* FIXME Not passing a GCancellable here. */
    /* FIXME Need a camel_filter_driver_get_session(). */
    return e_mail_session_uri_to_folder_sync (
        session, uri, 0, NULL, error);
}

static gboolean
session_play_sound_cb (const gchar *filename)
{
#ifdef HAVE_CANBERRA
    if (filename != NULL && *filename != '\0')
        ca_context_play (
            ca_gtk_context_get (), 0,
            CA_PROP_MEDIA_FILENAME, filename,
            NULL);
    else
#endif
        gdk_beep ();

    return FALSE;
}

static void
session_play_sound (CamelFilterDriver *driver,
                    const gchar *filename,
                    gpointer user_data)
{
    g_idle_add_full (
        G_PRIORITY_DEFAULT_IDLE,
        (GSourceFunc) session_play_sound_cb,
        g_strdup (filename), (GDestroyNotify) g_free);
}

static void
session_system_beep (CamelFilterDriver *driver,
                     gpointer user_data)
{
    g_idle_add ((GSourceFunc) session_play_sound_cb, NULL);
}

static CamelFilterDriver *
main_get_filter_driver (CamelSession *session,
                        const gchar *type,
                        GError **error)
{
    EMailSession *ms = E_MAIL_SESSION (session);
    CamelFilterDriver *driver;
    EFilterRule *rule = NULL;
    const gchar *config_dir;
    gchar *user, *system;
    GSettings *settings;
    ERuleContext *fc;
    EMailUISessionPrivate *priv;
    gboolean add_junk_test;

    priv = E_MAIL_UI_SESSION_GET_PRIVATE (session);

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

    config_dir = mail_session_get_config_dir ();
    user = g_build_filename (config_dir, "filters.xml", NULL);
    system = g_build_filename (EVOLUTION_PRIVDATADIR, "filtertypes.xml", NULL);
    fc = (ERuleContext *) em_filter_context_new (ms);
    e_rule_context_load (fc, system, user);
    g_free (system);
    g_free (user);

    driver = camel_filter_driver_new (session);
    camel_filter_driver_set_folder_func (driver, get_folder, session);

    if (g_settings_get_boolean (settings, "filters-log-actions")) {
        if (priv->filter_logfile == NULL) {
            gchar *filename;

            filename = g_settings_get_string (settings, "filters-log-file");
            if (filename) {
                priv->filter_logfile = g_fopen (filename, "a+");
                g_free (filename);
            }
        }

        if (priv->filter_logfile)
            camel_filter_driver_set_logfile (driver, priv->filter_logfile);
    }

    camel_filter_driver_set_shell_func (driver, mail_execute_shell_command, NULL);
    camel_filter_driver_set_play_sound_func (driver, session_play_sound, NULL);
    camel_filter_driver_set_system_beep_func (driver, session_system_beep, NULL);

    add_junk_test =
        priv->check_junk && (
        g_str_equal (type, E_FILTER_SOURCE_INCOMING) ||
        g_str_equal (type, E_FILTER_SOURCE_JUNKTEST));

    if (add_junk_test) {
        /* implicit junk check as 1st rule */
        camel_filter_driver_add_rule (
            driver, "Junk check", "(junk-test)",
            "(begin (set-system-flag \"junk\"))");
    }

    if (strcmp (type, E_FILTER_SOURCE_JUNKTEST) != 0) {
        GString *fsearch, *faction;

        fsearch = g_string_new ("");
        faction = g_string_new ("");

        if (!strcmp (type, E_FILTER_SOURCE_DEMAND))
            type = E_FILTER_SOURCE_INCOMING;

        /* add the user-defined rules next */
        while ((rule = e_rule_context_next_rule (fc, rule, type))) {
            g_string_truncate (fsearch, 0);
            g_string_truncate (faction, 0);

            /* skip disabled rules */
            if (!rule->enabled)
                continue;

            e_filter_rule_build_code (rule, fsearch);
            em_filter_rule_build_action (
                EM_FILTER_RULE (rule), faction);
            camel_filter_driver_add_rule (
                driver, rule->name,
                fsearch->str, faction->str);
        }

        g_string_free (fsearch, TRUE);
        g_string_free (faction, TRUE);
    }

    g_object_unref (fc);

    g_object_unref (settings);

    return driver;
}

static void
source_context_free (SourceContext *context)
{
    if (context->session != NULL)
        g_object_unref (context->session);

    if (context->service != NULL)
        g_object_unref (context->service);

    g_slice_free (SourceContext, context);
}

static gboolean
mail_ui_session_add_service_cb (SourceContext *context)
{
    EMailAccountStore *store;

    /* The CamelService should be fully initialized by now. */
    store = e_mail_ui_session_get_account_store (context->session);
    e_mail_account_store_add_service (store, context->service);

    return FALSE;
}

static void
mail_ui_session_set_property (GObject *object,
                              guint property_id,
                              const GValue *value,
                              GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_CHECK_JUNK:
            e_mail_ui_session_set_check_junk (
                E_MAIL_UI_SESSION (object),
                g_value_get_boolean (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
mail_ui_session_get_property (GObject *object,
                              guint property_id,
                              GValue *value,
                              GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_ACCOUNT_STORE:
            g_value_set_object (
                value,
                e_mail_ui_session_get_account_store (
                E_MAIL_UI_SESSION (object)));
            return;

        case PROP_CHECK_JUNK:
            g_value_set_boolean (
                value,
                e_mail_ui_session_get_check_junk (
                E_MAIL_UI_SESSION (object)));
            return;

        case PROP_LABEL_STORE:
            g_value_set_object (
                value,
                e_mail_ui_session_get_label_store (
                E_MAIL_UI_SESSION (object)));
            return;

        case PROP_PHOTO_CACHE:
            g_value_set_object (
                value,
                e_mail_ui_session_get_photo_cache (
                E_MAIL_UI_SESSION (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
mail_ui_session_dispose (GObject *object)
{
    EMailUISessionPrivate *priv;

    priv = E_MAIL_UI_SESSION_GET_PRIVATE (object);

    if (priv->registry != NULL) {
        g_object_unref (priv->registry);
        priv->registry = NULL;
    }

    if (priv->account_store != NULL) {
        e_mail_account_store_clear (priv->account_store);
        g_object_unref (priv->account_store);
        priv->account_store = NULL;
    }

    if (priv->label_store != NULL) {
        g_object_unref (priv->label_store);
        priv->label_store = NULL;
    }

    if (priv->photo_cache != NULL) {
        g_object_unref (priv->photo_cache);
        priv->photo_cache = NULL;
    }

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

static void
mail_ui_session_constructed (GObject *object)
{
    EMailUISessionPrivate *priv;
    EMFolderTreeModel *folder_tree_model;
    ESourceRegistry *registry;
    EClientCache *client_cache;
    EMailSession *session;
    EShell *shell;

    session = E_MAIL_SESSION (object);
    shell = e_shell_get_default ();

    /* synchronize online state first, before any CamelService is created */
    g_object_bind_property (
        shell, "online",
        session, "online",
        G_BINDING_SYNC_CREATE);

    priv = E_MAIL_UI_SESSION_GET_PRIVATE (object);
    priv->account_store = e_mail_account_store_new (session);

    /* Keep our own reference to the ESourceRegistry so we
     * can easily disconnect signal handlers in dispose(). */
    registry = e_mail_session_get_registry (session);
    priv->registry = g_object_ref (registry);

    client_cache = e_shell_get_client_cache (shell);
    priv->photo_cache = e_photo_cache_new (client_cache);

    /* XXX Make sure the folder tree model is created before we
     *     add built-in CamelStores so it gets signals from the
     *     EMailAccountStore.
     *
     * XXX This is creating a circular reference.  Perhaps the
     *     model should only hold a weak pointer to EMailSession?
     *
     * FIXME EMailSession should just own the default instance.
     */
    folder_tree_model = em_folder_tree_model_get_default ();
    em_folder_tree_model_set_session (folder_tree_model, session);

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

static CamelService *
mail_ui_session_add_service (CamelSession *session,
                             const gchar *uid,
                             const gchar *protocol,
                             CamelProviderType type,
                             GError **error)
{
    CamelService *service;

    /* Chain up to parent's constructed() method. */
    service = CAMEL_SESSION_CLASS (e_mail_ui_session_parent_class)->
        add_service (session, uid, protocol, type, error);

    /* Inform the EMailAccountStore of the new CamelService
     * from an idle callback so the service has a chance to
     * fully initialize first. */
    if (CAMEL_IS_STORE (service)) {
        SourceContext *context;

        context = g_slice_new0 (SourceContext);
        context->session = g_object_ref (session);
        context->service = g_object_ref (service);

        /* Prioritize ahead of GTK+ redraws. */
        g_idle_add_full (
            G_PRIORITY_HIGH_IDLE,
            (GSourceFunc) mail_ui_session_add_service_cb,
            context, (GDestroyNotify) source_context_free);
    }

    return service;
}

static void
mail_ui_session_remove_service (CamelSession *session,
                                CamelService *service)
{
    EMailAccountStore *store;
    EMailUISession *ui_session;

    /* Passing a NULL parent window skips confirmation prompts. */
    ui_session = E_MAIL_UI_SESSION (session);
    store = e_mail_ui_session_get_account_store (ui_session);
    e_mail_account_store_remove_service (store, NULL, service);
}

CamelCertTrust
e_mail_ui_session_trust_prompt (CamelSession *session,
                                CamelService *service,
                                GTlsCertificate *certificate,
                                GTlsCertificateFlags errors)
{
    g_type_ensure (E_TYPE_MAIL_UI_SESSION);

    return CAMEL_SESSION_CLASS (e_mail_ui_session_parent_class)->
        trust_prompt (session, service, certificate, errors);
}

static CamelFilterDriver *
mail_ui_session_get_filter_driver (CamelSession *session,
                                   const gchar *type,
                                   GError **error)
{
    return (CamelFilterDriver *) mail_call_main (
        MAIL_CALL_p_ppp, (MailMainFunc) main_get_filter_driver,
        session, type, error);
}

static gboolean
mail_ui_session_lookup_addressbook (CamelSession *session,
                                    const gchar *name)
{
    CamelInternetAddress *cia;
    gboolean known_address = FALSE;

    /* FIXME CamelSession's lookup_addressbook() method needs redone.
     *       No GCancellable provided, no means of reporting an error. */

    if (!mail_config_get_lookup_book ())
        return FALSE;

    cia = camel_internet_address_new ();

    if (camel_address_decode (CAMEL_ADDRESS (cia), name) > 0) {
        GError *error = NULL;

        e_mail_ui_session_check_known_address_sync (
            E_MAIL_UI_SESSION (session), cia,
            mail_config_get_lookup_book_local_only (),
            NULL, &known_address, &error);

        if (error != NULL) {
            g_warning ("%s: %s", G_STRFUNC, error->message);
            g_error_free (error);
        }
    } else {
        g_warning (
            "%s: Failed to decode internet "
            "address '%s'", G_STRFUNC, name);
    }

    g_object_unref (cia);

    return known_address;
}

static void
mail_ui_session_user_alert (CamelSession *session,
                            CamelService *service,
                            CamelSessionAlertType type,
                            const gchar *alert_message)
{
    EAlert *alert;
    EShell *shell;
    const gchar *alert_tag;
    gchar *display_name;

    shell = e_shell_get_default ();

    switch (type) {
        case CAMEL_SESSION_ALERT_INFO:
            alert_tag = "mail:user-alert-info";
            break;
        case CAMEL_SESSION_ALERT_WARNING:
            alert_tag = "mail:user-alert-warning";
            break;
        case CAMEL_SESSION_ALERT_ERROR:
            alert_tag = "mail:user-alert-error";
            break;
        default:
            g_return_if_reached ();
    }

    display_name = camel_service_dup_display_name (service);

    /* Just submit the alert to the EShell rather than hunting for
     * a suitable window.  This will post it to all shell windows in
     * all views, but if it's coming from the server then it must be
     * important... right? */
    alert = e_alert_new (alert_tag, display_name, alert_message, NULL);
    e_shell_submit_alert (shell, alert);
    g_object_unref (alert);

    g_free (display_name);
}

static void
mail_ui_session_refresh_service (EMailSession *session,
                                 CamelService *service)
{
    if (camel_session_get_online (CAMEL_SESSION (session)))
        mail_receive_service (service);
}

static EMVFolderContext *
mail_ui_session_create_vfolder_context (EMailSession *session)
{
    return (EMVFolderContext *) em_vfolder_editor_context_new (session);
}

static void
e_mail_ui_session_class_init (EMailUISessionClass *class)
{
    GObjectClass *object_class;
    CamelSessionClass *session_class;
    EMailSessionClass *mail_session_class;

    g_type_class_add_private (class, sizeof (EMailUISessionPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = mail_ui_session_set_property;
    object_class->get_property = mail_ui_session_get_property;
    object_class->dispose = mail_ui_session_dispose;
    object_class->constructed = mail_ui_session_constructed;

    session_class = CAMEL_SESSION_CLASS (class);
    session_class->add_service = mail_ui_session_add_service;
    session_class->remove_service = mail_ui_session_remove_service;
    session_class->get_filter_driver = mail_ui_session_get_filter_driver;
    session_class->lookup_addressbook = mail_ui_session_lookup_addressbook;
    session_class->user_alert = mail_ui_session_user_alert;

    mail_session_class = E_MAIL_SESSION_CLASS (class);
    mail_session_class->create_vfolder_context = mail_ui_session_create_vfolder_context;
    mail_session_class->refresh_service = mail_ui_session_refresh_service;

    g_object_class_install_property (
        object_class,
        PROP_CHECK_JUNK,
        g_param_spec_boolean (
            "check-junk",
            "Check Junk",
            "Check if incoming messages are junk",
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_LABEL_STORE,
        g_param_spec_object (
            "label-store",
            "Label Store",
            "Mail label store",
            E_TYPE_MAIL_LABEL_LIST_STORE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_PHOTO_CACHE,
        g_param_spec_object (
            "photo-cache",
            "Photo Cache",
            "Contact photo cache",
            E_TYPE_PHOTO_CACHE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    signals[ACTIVITY_ADDED] = g_signal_new (
        "activity-added",
        G_OBJECT_CLASS_TYPE (class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (EMailUISessionClass, activity_added),
        NULL, NULL,
        g_cclosure_marshal_VOID__OBJECT,
        G_TYPE_NONE, 1,
        E_TYPE_ACTIVITY);
}

static void
e_mail_ui_session_init (EMailUISession *session)
{
    session->priv = E_MAIL_UI_SESSION_GET_PRIVATE (session);
    session->priv->label_store = e_mail_label_list_store_new ();
}

EMailSession *
e_mail_ui_session_new (ESourceRegistry *registry)
{
    const gchar *user_data_dir;
    const gchar *user_cache_dir;

    g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);

    user_data_dir = mail_session_get_data_dir ();
    user_cache_dir = mail_session_get_cache_dir ();

    return g_object_new (
        E_TYPE_MAIL_UI_SESSION,
        "registry", registry,
        "user-data-dir", user_data_dir,
        "user-cache-dir", user_cache_dir,
        NULL);
}

EMailAccountStore *
e_mail_ui_session_get_account_store (EMailUISession *session)
{
    g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL);

    return session->priv->account_store;
}

/**
 * e_mail_ui_session_get_check_junk:
 * @session: an #EMailUISession
 *
 * Returns whether to automatically check incoming messages for junk content.
 *
 * Returns: whether to check for junk messages
 **/
gboolean
e_mail_ui_session_get_check_junk (EMailUISession *session)
{
    g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), FALSE);

    return session->priv->check_junk;
}

/**
 * e_mail_ui_session_set_check_junk:
 * @session: an #EMailUISession
 * @check_junk: whether to check for junk messages
 *
 * Sets whether to automatically check incoming messages for junk content.
 **/
void
e_mail_ui_session_set_check_junk (EMailUISession *session,
                                  gboolean check_junk)
{
    g_return_if_fail (E_IS_MAIL_UI_SESSION (session));

    if (check_junk == session->priv->check_junk)
        return;

    session->priv->check_junk = check_junk;

    g_object_notify (G_OBJECT (session), "check-junk");
}

EMailLabelListStore *
e_mail_ui_session_get_label_store (EMailUISession *session)
{
    g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL);

    return session->priv->label_store;
}

EPhotoCache *
e_mail_ui_session_get_photo_cache (EMailUISession *session)
{
    g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL);

    return session->priv->photo_cache;
}

void
e_mail_ui_session_add_activity (EMailUISession *session,
                                EActivity *activity)
{
    g_return_if_fail (E_IS_MAIL_UI_SESSION (session));
    g_return_if_fail (E_IS_ACTIVITY (activity));

    g_signal_emit (session, signals[ACTIVITY_ADDED], 0, activity);
}

/**
 * e_mail_ui_session_check_known_address_sync:
 * @session: an #EMailUISession
 * @addr: a #CamelInternetAddress
 * @check_local_only: only check the builtin address book
 * @cancellable: optional #GCancellable object, or %NULL
 * @out_known_address: return location for the determination of
 *                     whether @addr is a known address
 * @error: return location for a #GError, or %NULL
 *
 * Determines whether @addr is a known email address by querying address
 * books for contacts with a matching email address.  If @check_local_only
 * is %TRUE then only the builtin address book is checked, otherwise all
 * enabled address books are checked.
 *
 * The result of the query is returned through the @out_known_address
 * boolean pointer, not through the return value.  The return value only
 * indicates whether the address book queries were completed successfully.
 * If an error occurred, the function sets @error and returns %FALSE.
 *
 * Returns: whether address books were successfully queried
 **/
gboolean
e_mail_ui_session_check_known_address_sync (EMailUISession *session,
                                            CamelInternetAddress *addr,
                                            gboolean check_local_only,
                                            GCancellable *cancellable,
                                            gboolean *out_known_address,
                                            GError **error)
{
    EPhotoCache *photo_cache;
    EClientCache *client_cache;
    ESourceRegistry *registry;
    EBookQuery *book_query;
    GList *list, *link;
    const gchar *email_address = NULL;
    gchar *book_query_string;
    gboolean known_address = FALSE;
    gboolean success = TRUE;

    g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), FALSE);
    g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), FALSE);

    camel_internet_address_get (addr, 0, NULL, &email_address);
    g_return_val_if_fail (email_address != NULL, FALSE);

    /* XXX EPhotoCache holds a reference on EClientCache, which
     *     we need.  EMailUISession should probably hold its own
     *     EClientCache reference, but this will do for now. */
    photo_cache = e_mail_ui_session_get_photo_cache (session);
    client_cache = e_photo_cache_ref_client_cache (photo_cache);
    registry = e_client_cache_ref_registry (client_cache);

    book_query = e_book_query_field_test (
        E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email_address);
    book_query_string = e_book_query_to_string (book_query);
    e_book_query_unref (book_query);

    if (check_local_only) {
        ESource *source;

        source = e_source_registry_ref_builtin_address_book (registry);
        list = g_list_prepend (NULL, g_object_ref (source));
        g_object_unref (source);
    } else {
        list = e_source_registry_list_sources (
            registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);
    }

    for (link = list; link != NULL; link = g_list_next (link)) {
        ESource *source = E_SOURCE (link->data);
        EClient *client;
        GSList *uids = NULL;

        /* Skip disabled sources. */
        if (!e_source_get_enabled (source))
            continue;

        client = e_client_cache_get_client_sync (
            client_cache, source,
            E_SOURCE_EXTENSION_ADDRESS_BOOK,
            cancellable, error);

        if (client == NULL) {
            success = FALSE;
            break;
        }

        success = e_book_client_get_contacts_uids_sync (
            E_BOOK_CLIENT (client), book_query_string,
            &uids, cancellable, error);

        g_object_unref (client);

        if (!success) {
            g_warn_if_fail (uids == NULL);
            break;
        }

        if (uids != NULL) {
            g_slist_free_full (uids, (GDestroyNotify) g_free);
            known_address = TRUE;
            break;
        }
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);

    g_free (book_query_string);

    g_object_unref (registry);
    g_object_unref (client_cache);

    if (success && out_known_address != NULL)
        *out_known_address = known_address;

    return success;
}