[evolution-data-server/gnome-3-22] Bug 777431 - [IMAPx] Gmail's BODY search returns only whole word matches



commit 9789a30a14ee1dfd6bad34bb1580a64ce26818ee
Author: Milan Crha <mcrha redhat com>
Date:   Wed Jan 18 11:25:48 2017 +0100

    Bug 777431 - [IMAPx] Gmail's BODY search returns only whole word matches

 camel/camel-folder-search.c                |   74 ++++++++++++++++++++++++---
 camel/camel-folder-search.h                |    5 ++
 camel/camel-folder.c                       |   53 +++++++++++++++-----
 camel/camel-folder.h                       |    4 ++
 camel/providers/imapx/camel-imapx-search.c |   64 ++++++++++++++++++++++++
 camel/providers/imapx/camel-imapx-store.c  |   10 ++--
 camel/providers/imapx/camel-imapx-store.h  |    2 +
 7 files changed, 186 insertions(+), 26 deletions(-)
---
diff --git a/camel/camel-folder-search.c b/camel/camel-folder-search.c
index 38eb403..4466e6f 100644
--- a/camel/camel-folder-search.c
+++ b/camel/camel-folder-search.c
@@ -75,6 +75,8 @@ struct _CamelFolderSearchPrivate {
         * This saves us having to perform more complex UID life cycle management
         * and the overhead from the additional refs/unrefs it would require. */
        GPtrArray *owned_pstrings;
+
+       gboolean only_cached_messages;
 };
 
 typedef enum {
@@ -269,13 +271,28 @@ fill_thread_table (CamelFolderThreadNode *root,
 }
 
 static CamelMimeMessage *
+search_get_message_sync (CamelFolderSearch *search,
+                        CamelFolder *folder,
+                        const gchar *uid,
+                        GCancellable *cancellable)
+{
+       g_return_val_if_fail (CAMEL_IS_FOLDER_SEARCH (search), NULL);
+       g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       if (camel_folder_search_get_only_cached_messages (search))
+               return camel_folder_get_message_cached (folder, uid, cancellable);
+
+       return camel_folder_get_message_sync (folder, uid, cancellable, NULL);
+}
+
+static CamelMimeMessage *
 get_current_message (CamelFolderSearch *search)
 {
        if (!search || !search->folder || !search->current)
                return NULL;
 
-       return camel_folder_get_message_sync (
-               search->folder, search->current->uid, search->priv->cancellable, NULL);
+       return search_get_message_sync (search, search->folder, search->current->uid, 
search->priv->cancellable);
 }
 
 static CamelSExpResult *
@@ -576,7 +593,8 @@ match_words_1message (CamelDataWrapper *object,
 }
 
 static gboolean
-match_words_message (CamelFolder *folder,
+match_words_message (CamelFolderSearch *search,
+                    CamelFolder *folder,
                      const gchar *uid,
                      struct _camel_search_words *words,
                      GCancellable *cancellable,
@@ -589,7 +607,7 @@ match_words_message (CamelFolder *folder,
        if (g_cancellable_set_error_if_cancelled (cancellable, error))
                return truth;
 
-       msg = camel_folder_get_message_sync (folder, uid, cancellable, NULL);
+       msg = search_get_message_sync (search, folder, uid, cancellable);
        if (msg) {
                mask = 0;
                truth = match_words_1message ((CamelDataWrapper *) msg, words, &mask, cancellable);
@@ -622,7 +640,7 @@ match_words_messages (CamelFolderSearch *search,
                for (i = 0; i < indexed->len && !g_cancellable_is_cancelled (cancellable); i++) {
                        const gchar *uid = g_ptr_array_index (indexed, i);
 
-                       if (match_words_message (
+                       if (match_words_message (search,
                                        search->folder, uid, words,
                                        cancellable, error))
                                g_ptr_array_add (matches, (gchar *) uid);
@@ -635,7 +653,7 @@ match_words_messages (CamelFolderSearch *search,
                for (i = 0; i < v->len && !g_cancellable_is_cancelled (cancellable); i++) {
                        gchar *uid = g_ptr_array_index (v, i);
 
-                       if (match_words_message (
+                       if (match_words_message (search,
                                search->folder, uid, words,
                                cancellable, error))
                                g_ptr_array_add (matches, (gchar *) uid);
@@ -1079,7 +1097,7 @@ folder_search_body_contains (CamelSExp *sexp,
                                                                error);
                                        } else {
                                                /* TODO: cache current message incase of multiple body search 
terms */
-                                               truth = match_words_message (
+                                               truth = match_words_message (search,
                                                        search->folder,
                                                        camel_message_info_get_uid (search->current),
                                                        words,
@@ -1181,8 +1199,7 @@ folder_search_body_regex (CamelSExp *sexp,
                        for (i = 0; i < v->len && !g_cancellable_is_cancelled (search->priv->cancellable); 
i++) {
                                gchar *uid = g_ptr_array_index (v, i);
 
-                               message = camel_folder_get_message_sync (
-                                       search->folder, uid, search->priv->cancellable, NULL);
+                               message = search_get_message_sync (search, search->folder, uid, 
search->priv->cancellable);
                                if (message) {
                                        if (camel_search_message_body_contains ((CamelDataWrapper *) message, 
&pattern)) {
                                                g_ptr_array_add (r->value.ptrarray, uid);
@@ -1698,6 +1715,7 @@ static void
 camel_folder_search_init (CamelFolderSearch *search)
 {
        search->priv = CAMEL_FOLDER_SEARCH_GET_PRIVATE (search);
+       search->priv->only_cached_messages = FALSE;
        search->sexp = camel_sexp_new ();
 }
 
@@ -1735,6 +1753,44 @@ camel_folder_search_new (void)
 }
 
 /**
+ * camel_folder_search_set_only_cached_messages:
+ * @search: a #CamelFolderSearch
+ * @only_cached_messages: a value to set
+ *
+ * Sets whether only locally cached messages can be searched. The default
+ * value is %FALSE, which means that when a message is required and it is
+ * not available locally, then it is downloaded from the server, if possible.
+ *
+ * Since: 3.22.5
+ **/
+void
+camel_folder_search_set_only_cached_messages (CamelFolderSearch *search,
+                                             gboolean only_cached_messages)
+{
+       g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search));
+
+       search->priv->only_cached_messages = only_cached_messages;
+}
+
+/**
+ * camel_folder_search_get_only_cached_messages:
+ * @search: a #CamelFolderSearch
+ *
+ * Returns: Whether only cached messages can be searched. See
+ *    camel_folder_search_set_only_cached_messages() for more
+ *    information what it means.
+ *
+ * Since: 3.22.5
+ **/
+gboolean
+camel_folder_search_get_only_cached_messages (CamelFolderSearch *search)
+{
+       g_return_val_if_fail (CAMEL_IS_FOLDER_SEARCH (search), FALSE);
+
+       return search->priv->only_cached_messages;
+}
+
+/**
  * camel_folder_search_set_folder:
  * @search:
  * @folder: A folder.
diff --git a/camel/camel-folder-search.h b/camel/camel-folder-search.h
index 7c88f68..121d38f 100644
--- a/camel/camel-folder-search.h
+++ b/camel/camel-folder-search.h
@@ -268,6 +268,11 @@ struct _CamelFolderSearchClass {
 GType          camel_folder_search_get_type    (void) G_GNUC_CONST;
 CamelFolderSearch *
                camel_folder_search_new         (void);
+void           camel_folder_search_set_only_cached_messages
+                                               (CamelFolderSearch *search,
+                                                gboolean only_cached_messages);
+gboolean       camel_folder_search_get_only_cached_messages
+                                               (CamelFolderSearch *search);
 
 /* XXX This stuff currently gets cleared when you run a search.
  *     What on earth was i thinking ... */
diff --git a/camel/camel-folder.c b/camel/camel-folder.c
index bf2f2bf..bcb79b1 100644
--- a/camel/camel-folder.c
+++ b/camel/camel-folder.c
@@ -3022,7 +3022,7 @@ camel_folder_get_message_sync (CamelFolder *folder,
                                GError **error)
 {
        CamelFolderClass *class;
-       CamelMimeMessage *message = NULL;
+       CamelMimeMessage *message;
 
        g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
        g_return_val_if_fail (message_uid != NULL, NULL);
@@ -3038,21 +3038,14 @@ camel_folder_get_message_sync (CamelFolder *folder,
                message_uid, camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store 
(folder))),
                camel_folder_get_full_name (folder));
 
-       if (class->get_message_cached) {
-               /* Return cached message, if available locally; this should
-                * not do any network I/O, only check if message is already
-                * downloaded and return it quicker, not being blocked by
-                * the folder's lock.  Returning NULL is not considered as
-                * an error, it just means that the message is still
-                * to-be-downloaded. */
-               message = class->get_message_cached (
-                       folder, message_uid, cancellable);
-       }
+       message = camel_folder_get_message_cached (folder, message_uid, cancellable);
 
        if (message == NULL) {
                /* Recover from a dropped connection, unless we're offline. */
-               if (!folder_maybe_connect_sync (folder, cancellable, error))
+               if (!folder_maybe_connect_sync (folder, cancellable, error)) {
+                       camel_operation_pop_message (cancellable);
                        return NULL;
+               }
 
                camel_folder_lock (folder);
 
@@ -3094,6 +3087,42 @@ camel_folder_get_message_sync (CamelFolder *folder,
        return message;
 }
 
+/**
+ * camel_folder_get_message_cached:
+ * @folder: a #CamelFolder
+ * @message_uid: the message UID
+ * @cancellable: optional #GCancellable object, or %NULL
+ *
+ * Gets the message corresponding to @message_uid from the @folder cache,
+ * if available locally. This should not do any network I/O, only check
+ * if message is already downloaded and return it quickly, not being
+ * blocked by the folder's lock. Returning NULL is not considered as
+ * an error, it just means that the message is still to-be-downloaded.
+ *
+ * Note: This function is called automatically within camel_folder_get_message_sync().
+ *
+ * Returns: (transfer full) (nullable): a cached #CamelMimeMessage corresponding
+ *    to the requested UID
+ *
+ * Since: 3.22.5
+ **/
+CamelMimeMessage *
+camel_folder_get_message_cached (CamelFolder *folder,
+                                const gchar *message_uid,
+                                GCancellable *cancellable)
+{
+       CamelFolderClass *class;
+
+       g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+       g_return_val_if_fail (message_uid != NULL, NULL);
+
+       class = CAMEL_FOLDER_GET_CLASS (folder);
+       if (!class->get_message_cached)
+               return NULL;
+
+       return class->get_message_cached (folder, message_uid, cancellable);
+}
+
 /* Helper for camel_folder_get_message() */
 static void
 folder_get_message_thread (GTask *task,
diff --git a/camel/camel-folder.h b/camel/camel-folder.h
index df2cdbb..fd49f38 100644
--- a/camel/camel-folder.h
+++ b/camel/camel-folder.h
@@ -437,6 +437,10 @@ CamelMimeMessage *
                camel_folder_get_message_finish (CamelFolder *folder,
                                                 GAsyncResult *result,
                                                 GError **error);
+CamelMimeMessage *
+               camel_folder_get_message_cached (CamelFolder *folder,
+                                                const gchar *message_uid,
+                                                GCancellable *cancellable);
 CamelFolderQuotaInfo *
                camel_folder_get_quota_info_sync
                                                (CamelFolder *folder,
diff --git a/camel/providers/imapx/camel-imapx-search.c b/camel/providers/imapx/camel-imapx-search.c
index a14ba6e..75769e9 100644
--- a/camel/providers/imapx/camel-imapx-search.c
+++ b/camel/providers/imapx/camel-imapx-search.c
@@ -350,6 +350,7 @@ imapx_search_body_contains (CamelSExp *sexp,
        CamelSExpResult *result;
        GString *criteria;
        GPtrArray *words;
+       gboolean is_gmail;
 
        /* Always do body-search server-side */
        if (imapx_search->priv->local_data_search) {
@@ -390,10 +391,73 @@ imapx_search_body_contains (CamelSExp *sexp,
 
        result = imapx_search_process_criteria (sexp, search, imapx_store, criteria, "BODY", words, 
G_STRFUNC);
 
+       is_gmail = camel_imapx_store_is_gmail_server (imapx_store);
+
        g_string_free (criteria, TRUE);
        g_ptr_array_free (words, TRUE);
        g_object_unref (imapx_store);
 
+       if (is_gmail && result && (result->type == CAMEL_SEXP_RES_ARRAY_PTR || (result->type == 
CAMEL_SEXP_RES_BOOL && !result->value.boolean))) {
+               /* Gmail returns BODY matches on whole words only, which is not it should be,
+                  thus try also locally cached messages to provide any better results. */
+               gboolean was_only_cached_messages;
+               CamelSExpResult *cached_result;
+
+               was_only_cached_messages = camel_folder_search_get_only_cached_messages (search);
+               camel_folder_search_set_only_cached_messages (search, TRUE);
+
+               cached_result = CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->body_contains 
(sexp, argc, argv, search);
+
+               camel_folder_search_set_only_cached_messages (search, was_only_cached_messages);
+
+               if (cached_result && cached_result->type == result->type) {
+                       if (result->type == CAMEL_SEXP_RES_BOOL) {
+                               result->value.boolean = cached_result->value.boolean;
+                       } else {
+                               /* Merge the two UID arrays */
+                               GHashTable *merge;
+                               GHashTableIter iter;
+                               GPtrArray *array;
+                               gpointer key;
+                               guint ii;
+
+                               /* UID-s are strings from the string pool, thus can be compared directly */
+                               merge = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+                               array = result->value.ptrarray;
+                               for (ii = 0; array && ii < array->len; ii++) {
+                                       gpointer uid = g_ptr_array_index (array, ii);
+
+                                       if (uid)
+                                               g_hash_table_insert (merge, uid, NULL);
+                               }
+
+                               array = cached_result->value.ptrarray;
+                               for (ii = 0; array && ii < array->len; ii++) {
+                                       gpointer uid = g_ptr_array_index (array, ii);
+
+                                       if (uid)
+                                               g_hash_table_insert (merge, uid, NULL);
+                               }
+
+                               array = g_ptr_array_new_full (g_hash_table_size (merge), (GDestroyNotify) 
camel_pstring_free);
+
+                               g_hash_table_iter_init (&iter, merge);
+                               while (g_hash_table_iter_next (&iter, &key, NULL)) {
+                                       g_ptr_array_add (array, (gpointer) camel_pstring_strdup (key));
+                               }
+
+                               g_hash_table_destroy (merge);
+
+                               g_ptr_array_unref (result->value.ptrarray);
+
+                               result->value.ptrarray = array;
+                       }
+               }
+
+               camel_sexp_result_free (sexp, cached_result);
+       }
+
        return result;
 }
 
diff --git a/camel/providers/imapx/camel-imapx-store.c b/camel/providers/imapx/camel-imapx-store.c
index cca9c25..f358816 100644
--- a/camel/providers/imapx/camel-imapx-store.c
+++ b/camel/providers/imapx/camel-imapx-store.c
@@ -2258,15 +2258,15 @@ exit:
        return success;
 }
 
-static gboolean
-imapx_is_gmail_server (CamelService *service)
+gboolean
+camel_imapx_store_is_gmail_server (CamelIMAPXStore *imapx_store)
 {
        CamelSettings *settings;
        gboolean is_gmail = FALSE;
 
-       g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+       g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store), FALSE);
 
-       settings = camel_service_ref_settings (service);
+       settings = camel_service_ref_settings (CAMEL_SERVICE (imapx_store));
        if (CAMEL_IS_NETWORK_SETTINGS (settings)) {
                gchar *host;
 
@@ -2536,7 +2536,7 @@ imapx_initial_setup_sync (CamelStore *store,
 
        /* Skip changing Sent folder for GMail, because GMail stores sent messages
           automatically, thus it would make doubled copies on the server. */
-       if (!imapx_is_gmail_server (CAMEL_SERVICE (store))) {
+       if (!camel_imapx_store_is_gmail_server (imapx_store)) {
                imapx_check_initial_setup_group (imapx_store, finfo, save_setup,
                        CAMEL_IMAPX_LIST_ATTR_SENT,
                        CAMEL_STORE_SETUP_SENT_FOLDER, NULL, NULL,
diff --git a/camel/providers/imapx/camel-imapx-store.h b/camel/providers/imapx/camel-imapx-store.h
index e25eccd..4eec36d 100644
--- a/camel/providers/imapx/camel-imapx-store.h
+++ b/camel/providers/imapx/camel-imapx-store.h
@@ -75,6 +75,8 @@ struct _CamelIMAPXStoreClass {
 };
 
 GType          camel_imapx_store_get_type      (void);
+gboolean       camel_imapx_store_is_gmail_server
+                                               (CamelIMAPXStore *store);
 CamelIMAPXConnManager *
                camel_imapx_store_get_conn_manager
                                                (CamelIMAPXStore *store);


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