diff -up evolution-data-server-3.8.5/camel/camel-imapx-search.c.imapx-server-side-search evolution-data-server-3.8.5/camel/camel-imapx-search.c --- evolution-data-server-3.8.5/camel/camel-imapx-search.c.imapx-server-side-search 2013-07-23 13:57:42.000000000 +0200 +++ evolution-data-server-3.8.5/camel/camel-imapx-search.c 2014-02-03 16:39:00.652093324 +0100 @@ -27,6 +27,7 @@ struct _CamelIMAPXSearchPrivate { GWeakRef server; + gint *local_data_search; /* not NULL, if testing whether all used headers are all locally available */ }; enum { @@ -88,32 +89,194 @@ imapx_search_dispose (GObject *object) } static CamelSExpResult * +imapx_search_result_match_all (CamelSExp *sexp, + CamelFolderSearch *search) +{ + CamelSExpResult *result; + + g_return_val_if_fail (search != NULL, NULL); + + if (search->current != NULL) { + result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + result->value.boolean = TRUE; + } else { + gint ii; + + result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + 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; +} + +static CamelSExpResult * +imapx_search_result_match_none (CamelSExp *sexp, + CamelFolderSearch *search) +{ + CamelSExpResult *result; + + g_return_val_if_fail (search != NULL, NULL); + + if (search->current != NULL) { + result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + result->value.boolean = FALSE; + } else { + result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + result->value.ptrarray = g_ptr_array_new (); + } + + return result; +} + +static CamelSExpResult * +imapx_search_process_criteria (CamelSExp *sexp, + CamelFolderSearch *search, + CamelIMAPXServer *server, + const GString *criteria, + const gchar *from_function) +{ + CamelSExpResult *result; + GPtrArray *uids = NULL; + GError *error = NULL; + + 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", + from_function, criteria->str, error->message); + uids = g_ptr_array_new (); + g_error_free (error); + } + + if (search->current != NULL) { + result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + result->value.boolean = (uids && uids->len > 0); + } else { + result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + result->value.ptrarray = g_ptr_array_ref (uids); + } + + g_ptr_array_unref (uids); + + return result; +} + +static CamelSExpResult * +imapx_search_match_all (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search) +{ + CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search); + CamelIMAPXServer *server; + CamelSExpResult *result; + GPtrArray *summary; + gint local_data_search = 0, *prev_local_data_search, ii; + + if (argc != 1) + return imapx_search_result_match_none (sexp, search); + + server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search)); + if (!server || search->current || !search->summary) { + g_clear_object (&server); + + /* Chain up to parent's method. */ + return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)-> + match_all (sexp, argc, argv, search); + } + + /* First try to see whether all used headers are available locally - if + they are, then do not use server-side filtering at all. */ + prev_local_data_search = imapx_search->priv->local_data_search; + imapx_search->priv->local_data_search = &local_data_search; + + summary = search->summary_set ? search->summary_set : search->summary; + + if (!CAMEL_IS_VEE_FOLDER (search->folder)) { + camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL); + } + + for (ii = 0; ii < summary->len; ii++) { + search->current = camel_folder_summary_get (search->folder->summary, summary->pdata[ii]); + if (search->current) { + result = camel_sexp_term_eval (sexp, argv[0]); + camel_sexp_result_free (sexp, result); + camel_message_info_free (search->current); + search->current = NULL; + break; + } + } + imapx_search->priv->local_data_search = prev_local_data_search; + + if (local_data_search >= 0) { + g_clear_object (&server); + + /* Chain up to parent's method. */ + return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)-> + match_all (sexp, argc, argv, search); + } + + /* let's change the requirements a bit, the parent class expects as a result boolean, + but here is expected GPtrArray of matched UIDs */ + result = camel_sexp_term_eval (sexp, argv[0]); + + g_object_unref (server); + + g_return_val_if_fail (result != NULL, result); + g_return_val_if_fail (result->type == CAMEL_SEXP_RES_ARRAY_PTR, result); + + return result; +} + +static CamelSExpResult * imapx_search_body_contains (CamelSExp *sexp, gint argc, CamelSExpResult **argv, CamelFolderSearch *search) { + CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search); CamelIMAPXServer *server; CamelSExpResult *result; - CamelSExpResultType type; GString *criteria; - GPtrArray *uids; gint ii, jj; - GError *error = NULL; + + /* Always do body-search server-side */ + if (imapx_search->priv->local_data_search) { + *imapx_search->priv->local_data_search = -1; + return imapx_search_result_match_none (sexp, search); + } /* Match everything if argv = [""] */ if (argc == 1 && argv[0]->value.string[0] == '\0') - goto match_all; + return imapx_search_result_match_all (sexp, search); /* Match nothing if empty argv or empty summary. */ if (argc == 0 || search->summary->len == 0) - goto match_none; + return imapx_search_result_match_none (sexp, search); 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; + if (server == NULL) { + /* Chain up to parent's method. */ + return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)-> + body_contains (sexp, argc, argv, search); + } /* Build the IMAP search criteria. */ @@ -157,78 +320,217 @@ imapx_search_body_contains (CamelSExp *s } } - uids = camel_imapx_server_uid_search ( - server, search->folder, criteria->str, NULL, &error); + result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC); - /* Sanity check. */ - g_return_val_if_fail ( - ((uids != NULL) && (error == NULL)) || - ((uids == NULL) && (error != NULL)), NULL); + g_string_free (criteria, TRUE); + g_object_unref (server); - /* 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); + return result; +} + +static gboolean +imapx_search_is_header_from_summary (const gchar *header_name) +{ + return g_ascii_strcasecmp (header_name, "From") == 0 || + g_ascii_strcasecmp (header_name, "To") == 0 || + g_ascii_strcasecmp (header_name, "CC") == 0 || + g_ascii_strcasecmp (header_name, "Subject") == 0; +} + +static CamelSExpResult * +imapx_search_header_contains (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) +{ + CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search); + CamelIMAPXServer *server; + CamelSExpResult *result; + const gchar *headername, *command = NULL; + GString *criteria; + gint ii, jj; + + /* Match nothing if empty argv or empty summary. */ + if (argc <= 1 || + argv[0]->type != CAMEL_SEXP_RES_STRING || + search->summary->len == 0) + return imapx_search_result_match_none (sexp, search); + + headername = argv[0]->value.string; + + if (imapx_search_is_header_from_summary (headername)) { + if (imapx_search->priv->local_data_search) { + if (*imapx_search->priv->local_data_search >= 0) + *imapx_search->priv->local_data_search = (*imapx_search->priv->local_data_search) + 1; + return imapx_search_result_match_all (sexp, search); + } + + /* Chain up to parent's method. */ + return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)-> + header_contains (sexp, argc, argv, search); + } else if (imapx_search->priv->local_data_search) { + *imapx_search->priv->local_data_search = -1; + return imapx_search_result_match_none (sexp, search); + } + + server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search)); + + /* This will be NULL if we're offline. Search from cache. */ + if (server == NULL) { + /* Chain up to parent's method. */ + return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)-> + header_contains (sexp, argc, argv, search); } + /* Build the IMAP search criteria. */ + + criteria = g_string_sized_new (128); + 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); + 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); } - g_ptr_array_unref (uids); + if (g_ascii_strcasecmp (headername, "From") == 0) + command = "FROM"; + else if (g_ascii_strcasecmp (headername, "To") == 0) + command = "TO"; + else if (g_ascii_strcasecmp (headername, "CC") == 0) + command = "CC"; + else if (g_ascii_strcasecmp (headername, "Bcc") == 0) + command = "BCC"; + else if (g_ascii_strcasecmp (headername, "Subject") == 0) + command = "SUBJECT"; - g_string_free (criteria, TRUE); + for (ii = 1; 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, ' '); + + if (command) + g_string_append (criteria, command); + else + g_string_append_printf (criteria, "HEADER \"%s\"", headername); + + g_string_append (criteria, " \""); + + 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, '"'); + } + } + + result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC); + 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 (); +static CamelSExpResult * +imapx_search_header_exists (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) +{ + CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search); + CamelIMAPXServer *server; + CamelSExpResult *result; + GString *criteria; + gint ii; - for (ii = 0; ii < search->summary->len; ii++) - g_ptr_array_add ( - result->value.ptrarray, - (gpointer) search->summary->pdata[ii]); + /* Match nothing if empty argv or empty summary. */ + if (argc == 0 || search->summary->len == 0) + return imapx_search_result_match_none (sexp, search); + + /* Check if asking for locally stored headers only */ + for (ii = 0; ii < argc; ii++) { + if (argv[ii]->type != CAMEL_SEXP_RES_STRING) + continue; + + if (!imapx_search_is_header_from_summary (argv[ii]->value.string)) + break; } - return result; + /* All headers are from summary */ + if (ii == argc) { + if (imapx_search->priv->local_data_search) { + if (*imapx_search->priv->local_data_search >= 0) + *imapx_search->priv->local_data_search = (*imapx_search->priv->local_data_search) + 1; + + return imapx_search_result_match_all (sexp, search); + } + + /* Chain up to parent's method. */ + return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)-> + header_exists (sexp, argc, argv, search); + } else if (imapx_search->priv->local_data_search) { + *imapx_search->priv->local_data_search = -1; + return imapx_search_result_match_none (sexp, search); + } + + server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search)); + + /* This will be NULL if we're offline. Search from cache. */ + if (server == NULL) { + /* Chain up to parent's method. */ + return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)-> + header_exists (sexp, argc, argv, search); + } + + /* Build the IMAP search criteria. */ + + criteria = g_string_sized_new (128); -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 (); + 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); } - return result; + for (ii = 0; ii < argc; ii++) { + const gchar *headername; + + if (argv[ii]->type != CAMEL_SEXP_RES_STRING) + continue; + + headername = argv[ii]->value.string; + + if (criteria->len > 0) + g_string_append_c (criteria, ' '); -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); + g_string_append_printf (criteria, "HEADER \"%s\" \"\"", headername); + } + + result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC); + + g_string_free (criteria, TRUE); + g_object_unref (server); + + return result; } static void @@ -245,7 +547,10 @@ camel_imapx_search_class_init (CamelIMAP object_class->dispose = imapx_search_dispose; search_class = CAMEL_FOLDER_SEARCH_CLASS (class); + search_class->match_all = imapx_search_match_all; search_class->body_contains = imapx_search_body_contains; + search_class->header_contains = imapx_search_header_contains; + search_class->header_exists = imapx_search_header_exists; g_object_class_install_property ( object_class, @@ -263,6 +568,7 @@ static void camel_imapx_search_init (CamelIMAPXSearch *search) { search->priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (search); + search->priv->local_data_search = NULL; } /**