[evolution-data-server] IMAPX: Ask server to handle "body-contains" searches.



commit 0eb3de7b71e36041a4e208433385d34c37bd7c07
Author: Matthew Barnes <mbarnes redhat com>
Date:   Sat Dec 22 09:22:40 2012 -0500

    IMAPX: Ask server to handle "body-contains" searches.
    
    This isn't as sophisticated/convoluted as the version in the older IMAP
    backend, but it still beats downloading the entire message as we've been
    doing.  There's no caching of past search results.  I'm not convinced the
    performance gain is worth the extra complexity.  If I'm wrong or I change
    my mind, search result caching can always be added later.

 camel/Makefile.am                       |   10 +-
 camel/camel-imapx-folder.c              |  111 ++++++++++-
 camel/camel-imapx-search.c              |  333 +++++++++++++++++++++++++++++++
 camel/camel-imapx-search.h              |   80 ++++++++
 camel/camel-imapx-server.c              |  218 ++++++++++++++++++++-
 camel/camel-imapx-server.h              |    5 +
 camel/camel-imapx-utils.h               |    1 +
 camel/camel.h                           |    1 +
 docs/reference/camel/camel-docs.sgml    |    1 +
 docs/reference/camel/camel-sections.txt |   21 ++
 docs/reference/camel/camel.types        |    1 +
 11 files changed, 767 insertions(+), 15 deletions(-)
---
diff --git a/camel/Makefile.am b/camel/Makefile.am
index 5815d70..db212b3 100644
--- a/camel/Makefile.am
+++ b/camel/Makefile.am
@@ -175,11 +175,12 @@ libcamel_1_2_la_SOURCES = 			\
 	camel-vee-summary.c			\
 	camel-vtrash-folder.c			\
 	camel-imapx-command.c			\
-	camel-imapx-job.c			\
-	camel-imapx-settings.c			\
-	camel-imapx-server.c			\
 	camel-imapx-conn-manager.c		\
 	camel-imapx-folder.c			\
+	camel-imapx-job.c			\
+	camel-imapx-search.c			\
+	camel-imapx-server.c			\
+	camel-imapx-settings.c			\
 	camel-imapx-store-summary.c		\
 	camel-imapx-store.c			\
 	camel-imapx-stream.c			\
@@ -312,8 +313,9 @@ libcamelinclude_HEADERS =			\
 	camel-imapx-conn-manager.h		\
 	camel-imapx-folder.h			\
 	camel-imapx-job.h			\
-	camel-imapx-settings.h			\
+	camel-imapx-search.h			\
 	camel-imapx-server.h			\
+	camel-imapx-settings.h			\
 	camel-imapx-store-summary.h		\
 	camel-imapx-store.h			\
 	camel-imapx-stream.h			\
diff --git a/camel/camel-imapx-folder.c b/camel/camel-imapx-folder.c
index a9e3a56..8a52bb8 100644
--- a/camel/camel-imapx-folder.c
+++ b/camel/camel-imapx-folder.c
@@ -27,11 +27,12 @@
 #include <errno.h>
 #include <glib/gi18n-lib.h>
 
-#include "camel-imapx-utils.h"
-#include "camel-imapx-store.h"
 #include "camel-imapx-folder.h"
-#include "camel-imapx-summary.h"
+#include "camel-imapx-search.h"
 #include "camel-imapx-server.h"
+#include "camel-imapx-store.h"
+#include "camel-imapx-summary.h"
+#include "camel-imapx-utils.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -300,19 +301,49 @@ imapx_search_by_uids (CamelFolder *folder,
                       GCancellable *cancellable,
                       GError **error)
 {
-	CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
+	CamelIMAPXFolder *ifolder;
+	CamelIMAPXSearch *isearch;
+	CamelIMAPXServer *server = NULL;
+	CamelStore *parent_store;
 	GPtrArray *matches;
+	const gchar *folder_name;
+	gboolean online;
 
 	if (uids->len == 0)
 		return g_ptr_array_new ();
 
+	ifolder = CAMEL_IMAPX_FOLDER (folder);
+	folder_name = camel_folder_get_full_name (folder);
+	parent_store = camel_folder_get_parent_store (folder);
+
+	online = camel_offline_store_get_online (
+		CAMEL_OFFLINE_STORE (parent_store));
+
+	if (online) {
+		server = camel_imapx_store_get_server (
+			CAMEL_IMAPX_STORE (parent_store),
+			folder_name, cancellable, error);
+		if (server == NULL)
+			return NULL;
+	}
+
 	g_mutex_lock (&ifolder->search_lock);
 
+	isearch = CAMEL_IMAPX_SEARCH (ifolder->search);
+	camel_imapx_search_set_server (isearch, server);
+
 	camel_folder_search_set_folder (ifolder->search, folder);
-	matches = camel_folder_search_search (ifolder->search, expression, uids, cancellable, error);
+
+	matches = camel_folder_search_search (
+		ifolder->search, expression, uids, cancellable, error);
+
+	camel_imapx_search_set_server (isearch, NULL);
 
 	g_mutex_unlock (&ifolder->search_lock);
 
+	if (server != NULL)
+		g_object_unref (server);
+
 	return matches;
 }
 
@@ -322,16 +353,46 @@ imapx_count_by_expression (CamelFolder *folder,
                            GCancellable *cancellable,
                            GError **error)
 {
-	CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
+	CamelIMAPXFolder *ifolder;
+	CamelIMAPXSearch *isearch;
+	CamelIMAPXServer *server = NULL;
+	CamelStore *parent_store;
+	const gchar *folder_name;
+	gboolean online;
 	guint32 matches;
 
+	ifolder = CAMEL_IMAPX_FOLDER (folder);
+	folder_name = camel_folder_get_full_name (folder);
+	parent_store = camel_folder_get_parent_store (folder);
+
+	online = camel_offline_store_get_online (
+		CAMEL_OFFLINE_STORE (parent_store));
+
+	if (online) {
+		server = camel_imapx_store_get_server (
+			CAMEL_IMAPX_STORE (parent_store),
+			folder_name, cancellable, error);
+		if (server == NULL)
+			return 0;
+	}
+
 	g_mutex_lock (&ifolder->search_lock);
 
+	isearch = CAMEL_IMAPX_SEARCH (ifolder->search);
+	camel_imapx_search_set_server (isearch, server);
+
 	camel_folder_search_set_folder (ifolder->search, folder);
-	matches = camel_folder_search_count (ifolder->search, expression, cancellable, error);
+
+	matches = camel_folder_search_count (
+		ifolder->search, expression, cancellable, error);
+
+	camel_imapx_search_set_server (isearch, NULL);
 
 	g_mutex_unlock (&ifolder->search_lock);
 
+	if (server != NULL)
+		g_object_unref (server);
+
 	return matches;
 }
 
@@ -341,16 +402,46 @@ imapx_search_by_expression (CamelFolder *folder,
                             GCancellable *cancellable,
                             GError **error)
 {
-	CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
+	CamelIMAPXFolder *ifolder;
+	CamelIMAPXSearch *isearch;
+	CamelIMAPXServer *server = NULL;
+	CamelStore *parent_store;
 	GPtrArray *matches;
+	const gchar *folder_name;
+	gboolean online;
+
+	ifolder = CAMEL_IMAPX_FOLDER (folder);
+	folder_name = camel_folder_get_full_name (folder);
+	parent_store = camel_folder_get_parent_store (folder);
+
+	online = camel_offline_store_get_online (
+		CAMEL_OFFLINE_STORE (parent_store));
+
+	if (online) {
+		server = camel_imapx_store_get_server (
+			CAMEL_IMAPX_STORE (parent_store),
+			folder_name, cancellable, error);
+		if (server == NULL)
+			return NULL;
+	}
 
 	g_mutex_lock (&ifolder->search_lock);
 
+	isearch = CAMEL_IMAPX_SEARCH (ifolder->search);
+	camel_imapx_search_set_server (isearch, server);
+
 	camel_folder_search_set_folder (ifolder->search, folder);
-	matches = camel_folder_search_search (ifolder->search, expression, NULL, cancellable, error);
+
+	matches = camel_folder_search_search (
+		ifolder->search, expression, NULL, cancellable, error);
+
+	camel_imapx_search_set_server (isearch, NULL);
 
 	g_mutex_unlock (&ifolder->search_lock);
 
+	if (server != NULL)
+		g_object_unref (server);
+
 	return matches;
 }
 
@@ -1144,7 +1235,7 @@ camel_imapx_folder_new (CamelStore *store,
 	g_free (state_file);
 	camel_object_state_read (CAMEL_OBJECT (folder));
 
-	ifolder->search = camel_folder_search_new ();
+	ifolder->search = camel_imapx_search_new ();
 	g_mutex_init (&ifolder->search_lock);
 	g_mutex_init (&ifolder->stream_lock);
 	ifolder->ignore_recent = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
diff --git a/camel/camel-imapx-search.c b/camel/camel-imapx-search.c
new file mode 100644
index 0000000..cae7792
--- /dev/null
+++ b/camel/camel-imapx-search.c
@@ -0,0 +1,333 @@
+/*
+ * camel-imapx-search.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "camel-imapx-search.h"
+
+#include "camel-offline-store.h"
+#include "camel-search-private.h"
+
+#define CAMEL_IMAPX_SEARCH_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearchPrivate))
+
+struct _CamelIMAPXSearchPrivate {
+	GWeakRef server;
+};
+
+enum {
+	PROP_0,
+	PROP_SERVER
+};
+
+G_DEFINE_TYPE (
+	CamelIMAPXSearch,
+	camel_imapx_search,
+	CAMEL_TYPE_FOLDER_SEARCH)
+
+static void
+imapx_search_set_property (GObject *object,
+                           guint property_id,
+                           const GValue *value,
+                           GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SERVER:
+			camel_imapx_search_set_server (
+				CAMEL_IMAPX_SEARCH (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_search_get_property (GObject *object,
+                           guint property_id,
+                           GValue *value,
+                           GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SERVER:
+			g_value_take_object (
+				value,
+				camel_imapx_search_ref_server (
+				CAMEL_IMAPX_SEARCH (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_search_dispose (GObject *object)
+{
+	CamelIMAPXSearchPrivate *priv;
+
+	priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (object);
+
+	g_weak_ref_set (&priv->server, NULL);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (camel_imapx_search_parent_class)->dispose (object);
+}
+
+static CamelSExpResult *
+imapx_search_body_contains (CamelSExp *sexp,
+                            gint argc,
+                            CamelSExpResult **argv,
+                            CamelFolderSearch *search)
+{
+	CamelIMAPXServer *server;
+	CamelSExpResult *result;
+	CamelSExpResultType type;
+	GString *criteria;
+	GPtrArray *uids;
+	gint ii, jj;
+	GError *error = NULL;
+
+	/* Match everything if argv = [""] */
+	if (argc == 1 && argv[0]->value.string[0] == '\0')
+		goto match_all;
+
+	/* Match nothing if empty argv or empty summary. */
+	if (argc == 0 || search->summary->len == 0)
+		goto match_none;
+
+	server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
+
+	/* This will be NULL if we're offline.  Search from cache. */
+	if (server == NULL)
+		goto chain_up;
+
+	/* Build the IMAP search criteria. */
+
+	criteria = g_string_sized_new (128);
+
+	if (search->current != NULL) {
+		const gchar *uid;
+
+		/* Limit the search to a single UID. */
+		uid = camel_message_info_uid (search->current);
+		g_string_append_printf (criteria, "UID %s", uid);
+	}
+
+	for (ii = 0; ii < argc; ii++) {
+		struct _camel_search_words *words;
+		const guchar *term;
+
+		if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
+			continue;
+
+		/* Handle multiple search words within a single term. */
+		term = (const guchar *) argv[ii]->value.string;
+		words = camel_search_words_split (term);
+
+		for (jj = 0; jj < words->len; jj++) {
+			gchar *cp;
+
+			if (criteria->len > 0)
+				g_string_append_c (criteria, ' ');
+
+			g_string_append (criteria, "BODY \"");
+
+			cp = words->words[jj]->word;
+			for (; *cp != '\0'; cp++) {
+				if (*cp == '\\' || *cp == '"')
+					g_string_append_c (criteria, '\\');
+				g_string_append_c (criteria, *cp);
+			}
+
+			g_string_append_c (criteria, '"');
+		}
+	}
+
+	uids = camel_imapx_server_uid_search (
+		server, search->folder, criteria->str, NULL, &error);
+
+	/* Sanity check. */
+	g_return_val_if_fail (
+		((uids != NULL) && (error == NULL)) ||
+		((uids == NULL) && (error != NULL)), NULL);
+
+	/* XXX No allowance for errors in CamelSExp callbacks!
+	 *     Dump the error to the console and make like we
+	 *     got an empty result. */
+	if (error != NULL) {
+		g_warning (
+			"%s: (UID SEARCH %s): %s",
+			G_STRFUNC, criteria->str, error->message);
+		uids = g_ptr_array_new ();
+		g_error_free (error);
+	}
+
+	if (search->current != NULL) {
+		type = CAMEL_SEXP_RES_BOOL;
+		result = camel_sexp_result_new (sexp, type);
+		result->value.boolean = (uids->len > 0);
+	} else {
+		type = CAMEL_SEXP_RES_ARRAY_PTR;
+		result = camel_sexp_result_new (sexp, type);
+		result->value.ptrarray = g_ptr_array_ref (uids);
+	}
+
+	g_ptr_array_unref (uids);
+
+	g_string_free (criteria, TRUE);
+
+	g_object_unref (server);
+
+	return result;
+
+match_all:
+	if (search->current != NULL) {
+		type = CAMEL_SEXP_RES_BOOL;
+		result = camel_sexp_result_new (sexp, type);
+		result->value.boolean = TRUE;
+	} else {
+		type = CAMEL_SEXP_RES_ARRAY_PTR;
+		result = camel_sexp_result_new (sexp, type);
+		result->value.ptrarray = g_ptr_array_new ();
+
+		for (ii = 0; ii < search->summary->len; ii++)
+			g_ptr_array_add (
+				result->value.ptrarray,
+				(gpointer) search->summary->pdata[ii]);
+	}
+
+	return result;
+
+match_none:
+	if (search->current != NULL) {
+		type = CAMEL_SEXP_RES_BOOL;
+		result = camel_sexp_result_new (sexp, type);
+		result->value.boolean = FALSE;
+	} else {
+		type = CAMEL_SEXP_RES_ARRAY_PTR;
+		result = camel_sexp_result_new (sexp, type);
+		result->value.ptrarray = g_ptr_array_new ();
+	}
+
+	return result;
+
+chain_up:
+	/* Chain up to parent's body_contains() method. */
+	return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+		body_contains (sexp, argc, argv, search);
+}
+
+static void
+camel_imapx_search_class_init (CamelIMAPXSearchClass *class)
+{
+	GObjectClass *object_class;
+	CamelFolderSearchClass *search_class;
+
+	g_type_class_add_private (class, sizeof (CamelIMAPXSearchPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = imapx_search_set_property;
+	object_class->get_property = imapx_search_get_property;
+	object_class->dispose = imapx_search_dispose;
+
+	search_class = CAMEL_FOLDER_SEARCH_CLASS (class);
+	search_class->body_contains = imapx_search_body_contains;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SERVER,
+		g_param_spec_object (
+			"server",
+			"Server",
+			"Server proxy for server-side searches",
+			CAMEL_TYPE_IMAPX_SERVER,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_imapx_search_init (CamelIMAPXSearch *search)
+{
+	search->priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (search);
+}
+
+/**
+ * camel_imapx_search_new:
+ *
+ * Returns a new #CamelIMAPXSearch instance.
+ *
+ * The #CamelIMAPXSearch must be given a #CamelIMAPXSearch:server before
+ * it can issue server-side search requests.  Otherwise it will fallback
+ * to the default #CamelFolderSearch behavior.
+ *
+ * Returns: a new #CamelIMAPXSearch
+ *
+ * Since: 3.8
+ **/
+CamelFolderSearch *
+camel_imapx_search_new (void)
+{
+	return g_object_new (CAMEL_TYPE_IMAPX_SEARCH, NULL);
+}
+
+/**
+ * camel_imapx_search_ref_server:
+ * @search: a #CamelIMAPXSearch
+ *
+ * Returns a #CamelIMAPXServer to use for server-side searches,
+ * or %NULL when the corresponding #CamelIMAPXStore is offline.
+ *
+ * The returned #CamelIMAPXSearch is referenced for thread-safety and
+ * must be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXServer, or %NULL
+ *
+ * Since: 3.8
+ **/
+CamelIMAPXServer *
+camel_imapx_search_ref_server (CamelIMAPXSearch *search)
+{
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SEARCH (search), NULL);
+
+	return g_weak_ref_get (&search->priv->server);
+}
+
+/**
+ * camel_imapx_search_set_server:
+ * @search: a #CamelIMAPXSearch
+ * @server: a #CamelIMAPXServer, or %NULL
+ *
+ * Sets a #CamelIMAPXServer to use for server-side searches.  Generally
+ * this is set for the duration of a single search when online, and then
+ * reset to %NULL.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_search_set_server (CamelIMAPXSearch *search,
+                               CamelIMAPXServer *server)
+{
+	g_return_if_fail (CAMEL_IS_IMAPX_SEARCH (search));
+
+	if (server != NULL)
+		g_return_if_fail (CAMEL_IS_IMAPX_SERVER (server));
+
+	g_weak_ref_set (&search->priv->server, server);
+
+	g_object_notify (G_OBJECT (search), "server");
+}
+
diff --git a/camel/camel-imapx-search.h b/camel/camel-imapx-search.h
new file mode 100644
index 0000000..6e10b96
--- /dev/null
+++ b/camel/camel-imapx-search.h
@@ -0,0 +1,80 @@
+/*
+ * camel-imapx-search.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_IMAPX_SEARCH_H
+#define CAMEL_IMAPX_SEARCH_H
+
+#include <camel/camel-folder-search.h>
+#include <camel/camel-imapx-server.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_SEARCH \
+	(camel_imapx_search_get_type ())
+#define CAMEL_IMAPX_SEARCH(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearch))
+#define CAMEL_IMAPX_SEARCH_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearchClass))
+#define CAMEL_IS_IMAPX_SEARCH(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), CAMEL_TYPE_IMAPX_SEARCH))
+#define CAMEL_IS_IMAPX_SEARCH_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), CAMEL_TYPE_IMAPX_SEARCH))
+#define CAMEL_IMAPX_SEARCH_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearchClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXSearch CamelIMAPXSearch;
+typedef struct _CamelIMAPXSearchClass CamelIMAPXSearchClass;
+typedef struct _CamelIMAPXSearchPrivate CamelIMAPXSearchPrivate;
+
+/**
+ * CamelIMAPXSearch:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.8
+ **/
+struct _CamelIMAPXSearch {
+	CamelFolderSearch parent;
+	CamelIMAPXSearchPrivate *priv;
+};
+
+struct _CamelIMAPXSearchClass {
+	CamelFolderSearchClass parent_class;
+};
+
+GType		camel_imapx_search_get_type	(void) G_GNUC_CONST;
+CamelFolderSearch *
+		camel_imapx_search_new		(void);
+CamelIMAPXServer *
+		camel_imapx_search_ref_server	(CamelIMAPXSearch *search);
+void		camel_imapx_search_set_server	(CamelIMAPXSearch *search,
+						 CamelIMAPXServer *server);
+
+#endif /* CAMEL_IMAPX_SEARCH_H */
+
diff --git a/camel/camel-imapx-server.c b/camel/camel-imapx-server.c
index 9919714..76f6fdc 100644
--- a/camel/camel-imapx-server.c
+++ b/camel/camel-imapx-server.c
@@ -87,6 +87,7 @@ typedef struct _RenameFolderData RenameFolderData;
 typedef struct _CreateFolderData CreateFolderData;
 typedef struct _DeleteFolderData DeleteFolderData;
 typedef struct _QuotaData QuotaData;
+typedef struct _SearchData SearchData;
 
 struct _GetMessageData {
 	/* in: uid requested */
@@ -171,6 +172,11 @@ struct _DeleteFolderData {
 	gchar *folder_name;
 };
 
+struct _SearchData {
+	gchar *criteria;
+	GArray *results;
+};
+
 struct _QuotaData {
 	gchar *folder_name;
 };
@@ -261,6 +267,10 @@ static gboolean	imapx_untagged_recent		(CamelIMAPXServer *is,
 						 CamelIMAPXStream *stream,
 						 GCancellable *cancellable,
 						 GError **error);
+static gboolean	imapx_untagged_search		(CamelIMAPXServer *is,
+						 CamelIMAPXStream *stream,
+						 GCancellable *cancellable,
+						 GError **error);
 static gboolean	imapx_untagged_status		(CamelIMAPXServer *is,
 						 CamelIMAPXStream *stream,
 						 GCancellable *cancellable,
@@ -287,6 +297,7 @@ enum {
 	IMAPX_UNTAGGED_ID_QUOTA,
 	IMAPX_UNTAGGED_ID_QUOTAROOT,
 	IMAPX_UNTAGGED_ID_RECENT,
+	IMAPX_UNTAGGED_ID_SEARCH,
 	IMAPX_UNTAGGED_ID_STATUS,
 	IMAPX_UNTAGGED_ID_VANISHED,
 	IMAPX_UNTAGGED_LAST_ID
@@ -309,6 +320,7 @@ static const CamelIMAPXUntaggedRespHandlerDesc _untagged_descr[] = {
 	{CAMEL_IMAPX_UNTAGGED_QUOTA, imapx_untagged_quota, NULL, FALSE},
 	{CAMEL_IMAPX_UNTAGGED_QUOTAROOT, imapx_untagged_quotaroot, NULL, FALSE},
 	{CAMEL_IMAPX_UNTAGGED_RECENT, imapx_untagged_recent, NULL, TRUE},
+	{CAMEL_IMAPX_UNTAGGED_SEARCH, imapx_untagged_search, NULL, FALSE},
 	{CAMEL_IMAPX_UNTAGGED_STATUS, imapx_untagged_status, NULL, TRUE},
 	{CAMEL_IMAPX_UNTAGGED_VANISHED, imapx_untagged_vanished, NULL, TRUE},
 };
@@ -321,6 +333,12 @@ struct _CamelIMAPXServerPrivate {
 
 	CamelIMAPXStream *stream;
 	GMutex stream_lock;
+
+	/* Untagged SEARCH data gets deposited here.
+	 * The search command should claim the results
+	 * when finished and reset the pointer to NULL. */
+	GArray *search_results;
+	GMutex search_results_lock;
 };
 
 enum {
@@ -394,7 +412,8 @@ enum {
 	IMAPX_JOB_DELETE_FOLDER = 1 << 12,
 	IMAPX_JOB_RENAME_FOLDER = 1 << 13,
 	IMAPX_JOB_FETCH_MESSAGES = 1 << 14,
-	IMAPX_JOB_UPDATE_QUOTA_INFO = 1 << 15
+	IMAPX_JOB_UPDATE_QUOTA_INFO = 1 << 15,
+	IMAPX_JOB_UID_SEARCH = 1 << 16
 };
 
 /* Operations on the store (folder_tree) will have highest priority as we know for sure they are sync
@@ -406,6 +425,7 @@ enum {
 	IMAPX_PRIORITY_MANAGE_SUBSCRIPTION = 200,
 	IMAPX_PRIORITY_SYNC_CHANGES = 150,
 	IMAPX_PRIORITY_EXPUNGE = 150,
+	IMAPX_PRIORITY_SEARCH = 150,
 	IMAPX_PRIORITY_GET_MESSAGE = 100,
 	IMAPX_PRIORITY_REFRESH_INFO = 0,
 	IMAPX_PRIORITY_NOOP = 0,
@@ -696,6 +716,17 @@ delete_folder_data_free (DeleteFolderData *data)
 }
 
 static void
+search_data_free (SearchData *data)
+{
+	g_free (data->criteria);
+
+	if (data->results != NULL)
+		g_array_unref (data->results);
+
+	g_slice_free (SearchData, data);
+}
+
+static void
 quota_data_free (QuotaData *data)
 {
 	g_free (data->folder_name);
@@ -2082,6 +2113,63 @@ imapx_untagged_recent (CamelIMAPXServer *is,
 }
 
 static gboolean
+imapx_untagged_search (CamelIMAPXServer *is,
+                       CamelIMAPXStream *stream,
+                       GCancellable *cancellable,
+                       GError **error)
+{
+	GArray *search_results;
+	gint tok;
+	guint len;
+	guchar *token;
+	guint64 number;
+	gboolean success = FALSE;
+	GError *local_error = NULL;
+
+	search_results = g_array_new (FALSE, FALSE, sizeof (guint64));
+
+	while (TRUE) {
+		/* Peek at the next token, and break
+		 * out of the loop if we get a newline. */
+		tok = camel_imapx_stream_token (
+			stream, &token, &len, cancellable, error);
+		if (tok == '\n')
+			break;
+		if (tok == IMAPX_TOK_ERROR || tok == IMAPX_TOK_PROTOCOL)
+			goto exit;
+		camel_imapx_stream_ungettoken (stream, tok, token, len);
+
+		/* XXX camel_imapx_stream_number() should return the
+		 *     number as an out parameter, so we can more easily
+		 *     distinguish between a real '0' and an error. */
+		number = camel_imapx_stream_number (
+			stream, cancellable, &local_error);
+		if (local_error == NULL) {
+			g_array_append_val (search_results, number);
+		} else {
+			g_propagate_error (error, local_error);
+			goto exit;
+		}
+	}
+
+	g_mutex_lock (&is->priv->search_results_lock);
+
+	if (is->priv->search_results == NULL)
+		is->priv->search_results = g_array_ref (search_results);
+	else
+		g_warning ("%s: Conflicting search results", G_STRFUNC);
+
+	g_mutex_unlock (&is->priv->search_results_lock);
+
+	success = TRUE;
+
+exit:
+	g_array_unref (search_results);
+
+	return success;
+}
+
+static gboolean
 imapx_untagged_status (CamelIMAPXServer *is,
                        CamelIMAPXStream *stream,
                        GCancellable *cancellable,
@@ -6154,6 +6242,71 @@ imapx_job_update_quota_info_start (CamelIMAPXJob *job,
 /* ********************************************************************** */
 
 static gboolean
+imapx_command_uid_search_done (CamelIMAPXServer *is,
+                               CamelIMAPXCommand *ic,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+	CamelIMAPXJob *job;
+	SearchData *data;
+	gboolean success = TRUE;
+
+	job = camel_imapx_command_get_job (ic);
+	g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
+
+	data = camel_imapx_job_get_data (job);
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	if (camel_imapx_command_set_error_if_failed (ic, error)) {
+		g_prefix_error (error, "%s: ", _("Search failed"));
+		success = FALSE;
+	}
+
+	/* Don't worry about the success state and presence of search
+	 * results not agreeing here.  camel_imapx_server_uid_search()
+	 * will disregard the search results if an error occurred. */
+	g_mutex_lock (&is->priv->search_results_lock);
+	data->results = is->priv->search_results;
+	is->priv->search_results = NULL;
+	g_mutex_unlock (&is->priv->search_results_lock);
+
+	imapx_unregister_job (is, job);
+	camel_imapx_command_unref (ic);
+
+	return success;
+}
+
+static gboolean
+imapx_job_uid_search_start (CamelIMAPXJob *job,
+                            CamelIMAPXServer *is,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+	CamelFolder *folder;
+	CamelIMAPXCommand *ic;
+	SearchData *data;
+
+	data = camel_imapx_job_get_data (job);
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	folder = camel_imapx_job_ref_folder (job);
+	g_return_val_if_fail (folder != NULL, FALSE);
+
+	ic = camel_imapx_command_new (
+		is, "UID SEARCH", folder,
+		"UID SEARCH %t", data->criteria);
+	ic->pri = job->pri;
+	camel_imapx_command_set_job (ic, job);
+	ic->complete = imapx_command_uid_search_done;
+
+	g_object_unref (folder);
+
+	return imapx_command_queue (is, ic, cancellable, error);
+}
+
+/* ********************************************************************** */
+
+static gboolean
 imapx_command_noop_done (CamelIMAPXServer *is,
                          CamelIMAPXCommand *ic,
                          GCancellable *cancellable,
@@ -6791,6 +6944,10 @@ imapx_server_finalize (GObject *object)
 	g_free (is->priv->context);
 	g_hash_table_destroy (is->priv->untagged_handlers);
 
+	if (is->priv->search_results != NULL)
+		g_array_unref (is->priv->search_results);
+	g_mutex_clear (&is->priv->search_results_lock);
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (camel_imapx_server_parent_class)->finalize (object);
 }
@@ -6887,6 +7044,7 @@ camel_imapx_server_init (CamelIMAPXServer *is)
 	is->priv->untagged_handlers = create_initial_untagged_handler_table ();
 
 	g_mutex_init (&is->priv->stream_lock);
+	g_mutex_init (&is->priv->search_results_lock);
 
 	is->queue = camel_imapx_command_queue_new ();
 	is->active = camel_imapx_command_queue_new ();
@@ -8019,6 +8177,64 @@ camel_imapx_server_update_quota_info (CamelIMAPXServer *is,
 	return success;
 }
 
+GPtrArray *
+camel_imapx_server_uid_search (CamelIMAPXServer *is,
+                               CamelFolder *folder,
+                               const gchar *criteria,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+	CamelIMAPXJob *job;
+	SearchData *data;
+	GPtrArray *results = NULL;
+
+	g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+	g_return_val_if_fail (criteria != NULL, NULL);
+
+	data = g_slice_new0 (SearchData);
+	data->criteria = g_strdup (criteria);
+
+	job = camel_imapx_job_new (cancellable);
+	job->type = IMAPX_JOB_UID_SEARCH;
+	job->start = imapx_job_uid_search_start;
+	job->pri = IMAPX_PRIORITY_SEARCH;
+
+	camel_imapx_job_set_folder (job, folder);
+
+	camel_imapx_job_set_data (
+		job, data, (GDestroyNotify) search_data_free);
+
+	if (imapx_submit_job (is, job, error)) {
+		guint ii;
+
+		/* Convert the numeric UIDs to strings. */
+
+		g_return_val_if_fail (data->results != NULL, NULL);
+
+		results = g_ptr_array_new_full (
+			data->results->len,
+			(GDestroyNotify) camel_pstring_free);
+
+		for (ii = 0; ii < data->results->len; ii++) {
+			const gchar *pooled_uid;
+			guint64 numeric_uid;
+			gchar *alloced_uid;
+
+			numeric_uid = g_array_index (
+				data->results, guint64, ii);
+			alloced_uid = g_strdup_printf (
+				"%" G_GUINT64_FORMAT, numeric_uid);
+			pooled_uid = camel_pstring_add (alloced_uid, TRUE);
+			g_ptr_array_add (results, (gpointer) pooled_uid);
+		}
+	}
+
+	camel_imapx_job_unref (job);
+
+	return results;
+}
+
 IMAPXJobQueueInfo *
 camel_imapx_server_get_job_queue_info (CamelIMAPXServer *is)
 {
diff --git a/camel/camel-imapx-server.h b/camel/camel-imapx-server.h
index 15cdfce..fbabbbb 100644
--- a/camel/camel-imapx-server.h
+++ b/camel/camel-imapx-server.h
@@ -277,6 +277,11 @@ gboolean	camel_imapx_server_update_quota_info
 						 const gchar *folder_name,
 						 GCancellable *cancellable,
 						 GError **error);
+GPtrArray *	camel_imapx_server_uid_search	(CamelIMAPXServer *is,
+						 CamelFolder *folder,
+						 const gchar *criteria,
+						 GCancellable *cancellable,
+						 GError **error);
 struct _IMAPXJobQueueInfo *
 		camel_imapx_server_get_job_queue_info
 						(CamelIMAPXServer *is);
diff --git a/camel/camel-imapx-utils.h b/camel/camel-imapx-utils.h
index ed08313..80fcfb6 100644
--- a/camel/camel-imapx-utils.h
+++ b/camel/camel-imapx-utils.h
@@ -93,6 +93,7 @@ typedef enum _camel_imapx_id_t {
 #define CAMEL_IMAPX_UNTAGGED_QUOTA      "QUOTA"
 #define CAMEL_IMAPX_UNTAGGED_QUOTAROOT  "QUOTAROOT"
 #define CAMEL_IMAPX_UNTAGGED_RECENT     "RECENT"
+#define CAMEL_IMAPX_UNTAGGED_SEARCH     "SEARCH"
 #define CAMEL_IMAPX_UNTAGGED_STATUS     "STATUS"
 #define CAMEL_IMAPX_UNTAGGED_VANISHED   "VANISHED"
 
diff --git a/camel/camel.h b/camel/camel.h
index ceec583..63eeaf9 100644
--- a/camel/camel.h
+++ b/camel/camel.h
@@ -146,6 +146,7 @@
 #include <camel/camel-imapx-job.h>
 #include <camel/camel-imapx-conn-manager.h>
 #include <camel/camel-imapx-folder.h>
+#include <camel/camel-imapx-search.h>
 #include <camel/camel-imapx-server.h>
 #include <camel/camel-imapx-store-summary.h>
 #include <camel/camel-imapx-store.h>
diff --git a/docs/reference/camel/camel-docs.sgml b/docs/reference/camel/camel-docs.sgml
index 65bc07a..9065004 100644
--- a/docs/reference/camel/camel-docs.sgml
+++ b/docs/reference/camel/camel-docs.sgml
@@ -205,6 +205,7 @@
       <xi:include href="xml/camel-imapx-conn-manager.xml"/>
       <xi:include href="xml/camel-imapx-folder.xml"/>
       <xi:include href="xml/camel-imapx-job.xml"/>
+      <xi:include href="xml/camel-imapx-search.xml"/>
       <xi:include href="xml/camel-imapx-server.xml"/>
       <xi:include href="xml/camel-imapx-settings.xml"/>
       <xi:include href="xml/camel-imapx-store.xml"/>
diff --git a/docs/reference/camel/camel-sections.txt b/docs/reference/camel/camel-sections.txt
index 26083c9..96b70d1 100644
--- a/docs/reference/camel/camel-sections.txt
+++ b/docs/reference/camel/camel-sections.txt
@@ -838,6 +838,26 @@ uidset_state
 </SECTION>
 
 <SECTION>
+<FILE>camel-imapx-search</FILE>
+<TITLE>CamelIMAPXSearch</TITLE>
+CamelIMAPXSearch
+camel_imapx_search_new
+camel_imapx_search_ref_server
+camel_imapx_search_set_server
+<SUBSECTION Standard>
+CAMEL_IMAPX_SEARCH
+CAMEL_IS_IMAPX_SEARCH
+CAMEL_TYPE_IMAPX_SEARCH
+CAMEL_IMAPX_SEARCH_CLASS
+CAMEL_IS_IMAPX_SEARCH_CLASS
+CAMEL_IMAPX_SEARCH_GET_CLASS
+CamelIMAPXSearchClass
+camel_imapx_search_get_type
+<SUBSECTION Private>
+CamelIMAPXSearchPrivate
+</SECTION>
+
+<SECTION>
 <FILE>camel-imapx-server</FILE>
 <TITLE>CamelIMAPXServer</TITLE>
 CamelIMAPXServer
@@ -862,6 +882,7 @@ camel_imapx_server_create_folder
 camel_imapx_server_delete_folder
 camel_imapx_server_rename_folder
 camel_imapx_server_update_quota_info
+camel_imapx_server_uid_search
 camel_imapx_server_get_job_queue_info
 CamelIMAPXUntaggedRespHandlerDesc
 camel_imapx_server_register_untagged_handler
diff --git a/docs/reference/camel/camel.types b/docs/reference/camel/camel.types
index b732a7b..3865ec1 100644
--- a/docs/reference/camel/camel.types
+++ b/docs/reference/camel/camel.types
@@ -18,6 +18,7 @@ camel_gpg_context_get_type
 camel_html_parser_get_type
 camel_imapx_conn_manager_get_type
 camel_imapx_folder_get_type
+camel_imapx_search_get_type
 camel_imapx_server_get_type
 camel_imapx_settings_get_type
 camel_imapx_store_get_type



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