[evolution-data-server] evo-I#1639 - Filter messages by condition if sender is in address book



commit e248367d50b9f1b7d92cb5154cfd9844cba9f5ef
Author: Milan Crha <mcrha redhat com>
Date:   Fri Sep 24 14:10:25 2021 +0200

    evo-I#1639 - Filter messages by condition if sender is in address book
    
    Related to https://gitlab.gnome.org/GNOME/evolution/-/issues/1639

 docs/reference/camel/camel-docs.sgml.in            |   4 +
 .../evolution-data-server-docs.sgml.in             |   4 +
 .../backends/file/e-book-backend-file.c            |  57 +++++
 .../backends/ldap/e-book-backend-ldap.c            | 242 +++++++++++++++++++++
 .../libebook-contacts/e-book-contacts-utils.c      |  41 ++++
 .../libebook-contacts/e-book-contacts-utils.h      |   4 +
 src/addressbook/libebook/e-book-client.c           | 159 ++++++++++++++
 src/addressbook/libebook/e-book-client.h           |  14 ++
 .../libedata-book/e-book-backend-sync.c            |  57 +++++
 .../libedata-book/e-book-backend-sync.h            |  12 +-
 src/addressbook/libedata-book/e-book-backend.c     | 182 ++++++++++++++++
 src/addressbook/libedata-book/e-book-backend.h     |  23 +-
 src/addressbook/libedata-book/e-book-cache.c       |  88 ++++++++
 src/addressbook/libedata-book/e-book-cache.h       |   4 +
 .../libedata-book/e-book-meta-backend.c            |  24 ++
 src/addressbook/libedata-book/e-data-book.c        |  97 +++++++++
 src/addressbook/libedata-book/e-data-book.h        |   5 +
 src/camel/camel-filter-search.c                    |  62 +++++-
 src/camel/camel-folder-search.c                    |  68 ++++++
 src/camel/camel-folder-search.h                    |  11 +-
 src/camel/camel-session.c                          |  62 ++++++
 src/camel/camel-session.h                          |  17 +-
 .../org.gnome.evolution.dataserver.AddressBook.xml |   5 +
 tests/libedata-book/test-book-meta-backend.c       |  43 ++++
 24 files changed, 1280 insertions(+), 5 deletions(-)
---
diff --git a/docs/reference/camel/camel-docs.sgml.in b/docs/reference/camel/camel-docs.sgml.in
index 4f54beb54..4eb653763 100644
--- a/docs/reference/camel/camel-docs.sgml.in
+++ b/docs/reference/camel/camel-docs.sgml.in
@@ -298,6 +298,10 @@
     <title>Index of deprecated symbols</title>
     <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
   </index>
+  <index id="api-index-3-44" role="3.44">
+    <title>Index of new symbols in 3.44</title>
+    <xi:include href="xml/api-index-3.44.xml"><xi:fallback /></xi:include>
+  </index>
   <index id="api-index-3-42" role="3.42">
     <title>Index of new symbols in 3.42</title>
     <xi:include href="xml/api-index-3.42.xml"><xi:fallback /></xi:include>
diff --git a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in 
b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
index 874fc9384..c5ce8efa6 100644
--- a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
+++ b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
@@ -361,6 +361,10 @@
     <title>Index of deprecated symbols</title>
     <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
   </index>
+  <index id="api-index-3-44" role="3.44">
+    <title>Index of new symbols in 3.44</title>
+    <xi:include href="xml/api-index-3.44.xml"><xi:fallback /></xi:include>
+  </index>
   <index id="api-index-3-42" role="3.42">
     <title>Index of new symbols in 3.42</title>
     <xi:include href="xml/api-index-3.42.xml"><xi:fallback /></xi:include>
diff --git a/src/addressbook/backends/file/e-book-backend-file.c 
b/src/addressbook/backends/file/e-book-backend-file.c
index 887c56b05..fd6b571e8 100644
--- a/src/addressbook/backends/file/e-book-backend-file.c
+++ b/src/addressbook/backends/file/e-book-backend-file.c
@@ -1737,6 +1737,62 @@ book_backend_file_get_contact_list_uids_sync (EBookBackendSync *backend,
        return success;
 }
 
+static gboolean
+book_backend_file_gather_addresses_cb (gpointer ptr_name,
+                                      gpointer ptr_email,
+                                      gpointer user_data)
+{
+       GPtrArray *array = user_data;
+       const gchar *email = ptr_email;
+
+       if (email && *email)
+               g_ptr_array_add (array, e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email));
+
+       return TRUE;
+}
+
+static gboolean
+book_backend_file_contains_email_sync (EBookBackendSync *backend,
+                                      const gchar *email_address,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       EBookQuery *book_query = NULL;
+       GPtrArray *array;
+       gchar *sexp = NULL;
+       gboolean success = FALSE;
+
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       d (printf ("book_backend_file_contains_email_sync (%s)\n", email_address));
+
+       array = g_ptr_array_new_full (1, (GDestroyNotify) e_book_query_unref);
+
+       e_book_util_foreach_address (email_address, book_backend_file_gather_addresses_cb, array);
+
+       if (array->len > 0)
+               book_query = e_book_query_or (array->len, (EBookQuery **) array->pdata, FALSE);
+
+       if (book_query)
+               sexp = e_book_query_to_string (book_query);
+
+       if (sexp) {
+               GSList *uids = NULL;
+
+               success = book_backend_file_get_contact_list_uids_sync (backend, sexp,
+                       &uids, cancellable, error);
+               success = success && uids != NULL;
+
+               g_slist_free_full (uids, g_free);
+       }
+
+       g_clear_pointer (&book_query, e_book_query_unref);
+       g_ptr_array_unref (array);
+       g_free (sexp);
+
+       return success;
+}
+
 static void
 book_backend_file_start_view (EBookBackend *backend,
                               EDataBookView *book_view)
@@ -2141,6 +2197,7 @@ e_book_backend_file_class_init (EBookBackendFileClass *class)
        backend_sync_class->get_contact_sync = book_backend_file_get_contact_sync;
        backend_sync_class->get_contact_list_sync = book_backend_file_get_contact_list_sync;
        backend_sync_class->get_contact_list_uids_sync = book_backend_file_get_contact_list_uids_sync;
+       backend_sync_class->contains_email_sync = book_backend_file_contains_email_sync;
 
        backend_class = E_BOOK_BACKEND_CLASS (class);
        backend_class->impl_get_backend_property = book_backend_file_get_backend_property;
diff --git a/src/addressbook/backends/ldap/e-book-backend-ldap.c 
b/src/addressbook/backends/ldap/e-book-backend-ldap.c
index 74c1b8961..0ceaf8b71 100644
--- a/src/addressbook/backends/ldap/e-book-backend-ldap.c
+++ b/src/addressbook/backends/ldap/e-book-backend-ldap.c
@@ -5756,6 +5756,247 @@ book_backend_ldap_get_contact_list_uids (EBookBackend *backend,
        }
 }
 
+typedef struct {
+       LDAPOp op;
+       gboolean found;
+} LDAPContainsEmailOp;
+
+static void
+contains_email_dtor (LDAPOp *op)
+{
+       LDAPContainsEmailOp *contains_email_op = (LDAPContainsEmailOp *) op;
+
+       g_slice_free (LDAPContainsEmailOp, contains_email_op);
+}
+
+static void
+contains_email_handler (LDAPOp *op,
+                       LDAPMessage *res)
+{
+       LDAPContainsEmailOp *contains_email_op = (LDAPContainsEmailOp *) op;
+       EBookBackendLDAP *bl = E_BOOK_BACKEND_LDAP (op->backend);
+       LDAPMessage *e;
+       gint msg_type;
+
+       if (enable_debug)
+               printf ("%s:\n", G_STRFUNC);
+
+       g_rec_mutex_lock (&eds_ldap_handler_lock);
+       if (!bl->priv->ldap) {
+               g_rec_mutex_unlock (&eds_ldap_handler_lock);
+               e_data_book_respond_contains_email (op->book, op->opid, EC_ERROR_NOT_CONNECTED (), FALSE);
+               ldap_op_finished (op);
+               if (enable_debug)
+                       printf ("%s: ldap handler is NULL\n", G_STRFUNC);
+               return;
+       }
+       g_rec_mutex_unlock (&eds_ldap_handler_lock);
+
+       msg_type = ldap_msgtype (res);
+       if (msg_type == LDAP_RES_SEARCH_ENTRY) {
+               g_rec_mutex_lock (&eds_ldap_handler_lock);
+               if (bl->priv->ldap)
+                       e = ldap_first_entry (bl->priv->ldap, res);
+               else
+                       e = NULL;
+               g_rec_mutex_unlock (&eds_ldap_handler_lock);
+
+               while (NULL != e) {
+                       EContact *contact;
+                       gchar *uid = NULL;
+
+                       contact = build_contact_from_entry (bl, e, NULL, &uid);
+                       g_clear_object (&contact);
+
+                       if (enable_debug)
+                               printf ("uid = %s\n", uid ? uid : "(null)");
+
+                       if (uid) {
+                               contains_email_op->found = TRUE;
+                               g_free (uid);
+                       }
+
+                       g_rec_mutex_lock (&eds_ldap_handler_lock);
+                       if (bl->priv->ldap)
+                               e = ldap_next_entry (bl->priv->ldap, e);
+                       else
+                               e = NULL;
+                       g_rec_mutex_unlock (&eds_ldap_handler_lock);
+               }
+       } else if (msg_type == LDAP_RES_SEARCH_REFERENCE) {
+               /* ignore references */
+       } else if (msg_type == LDAP_RES_SEARCH_RESULT) {
+               gchar *ldap_error_msg = NULL;
+               gint ldap_error;
+
+               g_rec_mutex_lock (&eds_ldap_handler_lock);
+               if (bl->priv->ldap) {
+                       ldap_parse_result (
+                               bl->priv->ldap, res, &ldap_error,
+                               NULL, &ldap_error_msg, NULL, NULL, 0);
+               } else {
+                       ldap_error = LDAP_SERVER_DOWN;
+               }
+               g_rec_mutex_unlock (&eds_ldap_handler_lock);
+               if (ldap_error != LDAP_SUCCESS) {
+                       printf (
+                               "%s: %02X (%s), additional info: %s\n", G_STRFUNC,
+                               ldap_error,
+                               ldap_err2string (ldap_error), ldap_error_msg);
+               }
+               if (ldap_error_msg)
+                       ldap_memfree (ldap_error_msg);
+
+               if (ldap_error == LDAP_TIMELIMIT_EXCEEDED)
+                       e_data_book_respond_contains_email (
+                               op->book, op->opid,
+                               EC_ERROR (E_CLIENT_ERROR_SEARCH_TIME_LIMIT_EXCEEDED),
+                               FALSE);
+               else if (ldap_error == LDAP_SIZELIMIT_EXCEEDED)
+                       e_data_book_respond_contains_email (
+                               op->book, op->opid,
+                               EC_ERROR (E_CLIENT_ERROR_SEARCH_SIZE_LIMIT_EXCEEDED),
+                               FALSE);
+               else if (ldap_error == LDAP_SUCCESS)
+                       e_data_book_respond_contains_email (
+                               op->book, op->opid,
+                               NULL,
+                               contains_email_op->found);
+               else
+                       e_data_book_respond_contains_email (
+                               op->book, op->opid,
+                               ldap_error_to_response (ldap_error),
+                               contains_email_op->found);
+
+               ldap_op_finished (op);
+       } else {
+               g_warning ("unhandled search result type %d returned", msg_type);
+               e_data_book_respond_contains_email (
+                       op->book, op->opid,
+                       e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR,
+                       _("%s: Unhandled search result type %d returned"), G_STRFUNC, msg_type),
+                       FALSE);
+               ldap_op_finished (op);
+       }
+}
+
+static gboolean
+book_backend_ldap_gather_addresses_cb (gpointer ptr_name,
+                                      gpointer ptr_email,
+                                      gpointer user_data)
+{
+       GPtrArray *array = user_data;
+       const gchar *email = ptr_email;
+
+       if (email && *email)
+               g_ptr_array_add (array, e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email));
+
+       return TRUE;
+}
+
+static void
+book_backend_ldap_contains_email (EBookBackend *backend,
+                                 EDataBook *book,
+                                 guint32 opid,
+                                 GCancellable *cancellable,
+                                 const gchar *email_address)
+{
+       EBookBackendLDAP *bl = E_BOOK_BACKEND_LDAP (backend);
+       EBookQuery *book_query = NULL;
+       GPtrArray *array;
+       gchar *sexp = NULL;
+       GError *error = NULL;
+       gboolean found = FALSE, finished = TRUE;
+
+       array = g_ptr_array_new_full (1, (GDestroyNotify) e_book_query_unref);
+
+       e_book_util_foreach_address (email_address, book_backend_ldap_gather_addresses_cb, array);
+
+       if (array->len > 0)
+               book_query = e_book_query_or (array->len, (EBookQuery **) array->pdata, FALSE);
+
+       if (book_query)
+               sexp = e_book_query_to_string (book_query);
+
+       if (sexp) {
+               if (bl->priv->cache) {
+                       GList *contacts;
+                       contacts = e_book_backend_cache_get_contacts (bl->priv->cache, sexp);
+                       found = contacts != NULL;
+                       g_list_free_full (contacts, g_object_unref);
+               }
+
+               if (!found && !e_backend_get_online (E_BACKEND (backend))) {
+                       if (!bl->priv->marked_for_offline)
+                               error = EC_ERROR (E_CLIENT_ERROR_REPOSITORY_OFFLINE);
+               } else if (!found) {
+                       LDAPContainsEmailOp *contains_email_op;
+                       gint contains_email_msgid = 0;
+                       EDataBookView *book_view;
+                       gint ldap_error;
+                       gchar *ldap_query;
+
+                       g_rec_mutex_lock (&eds_ldap_handler_lock);
+                       if (!bl->priv->ldap) {
+                               g_rec_mutex_unlock (&eds_ldap_handler_lock);
+                               error = EC_ERROR_NOT_CONNECTED ();
+                               goto out;
+                       }
+                       g_rec_mutex_unlock (&eds_ldap_handler_lock);
+
+                       contains_email_op = g_slice_new0 (LDAPContainsEmailOp);
+                       book_view = find_book_view (bl);
+
+                       ldap_query = e_book_backend_ldap_build_query (bl, sexp);
+
+                       if (enable_debug)
+                               printf ("checking emails with filter: '%s'\n", ldap_query);
+
+                       do {
+                               g_rec_mutex_lock (&eds_ldap_handler_lock);
+                               if (bl->priv->ldap) {
+                                       ldap_error = ldap_search_ext (
+                                               bl->priv->ldap,
+                                               bl->priv->ldap_rootdn,
+                                               bl->priv->ldap_scope,
+                                               ldap_query,
+                                               NULL, 0, NULL, NULL,
+                                               NULL, /* XXX timeout */
+                                               1 /* sizelimit */, &contains_email_msgid);
+                               } else {
+                                       ldap_error = LDAP_SERVER_DOWN;
+                               }
+                               g_rec_mutex_unlock (&eds_ldap_handler_lock);
+                       } while (e_book_backend_ldap_reconnect (bl, book_view, ldap_error));
+
+                       g_free (ldap_query);
+
+                       if (ldap_error == LDAP_SUCCESS) {
+                               finished = FALSE;
+                               ldap_op_add (
+                                       (LDAPOp *) contains_email_op, backend, book,
+                                       book_view, opid, contains_email_msgid,
+                                       contains_email_handler, contains_email_dtor);
+                       } else {
+                               error = ldap_error_to_response (ldap_error);
+                               contains_email_dtor ((LDAPOp *) contains_email_op);
+                       }
+               }
+       } else {
+               error = EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY);
+       }
+
+ out:
+       if (finished)
+               e_data_book_respond_contains_email (book, opid, error, found);
+       else
+               g_clear_error (&error);
+
+       g_clear_pointer (&book_query, e_book_query_unref);
+       g_ptr_array_unref (array);
+       g_free (sexp);
+}
+
 static ESourceAuthenticationResult
 book_backend_ldap_authenticate_sync (EBackend *backend,
                                     const ENamedParameters *credentials,
@@ -6027,6 +6268,7 @@ e_book_backend_ldap_class_init (EBookBackendLDAPClass *class)
        book_backend_class->impl_get_contact = book_backend_ldap_get_contact;
        book_backend_class->impl_get_contact_list = book_backend_ldap_get_contact_list;
        book_backend_class->impl_get_contact_list_uids = book_backend_ldap_get_contact_list_uids;
+       book_backend_class->impl_contains_email = book_backend_ldap_contains_email;
        book_backend_class->impl_start_view = book_backend_ldap_start_view;
        book_backend_class->impl_stop_view = book_backend_ldap_stop_view;
        book_backend_class->impl_refresh = book_backend_ldap_refresh;
diff --git a/src/addressbook/libebook-contacts/e-book-contacts-utils.c 
b/src/addressbook/libebook-contacts/e-book-contacts-utils.c
index fc16d544f..d4efc8f67 100644
--- a/src/addressbook/libebook-contacts/e-book-contacts-utils.c
+++ b/src/addressbook/libebook-contacts/e-book-contacts-utils.c
@@ -166,3 +166,44 @@ e_book_util_conflict_resolution_to_operation_flags (EConflictResolution conflict
 
        return E_BOOK_OPERATION_FLAG_CONFLICT_KEEP_LOCAL;
 }
+
+/**
+ * e_book_util_foreach_address:
+ * @email_address: one or more email addresses as string
+ * @func: (scope call): a function to call for each email
+ * @user_data (closure func): user data passed to @func
+ *
+ * Parses the @email_address and calls @func for each found address.
+ * The first parameter of the @func is the name, the second parameter
+ * of the @func is the email, the third parameters of the @func is
+ * the @user_data. The @func returns %TRUE, to continue processing.
+ *
+ * Since: 3.44
+ **/
+void
+e_book_util_foreach_address (const gchar *email_address,
+                            GHRFunc func,
+                            gpointer user_data)
+{
+       CamelInternetAddress *address;
+       const gchar *name, *email;
+       gint index;
+
+       g_return_if_fail (func != NULL);
+
+       if (!email_address || !*email_address)
+               return;
+
+       address = camel_internet_address_new ();
+       if (!camel_address_decode (CAMEL_ADDRESS (address), email_address)) {
+               g_object_unref (address);
+               return;
+       }
+
+       for (index = 0; camel_internet_address_get (address, index, &name, &email); index++) {
+               if (!func ((gpointer) name, (gpointer) email, user_data))
+                       break;
+       }
+
+       g_object_unref (address);
+}
diff --git a/src/addressbook/libebook-contacts/e-book-contacts-utils.h 
b/src/addressbook/libebook-contacts/e-book-contacts-utils.h
index 62781d490..1bded4f93 100644
--- a/src/addressbook/libebook-contacts/e-book-contacts-utils.h
+++ b/src/addressbook/libebook-contacts/e-book-contacts-utils.h
@@ -114,6 +114,10 @@ EConflictResolution
 guint32                e_book_util_conflict_resolution_to_operation_flags /* bit-or of EBookOperationFlags */
                                                (EConflictResolution conflict_resolution);
 
+void           e_book_util_foreach_address     (const gchar *email_address,
+                                                GHRFunc func,
+                                                gpointer user_data);
+
 #ifndef EDS_DISABLE_DEPRECATED
 
 /**
diff --git a/src/addressbook/libebook/e-book-client.c b/src/addressbook/libebook/e-book-client.c
index 7265dd5e2..91776c464 100644
--- a/src/addressbook/libebook/e-book-client.c
+++ b/src/addressbook/libebook/e-book-client.c
@@ -79,6 +79,7 @@ struct _AsyncContext {
        gchar *uid;
        GMainContext *context;
        guint32 opflags;
+       gboolean success;
 };
 
 struct _SignalClosure {
@@ -3871,6 +3872,164 @@ e_book_client_get_contacts_uids_sync (EBookClient *client,
        return TRUE;
 }
 
+/* Helper for e_book_client_contains_email() */
+static void
+book_client_contains_email_thread (GSimpleAsyncResult *simple,
+                                  GObject *source_object,
+                                  GCancellable *cancellable)
+{
+       AsyncContext *async_context;
+       GError *local_error = NULL;
+
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       async_context->success = e_book_client_contains_email_sync (E_BOOK_CLIENT (source_object), 
async_context->sexp, cancellable, &local_error);
+
+       if (local_error != NULL)
+               g_simple_async_result_take_error (simple, local_error);
+}
+
+/**
+ * e_book_client_contains_email:
+ * @client: an #EBookClient
+ * @email_address: an email address
+ * @cancellable: a #GCancellable; can be %NULL
+ * @callback: callback to call when a result is ready
+ * @user_data: user data for the @callback
+ *
+ * Asynchronously checks whether contains an @email_address. When the @email_address
+ * contains multiple addresses, then returns %TRUE when at least one
+ * address exists in the address book.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_book_client_contains_email_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.44
+ **/
+void
+e_book_client_contains_email (EBookClient *client,
+                             const gchar *email_address,
+                             GCancellable *cancellable,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_if_fail (E_IS_BOOK_CLIENT (client));
+       g_return_if_fail (email_address != NULL);
+
+       async_context = g_slice_new0 (AsyncContext);
+       async_context->sexp = g_strdup (email_address);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (client), callback, user_data,
+               e_book_client_contains_email);
+
+       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, book_client_contains_email_thread,
+               G_PRIORITY_DEFAULT, cancellable);
+
+       g_object_unref (simple);
+}
+
+/**
+ * e_book_client_contains_email_finish:
+ * @client: an #EBookClient
+ * @result: a #GAsyncResult
+ * @error: a #GError to set an error, if any
+ *
+ * Finishes previous call of e_book_client_contains_email().
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise.
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_book_client_contains_email_finish (EBookClient *client,
+                                    GAsyncResult *result,
+                                    GError **error)
+{
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (client),
+               e_book_client_contains_email), FALSE);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       if (g_simple_async_result_propagate_error (simple, error))
+               return FALSE;
+
+       return async_context->success;
+}
+
+/**
+ * e_book_client_cotnains_email_sync:
+ * @client: an #EBookClient
+ * @email_address: an email address
+ * @cancellable: a #GCancellable; can be %NULL
+ * @error: a #GError to set an error, if any
+ *
+ * Checks whether contains an @email_address. When the @email_address
+ * contains multiple addresses, then returns %TRUE when at least one
+ * address exists in the address book.
+ *
+ * If an error occurs, the function will set @error and return %FALSE.
+ *
+ * Returns: %TRUE when found the @email_address, %FALSE on failure
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_book_client_contains_email_sync (EBookClient *client,
+                                  const gchar *email_address,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       GError *local_error = NULL;
+       gchar *utf8_email_address;
+       gboolean success = FALSE;
+
+       g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       if (client->priv->direct_backend != NULL) {
+               /* Direct backend is not using D-Bus (obviously),
+                * so no need to strip D-Bus info from the error. */
+               success = e_book_backend_contains_email_sync (
+                       client->priv->direct_backend,
+                       email_address, cancellable, error);
+
+               return success;
+       }
+
+       utf8_email_address = e_util_utf8_make_valid (email_address);
+
+       e_dbus_address_book_call_contains_email_sync (
+               client->priv->dbus_proxy, utf8_email_address, &success,
+               cancellable, &local_error);
+
+       g_free (utf8_email_address);
+
+       if (local_error != NULL) {
+               g_dbus_error_strip_remote_error (local_error);
+               g_propagate_error (error, local_error);
+               return FALSE;
+       }
+
+       return success;
+}
+
 /* Helper for e_book_client_get_view() */
 static void
 book_client_get_view_in_dbus_thread (GSimpleAsyncResult *simple,
diff --git a/src/addressbook/libebook/e-book-client.h b/src/addressbook/libebook/e-book-client.h
index eca77f109..574b3cfa0 100644
--- a/src/addressbook/libebook/e-book-client.h
+++ b/src/addressbook/libebook/e-book-client.h
@@ -277,6 +277,20 @@ gboolean   e_book_client_get_contacts_uids_sync
                                                 GSList **out_contact_uids,
                                                 GCancellable *cancellable,
                                                 GError **error);
+void           e_book_client_contains_email    (EBookClient *client,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
+gboolean       e_book_client_contains_email_finish
+                                               (EBookClient *client,
+                                                GAsyncResult *result,
+                                                GError **error);
+gboolean       e_book_client_contains_email_sync
+                                               (EBookClient *client,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GError **error);
 void           e_book_client_get_view          (EBookClient *client,
                                                 const gchar *sexp,
                                                 GCancellable *cancellable,
diff --git a/src/addressbook/libedata-book/e-book-backend-sync.c 
b/src/addressbook/libedata-book/e-book-backend-sync.c
index 852b87dab..405218ddf 100644
--- a/src/addressbook/libedata-book/e-book-backend-sync.c
+++ b/src/addressbook/libedata-book/e-book-backend-sync.c
@@ -233,6 +233,24 @@ book_backend_sync_get_contact_list_uids_sync (EBookBackendSync *backend,
        return success;
 }
 
+static void
+book_backend_sync_contains_email (EBookBackend *backend,
+                                 EDataBook *book,
+                                 guint32 opid,
+                                 GCancellable *cancellable,
+                                 const gchar *email_address)
+{
+       GError *error = NULL;
+       gboolean found;
+
+       g_return_if_fail (E_IS_BOOK_BACKEND_SYNC (backend));
+       g_return_if_fail (E_IS_DATA_BOOK (book));
+
+       found = e_book_backend_sync_contains_email (E_BOOK_BACKEND_SYNC (backend), email_address, 
cancellable, &error);
+
+       e_data_book_respond_contains_email (book, opid, error, found);
+}
+
 static void
 e_book_backend_sync_class_init (EBookBackendSyncClass *klass)
 {
@@ -249,6 +267,7 @@ e_book_backend_sync_class_init (EBookBackendSyncClass *klass)
        book_backend_class->impl_get_contact = book_backend_sync_get_contact;
        book_backend_class->impl_get_contact_list = book_backend_sync_get_contact_list;
        book_backend_class->impl_get_contact_list_uids = book_backend_sync_get_contact_list_uids;
+       book_backend_class->impl_contains_email = book_backend_sync_contains_email;
 }
 
 static void
@@ -581,3 +600,41 @@ e_book_backend_sync_get_contact_list_uids (EBookBackendSync *backend,
 
        return FALSE;
 }
+
+/**
+ * e_book_backend_sync_contains_email:
+ * @backend: an #EBookBackendSync
+ * @email_address: an email address
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Checks whether contains an @email_address. When the @email_address
+ * contains multiple addresses, then returns %TRUE when at least one
+ * address exists in the address book.
+ *
+ * If an error occurs, the function will set @error and return %FALSE.
+ *
+ * Returns: %TRUE when found the @email_address, %FALSE on failure
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_book_backend_sync_contains_email (EBookBackendSync *backend,
+                                   const gchar *email_address,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       EBookBackendSyncClass *klass;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), FALSE);
+
+       klass = E_BOOK_BACKEND_SYNC_GET_CLASS (backend);
+       g_return_val_if_fail (klass != NULL, FALSE);
+
+       if (klass->contains_email_sync)
+               return klass->contains_email_sync (backend, email_address, cancellable, error);
+
+       g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_NOT_SUPPORTED, NULL));
+
+       return FALSE;
+}
diff --git a/src/addressbook/libedata-book/e-book-backend-sync.h 
b/src/addressbook/libedata-book/e-book-backend-sync.h
index 51176c8be..2a8ef14c9 100644
--- a/src/addressbook/libedata-book/e-book-backend-sync.h
+++ b/src/addressbook/libedata-book/e-book-backend-sync.h
@@ -139,8 +139,13 @@ struct _EBookBackendSyncClass {
                                                 GCancellable *cancellable,
                                                 GError **error);
 
+       gboolean        (*contains_email_sync)  (EBookBackendSync *backend,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GError **error);
+
        /* Padding for future expansion */
-       gpointer reserved_padding[20];
+       gpointer reserved_padding[19];
 };
 
 GType          e_book_backend_sync_get_type    (void) G_GNUC_CONST;
@@ -188,6 +193,11 @@ gboolean   e_book_backend_sync_get_contact_list_uids
                                                 GSList **out_uids, /* gchar * */
                                                 GCancellable *cancellable,
                                                 GError **error);
+gboolean       e_book_backend_sync_contains_email
+                                               (EBookBackendSync *backend,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GError **error);
 
 G_END_DECLS
 
diff --git a/src/addressbook/libedata-book/e-book-backend.c b/src/addressbook/libedata-book/e-book-backend.c
index f444bf3a5..852d6e4c2 100644
--- a/src/addressbook/libedata-book/e-book-backend.c
+++ b/src/addressbook/libedata-book/e-book-backend.c
@@ -2569,6 +2569,188 @@ e_book_backend_get_contact_list_uids_finish (EBookBackend *backend,
        return TRUE;
 }
 
+/**
+ * e_book_backend_contains_email_sync:
+ * @backend: an #EBookBackend
+ * @email_address: an email address
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Checks whether contains an @email_address. When the @email_address
+ * contains multiple addresses, then returns %TRUE when at least one
+ * address exists in the address book.
+ *
+ * If an error occurs, the function will set @error and return %FALSE.
+ *
+ * Returns: %TRUE when found the @email_address, %FALSE on failure
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_book_backend_contains_email_sync (EBookBackend *backend,
+                                   const gchar *email_address,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       EAsyncClosure *closure;
+       GAsyncResult *result;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND (backend), FALSE);
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       closure = e_async_closure_new ();
+
+       e_book_backend_contains_email (backend, email_address, cancellable,
+               e_async_closure_callback, closure);
+
+       result = e_async_closure_wait (closure);
+
+       success = e_book_backend_contains_email_finish (backend, result, error);
+
+       e_async_closure_free (closure);
+
+       return success;
+}
+
+/* Helper for e_book_backend_contains_email() */
+static void
+book_backend_contains_email_thread (GSimpleAsyncResult *simple,
+                                   GObject *source_object,
+                                   GCancellable *cancellable)
+{
+       EBookBackend *backend;
+       EBookBackendClass *klass;
+       EDataBook *data_book;
+       AsyncContext *async_context;
+
+       backend = E_BOOK_BACKEND (source_object);
+
+       klass = E_BOOK_BACKEND_GET_CLASS (backend);
+       g_return_if_fail (klass != NULL);
+
+       if (!klass->impl_contains_email) {
+               g_simple_async_result_take_error (simple, e_client_error_create 
(E_CLIENT_ERROR_NOT_SUPPORTED, NULL));
+               g_simple_async_result_complete_in_idle (simple);
+               return;
+       }
+
+       data_book = e_book_backend_ref_data_book (backend);
+       g_return_if_fail (data_book != NULL);
+
+       async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+       if (!e_book_backend_is_opened (backend)) {
+               g_simple_async_result_take_error (simple, e_client_error_create (E_CLIENT_ERROR_NOT_OPENED, 
NULL));
+               g_simple_async_result_complete_in_idle (simple);
+       } else {
+               guint32 opid;
+
+               opid = book_backend_stash_operation (backend, simple);
+
+               klass->impl_contains_email (backend, data_book, opid, cancellable, async_context->query);
+       }
+
+       g_object_unref (data_book);
+}
+
+/**
+ * e_book_backend_contains_email:
+ * @backend: an #EBookBackend
+ * @email_address: an email address
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously checks whether contains an @email_address. When the @email_address
+ * contains multiple addresses, then returns %TRUE when at least one
+ * address exists in the address book.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_book_backend_contains_email_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.44
+ **/
+void
+e_book_backend_contains_email (EBookBackend *backend,
+                              const gchar *email_address,
+                              GCancellable *cancellable,
+                              GAsyncReadyCallback callback,
+                              gpointer user_data)
+{
+       EBookBackendClass *klass;
+       GSimpleAsyncResult *simple;
+       AsyncContext *async_context;
+
+       g_return_if_fail (E_IS_BOOK_BACKEND (backend));
+       g_return_if_fail (email_address != NULL);
+
+       klass = E_BOOK_BACKEND_GET_CLASS (backend);
+       g_return_if_fail (klass != NULL);
+
+       async_context = g_slice_new0 (AsyncContext);
+       async_context->query = g_strdup (email_address);
+
+       simple = g_simple_async_result_new (
+               G_OBJECT (backend), callback, user_data,
+               e_book_backend_contains_email);
+
+       g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+       g_simple_async_result_set_op_res_gpointer (
+               simple, async_context, (GDestroyNotify) async_context_free);
+
+       if (klass->impl_contains_email != NULL) {
+               book_backend_push_operation (
+                       backend, simple, cancellable, FALSE,
+                       book_backend_contains_email_thread);
+               book_backend_dispatch_next_operation (backend);
+
+       } else {
+               g_simple_async_result_take_error (simple, e_client_error_create 
(E_CLIENT_ERROR_NOT_SUPPORTED, NULL));
+               g_simple_async_result_complete_in_idle (simple);
+       }
+
+       g_object_unref (simple);
+}
+
+/**
+ * e_book_backend_contains_email_finish:
+ * @backend: an #EBookBackend
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_book_backend_contains_email().
+ *
+ * If an error occurred, the function will set @error and return %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_book_backend_contains_email_finish (EBookBackend *backend,
+                                     GAsyncResult *result,
+                                     GError **error)
+{
+       GSimpleAsyncResult *simple;
+
+       g_return_val_if_fail (
+               g_simple_async_result_is_valid (
+               result, G_OBJECT (backend),
+               e_book_backend_contains_email), FALSE);
+
+       simple = G_SIMPLE_ASYNC_RESULT (result);
+
+       book_backend_unblock_operations (backend, simple);
+
+       if (g_simple_async_result_propagate_error (simple, error))
+               return FALSE;
+
+       return g_simple_async_result_get_op_res_gboolean (simple);
+}
+
 /**
  * e_book_backend_start_view:
  * @backend: an #EBookBackend
diff --git a/src/addressbook/libedata-book/e-book-backend.h b/src/addressbook/libedata-book/e-book-backend.h
index aa917098d..fef26fd2d 100644
--- a/src/addressbook/libedata-book/e-book-backend.h
+++ b/src/addressbook/libedata-book/e-book-backend.h
@@ -97,6 +97,7 @@ struct _EBookBackend {
  * @impl_dup_locale: Return the currently set locale setting (must be a string duplicate, for thread safety).
  * @impl_create_cursor: Create an #EDataBookCursor
  * @impl_delete_cursor: Delete an #EDataBookCursor previously created by this backend
+ * @impl_contains_email: Checkes whether the backend contains an email address
  * @closed: A signal notifying that the backend was closed
  * @shutdown: A signal notifying that the backend is being shut down
  *
@@ -198,8 +199,14 @@ struct _EBookBackendClass {
                                                 const gchar *sender);
        void            (*shutdown)             (EBookBackend *backend);
 
+       void            (*impl_contains_email)  (EBookBackend *backend,
+                                                EDataBook *book,
+                                                guint32 opid,
+                                                GCancellable *cancellable,
+                                                const gchar *email_address);
+
        /* Padding for future expansion */
-       gpointer reserved_padding[20];
+       gpointer reserved_padding[19];
 };
 
 GType          e_book_backend_get_type         (void) G_GNUC_CONST;
@@ -342,6 +349,20 @@ gboolean   e_book_backend_get_contact_list_uids_finish
                                                 GAsyncResult *result,
                                                 GQueue *out_uids,
                                                 GError **error);
+gboolean       e_book_backend_contains_email_sync
+                                               (EBookBackend *backend,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GError **error);
+void           e_book_backend_contains_email   (EBookBackend *backend,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
+gboolean       e_book_backend_contains_email_finish
+                                               (EBookBackend *backend,
+                                                GAsyncResult *result,
+                                                GError **error);
 
 void           e_book_backend_start_view       (EBookBackend *backend,
                                                 EDataBookView *view);
diff --git a/src/addressbook/libedata-book/e-book-cache.c b/src/addressbook/libedata-book/e-book-cache.c
index abe06d20f..1e62e7d57 100644
--- a/src/addressbook/libedata-book/e-book-cache.c
+++ b/src/addressbook/libedata-book/e-book-cache.c
@@ -5541,6 +5541,94 @@ e_book_cache_search_with_callback (EBookCache *book_cache,
        return ebc_search_internal (book_cache, sexp, SEARCH_FULL, NULL, func, user_data, cancellable, error);
 }
 
+typedef struct _ContainsEmailData {
+       GString *query;
+       const gchar *dbname;
+} ContainsEmailData;
+
+static gboolean
+ebc_gather_addresses_cb (gpointer in_name,
+                         gpointer in_email,
+                         gpointer user_data)
+{
+       ContainsEmailData *ced = user_data;
+       const gchar *email = in_email;
+
+       if (email && *email) {
+               if (ced->query->len)
+                       g_string_append (ced->query, " OR ");
+               else
+                       e_cache_sqlite_stmt_append_printf (ced->query, "SELECT COUNT(*) FROM %Q WHERE ", 
ced->dbname);
+
+               e_cache_sqlite_stmt_append_printf (ced->query, "%Q.value LIKE %Q", ced->dbname, email);
+       }
+
+       return TRUE;
+}
+
+/**
+ * e_book_cache_contains_email:
+ * @book_cache: an #EBookCache
+ * @email_address: an email address to check for
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Checks whether contains an @email_address. When the @email_address
+ * contains multiple addresses, then returns %TRUE when at least one
+ * address exists in the cache.
+ *
+ * If an error occurs, the function will set @error and return %FALSE.
+ *
+ * Returns: %TRUE when found the @email_address, %FALSE on failure
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_book_cache_contains_email (EBookCache *book_cache,
+                            const gchar *email_address,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       ContainsEmailData ced;
+       gboolean success = FALSE;
+       gint ii;
+
+       g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       for (ii = 0; ii < book_cache->priv->n_summary_fields; ii++) {
+               if (book_cache->priv->summary_fields[ii].field_id == E_CONTACT_EMAIL)
+                       break;
+       }
+
+       if (ii == book_cache->priv->n_summary_fields) {
+               g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_UNSUPPORTED_FIELD,
+                       _("Search by email not supported"));
+               return FALSE;
+       }
+
+       ced.query = g_string_new ("");
+       ced.dbname = book_cache->priv->summary_fields[ii].aux_table;
+
+       e_book_util_foreach_address (email_address, ebc_gather_addresses_cb, &ced);
+
+       if (ced.query->len == 0) {
+               g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_CONSTRAINT,
+                       _("No email address provided"));
+       } else {
+               gint n_found = 0;
+
+               g_string_append (ced.query, " LIMIT 1");
+
+               success = e_cache_sqlite_select (E_CACHE (book_cache), ced.query->str, ebc_get_int_cb, 
&n_found, cancellable, error);
+               success = success && n_found > 0;
+       }
+
+       g_string_free (ced.query, TRUE);
+
+       return success;
+}
+
 /**
  * e_book_cache_cursor_new:
  * @book_cache: An #EBookCache
diff --git a/src/addressbook/libedata-book/e-book-cache.h b/src/addressbook/libedata-book/e-book-cache.h
index 170dc6b9c..3a0d6b180 100644
--- a/src/addressbook/libedata-book/e-book-cache.h
+++ b/src/addressbook/libedata-book/e-book-cache.h
@@ -304,6 +304,10 @@ gboolean   e_book_cache_search_with_callback
                                                 gpointer user_data,
                                                 GCancellable *cancellable,
                                                 GError **error);
+gboolean       e_book_cache_contains_email     (EBookCache *book_cache,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GError **error);
 /* Cursor API */
 GType          e_book_cache_cursor_get_type    (void) G_GNUC_CONST;
 
diff --git a/src/addressbook/libedata-book/e-book-meta-backend.c 
b/src/addressbook/libedata-book/e-book-meta-backend.c
index f2382726f..a8029ab41 100644
--- a/src/addressbook/libedata-book/e-book-meta-backend.c
+++ b/src/addressbook/libedata-book/e-book-meta-backend.c
@@ -1819,6 +1819,29 @@ ebmb_get_contact_list_uids_sync (EBookBackendSync *book_backend,
        return e_book_meta_backend_search_uids_sync (E_BOOK_META_BACKEND (book_backend), query, out_uids, 
cancellable, error);
 }
 
+static gboolean
+ebmb_contains_email_sync (EBookBackendSync *book_backend,
+                         const gchar *email_address,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       EBookCache *cache;
+       gboolean found;
+
+       g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (book_backend));
+       if (!cache)
+               return FALSE;
+
+       found = e_book_cache_contains_email (cache, email_address, cancellable, error);
+
+       g_object_unref (cache);
+
+       return found;
+}
+
 static void
 ebmb_start_view (EBookBackend *book_backend,
                 EDataBookView *view)
@@ -2513,6 +2536,7 @@ e_book_meta_backend_class_init (EBookMetaBackendClass *klass)
        book_backend_sync_class->get_contact_sync = ebmb_get_contact_sync;
        book_backend_sync_class->get_contact_list_sync = ebmb_get_contact_list_sync;
        book_backend_sync_class->get_contact_list_uids_sync = ebmb_get_contact_list_uids_sync;
+       book_backend_sync_class->contains_email_sync = ebmb_contains_email_sync;
 
        book_backend_class = E_BOOK_BACKEND_CLASS (klass);
        book_backend_class->impl_get_backend_property = ebmb_get_backend_property;
diff --git a/src/addressbook/libedata-book/e-data-book.c b/src/addressbook/libedata-book/e-data-book.c
index 95646ae3f..2d48488b2 100644
--- a/src/addressbook/libedata-book/e-data-book.c
+++ b/src/addressbook/libedata-book/e-data-book.c
@@ -1068,6 +1068,56 @@ data_book_handle_get_cursor_cb (EDBusAddressBook *dbus_interface,
        return TRUE;
 }
 
+static void
+data_book_complete_contains_email_cb (GObject *source_object,
+                                     GAsyncResult *result,
+                                     gpointer user_data)
+{
+       AsyncContext *async_context = user_data;
+       GError *error = NULL;
+       gboolean res;
+
+       res = e_book_backend_contains_email_finish (E_BOOK_BACKEND (source_object), result, &error);
+
+       if (error == NULL) {
+               e_dbus_address_book_complete_contains_email (
+                       async_context->dbus_interface,
+                       async_context->invocation,
+                       res);
+       } else {
+               data_book_convert_to_client_error (error);
+               g_dbus_method_invocation_take_error (
+                       async_context->invocation, error);
+       }
+
+       async_context_free (async_context);
+}
+
+static gboolean
+data_book_handle_contains_email_cb (EDBusAddressBook *dbus_interface,
+                                   GDBusMethodInvocation *invocation,
+                                   const gchar *in_email,
+                                   EDataBook *data_book)
+{
+       EBookBackend *backend;
+       AsyncContext *async_context;
+
+       backend = e_data_book_ref_backend (data_book);
+       g_return_val_if_fail (backend != NULL, FALSE);
+
+       async_context = async_context_new (data_book, invocation);
+
+       e_book_backend_contains_email (
+               backend, in_email,
+               async_context->cancellable,
+               data_book_complete_contains_email_cb,
+               async_context);
+
+       g_object_unref (backend);
+
+       return TRUE;
+}
+
 static void
 data_book_source_unset_last_credentials_required_arguments_cb (GObject *source_object,
                                                               GAsyncResult *result,
@@ -1487,6 +1537,49 @@ e_data_book_respond_remove_contacts (EDataBook *book,
        g_object_unref (backend);
 }
 
+/**
+ * e_data_book_respond_contains_email:
+ * @book: An #EDataBook
+ * @opid: An operation ID
+ * @error: Operation error, if any, automatically freed if passed it
+ * @found: %TRUE, when found the email in the address book
+ *
+ * Finishes a call to check whether contains an email address.
+ *
+ * Since: 3.44
+ **/
+void
+e_data_book_respond_contains_email (EDataBook *book,
+                                   guint32 opid,
+                                   GError *error,
+                                   gboolean found)
+{
+       EBookBackend *backend;
+       GSimpleAsyncResult *simple;
+
+       g_return_if_fail (E_IS_DATA_BOOK (book));
+
+       backend = e_data_book_ref_backend (book);
+       g_return_if_fail (backend != NULL);
+
+       simple = e_book_backend_prepare_for_completion (backend, opid, NULL);
+       g_return_if_fail (simple != NULL);
+
+       /* Translators: This is prefix to a detailed error message */
+       g_prefix_error (&error, "%s", _("Cannot find email address: "));
+
+       if (error == NULL) {
+               g_simple_async_result_set_op_res_gboolean (simple, found);
+       } else {
+               g_simple_async_result_take_error (simple, error);
+       }
+
+       g_simple_async_result_complete_in_idle (simple);
+
+       g_object_unref (simple);
+       g_object_unref (backend);
+}
+
 /**
  * e_data_book_report_error:
  * @book: An #EDataBook
@@ -1948,6 +2041,10 @@ e_data_book_init (EDataBook *data_book)
                dbus_interface, "handle-get-cursor",
                G_CALLBACK (data_book_handle_get_cursor_cb),
                data_book);
+       g_signal_connect (
+               dbus_interface, "handle-contains-email",
+               G_CALLBACK (data_book_handle_contains_email_cb),
+               data_book);
        g_signal_connect (
                dbus_interface, "handle-close",
                G_CALLBACK (data_book_handle_close_cb),
diff --git a/src/addressbook/libedata-book/e-data-book.h b/src/addressbook/libedata-book/e-data-book.h
index 18808c15d..962c3758d 100644
--- a/src/addressbook/libedata-book/e-data-book.h
+++ b/src/addressbook/libedata-book/e-data-book.h
@@ -112,6 +112,11 @@ void               e_data_book_respond_get_contact_list_uids
                                                 guint32 opid,
                                                 GError *error,
                                                 const GSList *uids); /* gchar * */
+void           e_data_book_respond_contains_email
+                                               (EDataBook *book,
+                                                guint32 opid,
+                                                GError *error,
+                                                gboolean found);
 
 void           e_data_book_report_error        (EDataBook *book,
                                                 const gchar *message);
diff --git a/src/camel/camel-filter-search.c b/src/camel/camel-filter-search.c
index c9854bebc..df429b4cd 100644
--- a/src/camel/camel-filter-search.c
+++ b/src/camel/camel-filter-search.c
@@ -93,6 +93,7 @@ static CamelSExpResult *junk_test (struct _CamelSExp *f, gint argc, struct _Came
 static CamelSExpResult *message_location (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, 
FilterMessageSearch *fms);
 static CamelSExpResult *make_time_func (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, 
FilterMessageSearch *fms);
 static CamelSExpResult *compare_date_func (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, 
FilterMessageSearch *fms);
+static CamelSExpResult *addressbook_contains_func (struct _CamelSExp *f, gint argc, struct _CamelSExpResult 
**argv, FilterMessageSearch *fms);
 
 /* builtin functions */
 static struct {
@@ -126,7 +127,8 @@ static struct {
        { "junk-test",          (CamelSExpFunc) junk_test,          0 },
        { "message-location",   (CamelSExpFunc) message_location,   0 },
        { "make-time",          (CamelSExpFunc) make_time_func,     0 },
-       { "compare-date",       (CamelSExpFunc) compare_date_func,  0 }
+       { "compare-date",       (CamelSExpFunc) compare_date_func,  0 },
+       { "addressbook-contains",(CamelSExpFunc) addressbook_contains_func, 0 }
 };
 
 static void
@@ -1316,6 +1318,64 @@ compare_date_func (CamelSExp *sexp,
        return res;
 }
 
+static CamelSExpResult *
+addressbook_contains_func (CamelSExp *sexp,
+                          gint argc,
+                          CamelSExpResult **argv,
+                          FilterMessageSearch *fms)
+{
+       CamelSExpResult *res;
+
+       res = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+       res->value.boolean = FALSE;
+
+       if (argc == 2) {
+               GError *local_error = NULL;
+               const gchar *book_uid, *header, *email_address = NULL;
+               gboolean address_set = FALSE;
+
+               if (argv[0]->type != CAMEL_SEXP_RES_STRING) {
+                       camel_filter_search_log (fms, "addressbook-contains result:%d (incorrect first 
argument type, expects string)", res->value.boolean);
+                       return res;
+               }
+
+               if (argv[1]->type != CAMEL_SEXP_RES_STRING) {
+                       camel_filter_search_log (fms, "addressbook-contains result:%d (incorrect second 
argument type, expects string)", res->value.boolean);
+                       return res;
+               }
+
+               book_uid = argv[0]->value.string;
+               header = argv[1]->value.string;
+
+               if (header && g_ascii_strcasecmp (header, "From") == 0) {
+                       email_address = camel_message_info_get_from (fms->info);
+                       address_set = TRUE;
+               } else if (header && g_ascii_strcasecmp (header, "To") == 0) {
+                       email_address = camel_message_info_get_to (fms->info);
+                       address_set = TRUE;
+               } else if (header && g_ascii_strcasecmp (header, "Cc") == 0) {
+                       email_address = camel_message_info_get_cc (fms->info);
+                       address_set = TRUE;
+               }
+
+               res->value.boolean = email_address && camel_session_addressbook_contains_sync (fms->session,
+                       book_uid, email_address, fms->cancellable, &local_error);
+
+               if (!address_set)
+                       camel_filter_search_log (fms, "addressbook-contains result:%d unsupported header 
'%s'", res->value.boolean, header);
+               else if (local_error)
+                       camel_filter_search_log (fms, "addressbook-contains result:%d for '%s' in '%s' 
error:%s", res->value.boolean, email_address, book_uid, local_error->message);
+               else
+                       camel_filter_search_log (fms, "addressbook-contains result:%d for '%s' in '%s' ", 
res->value.boolean, email_address, book_uid);
+
+               g_clear_error (&local_error);
+       } else {
+               camel_filter_search_log (fms, "addressbook-contains result:%d expects two arguments, received 
%d", res->value.boolean, argc);
+       }
+
+       return res;
+}
+
 static const gchar *
 camel_search_result_to_string (gint value)
 {
diff --git a/src/camel/camel-folder-search.c b/src/camel/camel-folder-search.c
index b5db023b2..f865c690c 100644
--- a/src/camel/camel-folder-search.c
+++ b/src/camel/camel-folder-search.c
@@ -229,6 +229,10 @@ static struct {
        { "compare-date",
          G_STRUCT_OFFSET (CamelFolderSearchClass, compare_date),
          CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+       { "addressbook-contains",
+         G_STRUCT_OFFSET (CamelFolderSearchClass, addressbook_contains),
+         CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (CamelFolderSearch, camel_folder_search, G_TYPE_OBJECT)
@@ -1799,6 +1803,68 @@ folder_search_compare_date (CamelSExp *sexp,
        return res;
 }
 
+static CamelSExpResult *
+folder_search_addressbook_contains (CamelSExp *sexp,
+                                   gint argc,
+                                   CamelSExpResult **argv,
+                                   CamelFolderSearch *search)
+{
+       CamelSExpResult *res;
+
+       r (printf ("executing addressbook-contains\n"));
+
+       if (!search->priv->current) {
+               res = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+               res->value.ptrarray = g_ptr_array_new ();
+
+               return res;
+       }
+
+       res = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+       res->value.boolean = FALSE;
+
+       if (argc == 2) {
+               const gchar *book_uid, *header, *email_address = NULL;
+               gboolean address_set = FALSE;
+
+               if (argv[0]->type != CAMEL_SEXP_RES_STRING ||
+                   argv[1]->type != CAMEL_SEXP_RES_STRING)
+                       return res;
+
+               book_uid = argv[0]->value.string;
+               header = argv[1]->value.string;
+
+               if (header && g_ascii_strcasecmp (header, "From") == 0) {
+                       email_address = camel_message_info_get_from (search->priv->current);
+                       address_set = TRUE;
+               } else if (header && g_ascii_strcasecmp (header, "To") == 0) {
+                       email_address = camel_message_info_get_to (search->priv->current);
+                       address_set = TRUE;
+               } else if (header && g_ascii_strcasecmp (header, "Cc") == 0) {
+                       email_address = camel_message_info_get_cc (search->priv->current);
+                       address_set = TRUE;
+               }
+
+               if (address_set) {
+                       CamelStore *store;
+                       CamelSession *session = NULL;
+
+                       store = camel_folder_get_parent_store (search->priv->folder);
+                       if (store)
+                               session = camel_service_ref_session (CAMEL_SERVICE (store));
+
+                       if (session) {
+                               res->value.boolean = email_address && camel_session_addressbook_contains_sync 
(session,
+                                       book_uid, email_address, search->priv->cancellable, NULL);
+
+                               g_object_unref (session);
+                       }
+               }
+       }
+
+       return res;
+}
+
 static void
 camel_folder_search_class_init (CamelFolderSearchClass *class)
 {
@@ -1834,6 +1900,7 @@ camel_folder_search_class_init (CamelFolderSearchClass *class)
        class->message_location = folder_search_message_location;
        class->make_time = folder_search_make_time;
        class->compare_date = folder_search_compare_date;
+       class->addressbook_contains = folder_search_addressbook_contains;
 }
 
 static void
@@ -2116,6 +2183,7 @@ do_search_in_memory (CamelFolder *search_in_folder,
                "header-contains",
                "header-has-words",
                "header-ends-with",
+               "addressbook-contains",
                NULL };
        gint i;
 
diff --git a/src/camel/camel-folder-search.h b/src/camel/camel-folder-search.h
index be6d6f05f..0b26332f3 100644
--- a/src/camel/camel-folder-search.h
+++ b/src/camel/camel-folder-search.h
@@ -271,8 +271,17 @@ struct _CamelFolderSearchClass {
                                                 CamelSExpResult **argv,
                                                 CamelFolderSearch *search);
 
+       /* (addressbook-contains "book_uid" "header-name")
+        * Checks whether at least one address from header "header-name"
+        * is in the address book "book_uid". Not all headers are supported. */
+       CamelSExpResult *       (*addressbook_contains)
+                                               (CamelSExp *sexp,
+                                                gint argc,
+                                                CamelSExpResult **argv,
+                                                CamelFolderSearch *search);
+
        /* Padding for future expansion */
-       gpointer reserved[18];
+       gpointer reserved[17];
 };
 
 GType          camel_folder_search_get_type    (void) G_GNUC_CONST;
diff --git a/src/camel/camel-session.c b/src/camel/camel-session.c
index 1a060006b..688dc2a71 100644
--- a/src/camel/camel-session.c
+++ b/src/camel/camel-session.c
@@ -1329,6 +1329,68 @@ camel_session_lookup_addressbook (CamelSession *session,
        return class->lookup_addressbook (session, name);
 }
 
+/**
+ * CAMEL_SESSION_BOOK_UID_ANY:
+ *
+ * Can be used with camel_session_addressbook_contains_sync() as the book UID,
+ * meaning to check in all configured address books.
+ *
+ * Since: 3.44
+ **/
+
+/**
+ * CAMEL_SESSION_BOOK_UID_COMPLETION:
+ *
+ * Can be used with camel_session_addressbook_contains_sync() as the book UID,
+ * meaning to check in all address books enabled for auto-completion.
+ *
+ * Since: 3.44
+ **/
+
+/**
+ * camel_session_addressbook_contains_sync:
+ * @session: a #CamelSession
+ * @book_uid: an address book UID
+ * @email_address: an email address to check for
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Look up in an address book @book_uid for an address @email_address
+ * and returns whether any such contact exists.
+ *
+ * The @book_uid can be either one of the special constants
+ * %CAMEL_SESSION_BOOK_UID_ANY or %CAMEL_SESSION_BOOK_UID_COMPLETION,
+ * or it can be a UID of a configured address book.
+ *
+ * The @email_address can contain multiple addresses, then the function
+ * checks whether any of the given addresses is in the address book.
+ *
+ * Returns: %TRUE, when the @email_address could be found in the @book_uid
+ *
+ * Since: 3.44
+ **/
+gboolean
+camel_session_addressbook_contains_sync (CamelSession *session,
+                                        const gchar *book_uid,
+                                        const gchar *email_address,
+                                        GCancellable *cancellable,
+                                        GError **error)
+{
+       CamelSessionClass *klass;
+
+       g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+       g_return_val_if_fail (book_uid != NULL, FALSE);
+       g_return_val_if_fail (email_address != NULL, FALSE);
+
+       klass = CAMEL_SESSION_GET_CLASS (session);
+       g_return_val_if_fail (klass != NULL, FALSE);
+
+       if (!klass->addressbook_contains_sync)
+               return FALSE;
+
+       return klass->addressbook_contains_sync (session, book_uid, email_address, cancellable, error);
+}
+
 /**
  * camel_session_get_online:
  * @session: a #CamelSession
diff --git a/src/camel/camel-session.h b/src/camel/camel-session.h
index 31a4fc553..897225da1 100644
--- a/src/camel/camel-session.h
+++ b/src/camel/camel-session.h
@@ -54,6 +54,9 @@
 
 G_BEGIN_DECLS
 
+#define CAMEL_SESSION_BOOK_UID_ANY "*any"
+#define CAMEL_SESSION_BOOK_UID_COMPLETION "*completion"
+
 typedef struct _CamelSession CamelSession;
 typedef struct _CamelSessionClass CamelSessionClass;
 typedef struct _CamelSessionPrivate CamelSessionPrivate;
@@ -146,9 +149,15 @@ struct _CamelSessionClass {
                                                 GSList **out_certificates, /* gchar * */
                                                 GCancellable *cancellable,
                                                 GError **error);
+       gboolean        (*addressbook_contains_sync)
+                                               (CamelSession *session,
+                                                const gchar *book_uid,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GError **error);
 
        /* Padding for future expansion */
-       gpointer reserved_methods[19];
+       gpointer reserved_methods[18];
 
        /* Signals */
        void            (*job_started)          (CamelSession *session,
@@ -239,6 +248,12 @@ void               camel_session_set_junk_headers  (CamelSession *session,
                                                 gint len);
 gboolean       camel_session_lookup_addressbook (CamelSession *session,
                                                 const gchar *name);
+gboolean       camel_session_addressbook_contains_sync
+                                               (CamelSession *session,
+                                                const gchar *book_uid,
+                                                const gchar *email_address,
+                                                GCancellable *cancellable,
+                                                GError **error);
 
 gboolean       camel_session_authenticate_sync (CamelSession *session,
                                                 CamelService *service,
diff --git a/src/private/org.gnome.evolution.dataserver.AddressBook.xml 
b/src/private/org.gnome.evolution.dataserver.AddressBook.xml
index 61c645ba0..b3d16d30d 100644
--- a/src/private/org.gnome.evolution.dataserver.AddressBook.xml
+++ b/src/private/org.gnome.evolution.dataserver.AddressBook.xml
@@ -83,4 +83,9 @@
     <arg name="object_path" direction="out" type="o"/>
   </method>
 
+  <method name="ContainsEmail">
+    <arg name="email" direction="in" type="s"/>
+    <arg name="result" direction="out" type="b"/>
+  </method>
+
 </interface>
diff --git a/tests/libedata-book/test-book-meta-backend.c b/tests/libedata-book/test-book-meta-backend.c
index b3fdddd6e..dd7ca91c2 100644
--- a/tests/libedata-book/test-book-meta-backend.c
+++ b/tests/libedata-book/test-book-meta-backend.c
@@ -1750,6 +1750,46 @@ test_cursor (EBookMetaBackend *meta_backend)
        g_assert (success);
 }
 
+static void
+test_contains_email (EBookMetaBackend *meta_backend)
+{
+       EBookBackendSyncClass *backend_sync_class;
+       gboolean success;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       backend_sync_class = E_BOOK_BACKEND_SYNC_GET_CLASS (meta_backend);
+       g_return_if_fail (backend_sync_class != NULL);
+       g_return_if_fail (backend_sync_class->contains_email_sync != NULL);
+
+       success = backend_sync_class->contains_email_sync (E_BOOK_BACKEND_SYNC (meta_backend),
+               "bobby brown com", NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       success = backend_sync_class->contains_email_sync (E_BOOK_BACKEND_SYNC (meta_backend),
+               "\"Bobby\" <bobby brown org>", NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       success = backend_sync_class->contains_email_sync (E_BOOK_BACKEND_SYNC (meta_backend),
+               "\"Unknown\" <unknown@no.where>", NULL, &error);
+       g_assert_no_error (error);
+       g_assert (!success);
+
+       success = backend_sync_class->contains_email_sync (E_BOOK_BACKEND_SYNC (meta_backend),
+               "\"Unknown\" <unknown@no.where>, \"Bobby\" <bobby brown org>", NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       success = backend_sync_class->contains_email_sync (E_BOOK_BACKEND_SYNC (meta_backend),
+               "", NULL, &error);
+       g_assert_error (error, E_CACHE_ERROR, E_CACHE_ERROR_CONSTRAINT);
+       g_assert (!success);
+       g_clear_error (&error);
+}
+
 typedef void (* TestWithMainLoopFunc) (EBookMetaBackend *meta_backend);
 
 typedef struct _MainLoopThreadData {
@@ -1843,6 +1883,7 @@ main_loop_wrapper (test_get_contact_list)
 main_loop_wrapper (test_get_contact_list_uids)
 main_loop_wrapper (test_refresh)
 main_loop_wrapper (test_cursor)
+main_loop_wrapper (test_contains_email)
 
 #undef main_loop_wrapper
 
@@ -1899,6 +1940,8 @@ main (gint argc,
                tcu_fixture_setup, test_refresh_tcu, tcu_fixture_teardown);
        g_test_add ("/EBookMetaBackend/Cursor", TCUFixture, &closure,
                tcu_fixture_setup, test_cursor_tcu, tcu_fixture_teardown);
+       g_test_add ("/EBookMetaBackend/ContainsEmail", TCUFixture, &closure,
+               tcu_fixture_setup, test_contains_email_tcu, tcu_fixture_teardown);
 
        res = g_test_run ();
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]