From 29ac8e19743d830edffa268916f7e0db02cba36f Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Wed, 19 Dec 2012 13:27:11 -0500 Subject: [PATCH] CamelFolderSearch cleanups. --- camel/camel-folder-search.c | 2593 +++++++++++++++++++++---------------------- camel/camel-folder-search.h | 283 +++-- 2 files changed, 1486 insertions(+), 1390 deletions(-) diff --git a/camel/camel-folder-search.c b/camel/camel-folder-search.c index 6ef7f98..2834897 100644 --- a/camel/camel-folder-search.c +++ b/camel/camel-folder-search.c @@ -68,665 +68,467 @@ struct _CamelFolderSearchPrivate { GHashTable *threads_hash; }; -static CamelSExpResult *search_not (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); - -static CamelSExpResult *search_header_contains (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_header_matches (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_header_starts_with (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_header_ends_with (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_header_exists (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_header_soundex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_header_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_header_full_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_match_all (struct _CamelSExp *f, gint argc, struct _CamelSExpTerm **argv, CamelFolderSearch *search); -static CamelSExpResult *search_match_threads (struct _CamelSExp *f, gint argc, struct _CamelSExpTerm **argv, CamelFolderSearch *s); -static CamelSExpResult *search_body_contains (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_body_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); -static CamelSExpResult *search_user_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_user_tag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_system_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_get_sent_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_get_received_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_get_current_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_get_relative_months (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_get_size (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_uid (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); -static CamelSExpResult *search_message_location (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *s); - -static CamelSExpResult *search_dummy (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFolderSearch *search); - -static gint read_uid_callback (gpointer ref, gint ncol, gchar ** cols, gchar **name); - G_DEFINE_TYPE (CamelFolderSearch, camel_folder_search, CAMEL_TYPE_OBJECT) +/* this is just to OR results together */ +struct IterData { + gint count; + GPtrArray *uids; +}; + +/* or, store all unique values */ static void -folder_search_dispose (GObject *object) +htor (gchar *key, + gint value, + struct IterData *iter_data) { - CamelFolderSearch *search = CAMEL_FOLDER_SEARCH (object); - - if (search->sexp != NULL) { - g_object_unref (search->sexp); - search->sexp = NULL; - } - - /* Chain up to parent's dispose() method. */ - G_OBJECT_CLASS (camel_folder_search_parent_class)->dispose (object); + g_ptr_array_add (iter_data->uids, key); } +/* and, only store duplicates */ static void -folder_search_finalize (GObject *object) +htand (gchar *key, + gint value, + struct IterData *iter_data) { - CamelFolderSearch *search = CAMEL_FOLDER_SEARCH (object); - - g_free (search->last_search); - - /* Chain up to parent's finalize() method. */ - G_OBJECT_CLASS (camel_folder_search_parent_class)->finalize (object); + if (value == iter_data->count) + g_ptr_array_add (iter_data->uids, key); } static void -camel_folder_search_class_init (CamelFolderSearchClass *class) +add_thread_results (CamelFolderThreadNode *root, + GHashTable *result_hash) { - GObjectClass *object_class; - - g_type_class_add_private (class, sizeof (CamelFolderSearchPrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->dispose = folder_search_dispose; - object_class->finalize = folder_search_finalize; - - class->not_ = search_not; - class->match_all = search_match_all; - class->match_threads = search_match_threads; - class->body_contains = search_body_contains; - class->body_regex = search_body_regex; - class->header_contains = search_header_contains; - class->header_matches = search_header_matches; - class->header_starts_with = search_header_starts_with; - class->header_ends_with = search_header_ends_with; - class->header_exists = search_header_exists; - class->header_soundex = search_header_soundex; - class->header_regex = search_header_regex; - class->header_full_regex = search_header_full_regex; - class->user_tag = search_user_tag; - class->user_flag = search_user_flag; - class->system_flag = search_system_flag; - class->get_sent_date = search_get_sent_date; - class->get_received_date = search_get_received_date; - class->get_current_date = search_get_current_date; - class->get_relative_months = search_get_relative_months; - class->get_size = search_get_size; - class->uid = search_uid; - class->message_location = search_message_location; + while (root) { + g_hash_table_insert (result_hash, (gchar *) camel_message_info_uid (root->message), GINT_TO_POINTER (1)); + if (root->child) + add_thread_results (root->child, result_hash); + root = root->next; + } } static void -camel_folder_search_init (CamelFolderSearch *search) +add_results (gchar *uid, + gpointer dummy, + GPtrArray *result) { - search->priv = CAMEL_FOLDER_SEARCH_GET_PRIVATE (search); - search->sexp = camel_sexp_new (); + g_ptr_array_add (result, uid); } -static struct { - const gchar *name; - gint offset; - gint flags; /* 0x02 = immediate, 0x01 = always enter */ -} builtins[] = { - /* these have default implementations in e-sexp */ - { "and", G_STRUCT_OFFSET (CamelFolderSearchClass, and_), 2 }, - { "or", G_STRUCT_OFFSET (CamelFolderSearchClass, or_), 2 }, - /* we need to override this one though to implement an 'array not' */ - { "not", G_STRUCT_OFFSET (CamelFolderSearchClass, not_), 0 }, - { "<", G_STRUCT_OFFSET (CamelFolderSearchClass, lt), 2 }, - { ">", G_STRUCT_OFFSET (CamelFolderSearchClass, gt), 2 }, - { "=", G_STRUCT_OFFSET (CamelFolderSearchClass, eq), 2 }, - - /* these we have to use our own default if there is none */ - /* they should all be defined in the language? so it parses, or should they not?? */ - { "match-all", G_STRUCT_OFFSET (CamelFolderSearchClass, match_all), 3 }, - { "match-threads", G_STRUCT_OFFSET (CamelFolderSearchClass, match_threads), 3 }, - { "body-contains", G_STRUCT_OFFSET (CamelFolderSearchClass, body_contains), 1 }, - { "body-regex", G_STRUCT_OFFSET (CamelFolderSearchClass, body_regex), 1 }, - { "header-contains", G_STRUCT_OFFSET (CamelFolderSearchClass, header_contains), 1 }, - { "header-matches", G_STRUCT_OFFSET (CamelFolderSearchClass, header_matches), 1 }, - { "header-starts-with", G_STRUCT_OFFSET (CamelFolderSearchClass, header_starts_with), 1 }, - { "header-ends-with", G_STRUCT_OFFSET (CamelFolderSearchClass, header_ends_with), 1 }, - { "header-exists", G_STRUCT_OFFSET (CamelFolderSearchClass, header_exists), 1 }, - { "header-soundex", G_STRUCT_OFFSET (CamelFolderSearchClass, header_soundex), 1 }, - { "header-regex", G_STRUCT_OFFSET (CamelFolderSearchClass, header_regex), 1 }, - { "header-full-regex", G_STRUCT_OFFSET (CamelFolderSearchClass, header_full_regex), 1 }, - { "user-tag", G_STRUCT_OFFSET (CamelFolderSearchClass, user_tag), 1 }, - { "user-flag", G_STRUCT_OFFSET (CamelFolderSearchClass, user_flag), 1 }, - { "system-flag", G_STRUCT_OFFSET (CamelFolderSearchClass, system_flag), 1 }, - { "get-sent-date", G_STRUCT_OFFSET (CamelFolderSearchClass, get_sent_date), 1 }, - { "get-received-date", G_STRUCT_OFFSET (CamelFolderSearchClass, get_received_date), 1 }, - { "get-current-date", G_STRUCT_OFFSET (CamelFolderSearchClass, get_current_date), 1 }, - { "get-relative-months", G_STRUCT_OFFSET (CamelFolderSearchClass, get_relative_months), 1 }, - { "get-size", G_STRUCT_OFFSET (CamelFolderSearchClass, get_size), 1 }, - { "uid", G_STRUCT_OFFSET (CamelFolderSearchClass, uid), 1 }, - { "message-location", G_STRUCT_OFFSET (CamelFolderSearchClass, message_location), 1 }, -}; - -void -camel_folder_search_construct (CamelFolderSearch *search) +static void +fill_thread_table (CamelFolderThreadNode *root, + GHashTable *id_hash) { - gint i; - CamelFolderSearchClass *class; - - class = CAMEL_FOLDER_SEARCH_GET_CLASS (search); - - for (i = 0; i < G_N_ELEMENTS (builtins); i++) { - gpointer func; - /* c is sure messy sometimes */ - func = *((gpointer *)(((gchar *) class) + builtins[i].offset)); - if (func == NULL && builtins[i].flags&1) { - g_warning ("Search class doesn't implement '%s' method: %s", builtins[i].name, G_OBJECT_TYPE_NAME (search)); - func = (gpointer) search_dummy; - } - if (func != NULL) { - if (builtins[i].flags&2) { - camel_sexp_add_ifunction (search->sexp, 0, builtins[i].name, (CamelSExpIFunc) func, search); - } else { - camel_sexp_add_function (search->sexp, 0, builtins[i].name, (CamelSExpFunc) func, search); - } - } + while (root) { + g_hash_table_insert (id_hash, (gchar *) camel_message_info_uid (root->message), root); + if (root->child) + fill_thread_table (root->child, id_hash); + root = root->next; } } -/** - * camel_folder_search_new: - * - * Create a new CamelFolderSearch object. - * - * A CamelFolderSearch is a subclassable, extensible s-exp - * evaluator which enforces a particular set of s-expressions. - * Particular methods may be overriden by an implementation to - * implement a search for any sort of backend. - * - * Returns: A new CamelFolderSearch widget. - **/ -CamelFolderSearch * -camel_folder_search_new (void) +static CamelMimeMessage * +get_current_message (CamelFolderSearch *search) { - CamelFolderSearch *new; - - new = g_object_new (CAMEL_TYPE_FOLDER_SEARCH, NULL); - camel_folder_search_construct (new); + if (!search || !search->folder || !search->current) + return NULL; - return new; + return camel_folder_get_message_sync ( + search->folder, search->current->uid, search->priv->cancellable, NULL); } -/** - * camel_folder_search_set_folder: - * @search: - * @folder: A folder. - * - * Set the folder attribute of the search. This is currently unused, but - * could be used to perform a slow-search when indexes and so forth are not - * available. Or for use by subclasses. - **/ -void -camel_folder_search_set_folder (CamelFolderSearch *search, - CamelFolder *folder) +static CamelSExpResult * +check_header (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search, + camel_search_match_t how) { - g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search)); - g_return_if_fail (CAMEL_IS_FOLDER (folder)); + CamelSExpResult *r; + gint truth = FALSE; - search->folder = folder; -} + r (printf ("executing check-header %d\n", how)); -/** - * camel_folder_search_set_summary: - * @search: - * @summary: An array of CamelMessageInfo pointers. - * - * Set the array of summary objects representing the span of the search. - * - * If this is not set, then a subclass must provide the functions - * for searching headers and for the match-all operator. - **/ -void -camel_folder_search_set_summary (CamelFolderSearch *search, - GPtrArray *summary) -{ - g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search)); + /* are we inside a match-all? */ + if (search->current && argc > 1 + && argv[0]->type == CAMEL_SEXP_RES_STRING + && !g_cancellable_is_cancelled (search->priv->cancellable)) { + gchar *headername; + const gchar *header = NULL, *charset = NULL; + gchar strbuf[32]; + gint i, j; + camel_search_t type = CAMEL_SEARCH_TYPE_ASIS; + struct _camel_search_words *words; + CamelMimeMessage *message = NULL; + struct _camel_header_raw *raw_header; - search->summary = summary; -} + /* only a subset of headers are supported .. */ + headername = argv[0]->value.string; + if (!g_ascii_strcasecmp (headername, "subject")) { + header = camel_message_info_subject (search->current); + } else if (!g_ascii_strcasecmp (headername, "date")) { + /* FIXME: not a very useful form of the date */ + sprintf (strbuf, "%d", (gint) camel_message_info_date_sent (search->current)); + header = strbuf; + } else if (!g_ascii_strcasecmp (headername, "from")) { + header = camel_message_info_from (search->current); + type = CAMEL_SEARCH_TYPE_ADDRESS; + } else if (!g_ascii_strcasecmp (headername, "to")) { + header = camel_message_info_to (search->current); + type = CAMEL_SEARCH_TYPE_ADDRESS; + } else if (!g_ascii_strcasecmp (headername, "cc")) { + header = camel_message_info_cc (search->current); + type = CAMEL_SEARCH_TYPE_ADDRESS; + } else if (!g_ascii_strcasecmp (headername, "x-camel-mlist")) { + header = camel_message_info_mlist (search->current); + type = CAMEL_SEARCH_TYPE_MLIST; + } else { + message = get_current_message (search); + if (message) { + CamelContentType *ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message)); -/** - * camel_folder_search_set_body_index: - * @search: - * @body_index: - * - * Set the index representing the contents of all messages - * in this folder. If this is not set, then the folder implementation - * should sub-class the CamelFolderSearch and provide its own - * body-contains function. - **/ -void -camel_folder_search_set_body_index (CamelFolderSearch *search, - CamelIndex *body_index) -{ - g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search)); + if (ct) { + charset = camel_content_type_param (ct, "charset"); + charset = camel_iconv_charset_name (charset); + } + } + } - if (body_index != NULL) { - g_return_if_fail (CAMEL_IS_INDEX (body_index)); - g_object_ref (body_index); - } + if (header == NULL) + header = ""; - if (search->body_index != NULL) - g_object_unref (search->body_index); + /* performs an OR of all words */ + for (i = 1; i < argc && !truth; i++) { + if (argv[i]->type == CAMEL_SEXP_RES_STRING) { + if (argv[i]->value.string[0] == 0) { + truth = TRUE; + } else if (how == CAMEL_SEARCH_MATCH_CONTAINS) { + /* doesn't make sense to split words on anything but contains i.e. we can't have an ending match different words */ + words = camel_search_words_split ((const guchar *) argv[i]->value.string); + truth = TRUE; + for (j = 0; j < words->len && truth; j++) { + if (message) { + for (raw_header = ((CamelMimePart *) message)->headers; raw_header; raw_header = raw_header->next) { + /* empty name means any header */ + if (!headername || !*headername || !g_ascii_strcasecmp (raw_header->name, headername)) { + if (camel_search_header_match (raw_header->value, words->words[j]->word, how, type, charset)) + break; + } + } - search->body_index = body_index; -} + truth = raw_header != NULL; + } else + truth = camel_search_header_match (header, words->words[j]->word, how, type, charset); + } + camel_search_words_free (words); + } else { + if (message) { + for (raw_header = ((CamelMimePart *) message)->headers; raw_header && !truth; raw_header = raw_header->next) { + /* empty name means any header */ + if (!headername || !*headername || !g_ascii_strcasecmp (raw_header->name, headername)) { + truth = camel_search_header_match (raw_header->value, argv[i]->value.string, how, type, charset); + } + } + } else + truth = camel_search_header_match (header, argv[i]->value.string, how, type, charset); + } + } + } -static gboolean -do_search_in_memory (CamelFolder *search_in_folder, - const gchar *expr) -{ - /* if the expression contains any of these tokens, then perform a memory search, instead of the SQL one */ - const gchar *in_memory_tokens[] = { - "body-contains", - "body-regex", - "match-threads", - "message-location", - "header-soundex", - "header-regex", - "header-full-regex", - "header-contains", - "header-has-words", - NULL }; - gint i; + if (message) + g_object_unref (message); + } + /* TODO: else, find all matches */ - if (search_in_folder && - search_in_folder->summary && - (search_in_folder->summary->flags & CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY) != 0) - return TRUE; + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + r->value.boolean = truth; - if (!expr) - return FALSE; + return r; +} - for (i = 0; in_memory_tokens[i]; i++) { - if (strstr (expr, in_memory_tokens[i])) - return TRUE; +static gint +match_message_index (CamelIndex *idx, + const gchar *uid, + const gchar *match, + GError **error) +{ + CamelIndexCursor *wc, *nc; + const gchar *word, *name; + gint truth = FALSE; + + wc = camel_index_words (idx); + if (wc) { + while (!truth && (word = camel_index_cursor_next (wc))) { + if (camel_ustrstrcase (word,match) != NULL) { + /* perf: could have the wc cursor return the name cursor */ + nc = camel_index_find (idx, word); + if (nc) { + while (!truth && (name = camel_index_cursor_next (nc))) + truth = strcmp (name, uid) == 0; + g_object_unref (nc); + } + } + } + g_object_unref (wc); } - return FALSE; + return truth; } -/** - * camel_folder_search_count: - * @search: - * @expr: - * @cancellable: a #GCancellable - * @error: return location for a #GError, or %NULL - * - * Run a search. Search must have had Folder already set on it, and - * it must implement summaries. - * - * Returns: Number of messages that match the query. +/* + "one two" "three" "four five" * - * Since: 2.26 - **/ + * one and two + * or + * three + * or + * four and five + */ -guint32 -camel_folder_search_count (CamelFolderSearch *search, - const gchar *expr, - GCancellable *cancellable, - GError **error) +/* returns messages which contain all words listed in words */ +static GPtrArray * +match_words_index (CamelFolderSearch *search, + struct _camel_search_words *words, + GCancellable *cancellable, + GError **error) { - CamelSExpResult *r; - GPtrArray *summary_set; + GPtrArray *result = g_ptr_array_new (); + struct IterData lambdafoo; + CamelIndexCursor *wc, *nc; + const gchar *word, *name; gint i; - CamelDB *cdb; - gchar *sql_query, *tmp, *tmp1; - GHashTable *results; - guint32 count = 0; - - CamelFolderSearchPrivate *p; - - g_return_val_if_fail (search != NULL, 0); - - p = search->priv; if (g_cancellable_set_error_if_cancelled (cancellable, error)) - goto fail; - - if (!expr || !*expr) - expr = "(match-all)"; - - g_assert (search->folder); + return result; - p->cancellable = cancellable; - p->error = error; + /* we can have a maximum of 32 words, as we use it as the AND mask */ - /* We route body-contains search and thread based search through memory and not via db. */ - if (do_search_in_memory (search->folder, expr)) { - /* setup our search list only contains those we're interested in */ - search->summary = camel_folder_get_summary (search->folder); - if (search->folder->summary) - camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL); + wc = camel_index_words (search->body_index); + if (wc) { + GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal); - summary_set = search->summary; + while ((word = camel_index_cursor_next (wc))) { + for (i = 0; i < words->len; i++) { + if (camel_ustrstrcase (word, words->words[i]->word) != NULL) { + /* perf: could have the wc cursor return the name cursor */ + nc = camel_index_find (search->body_index, word); + if (nc) { + while ((name = camel_index_cursor_next (nc))) { + gint mask; - /* only re-parse if the search has changed */ - if (search->last_search == NULL - || strcmp (search->last_search, expr)) { - camel_sexp_input_text (search->sexp, expr, strlen (expr)); - if (camel_sexp_parse (search->sexp) == -1) { - g_set_error ( - error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("Cannot parse search expression: %s:\n%s"), - camel_sexp_error (search->sexp), expr); - goto fail; + mask = (GPOINTER_TO_INT (g_hash_table_lookup (ht, name))) | (1 << i); + g_hash_table_insert (ht, (gchar *) camel_pstring_peek (name), GINT_TO_POINTER (mask)); + } + g_object_unref (nc); + } + } } - - g_free (search->last_search); - search->last_search = g_strdup (expr); - } - r = camel_sexp_eval (search->sexp); - if (r == NULL) { - g_set_error ( - error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("Error executing search expression: %s:\n%s"), - camel_sexp_error (search->sexp), expr); - goto fail; } + g_object_unref (wc); - /* now create a folder summary to return?? */ - if (r->type == CAMEL_SEXP_RES_ARRAY_PTR) { - d (printf ("got result\n")); + lambdafoo.uids = result; + lambdafoo.count = (1 << words->len) - 1; + g_hash_table_foreach (ht, (GHFunc) htand, &lambdafoo); + g_hash_table_destroy (ht); + } - /* reorder result in summary order */ - results = g_hash_table_new (g_str_hash, g_str_equal); - for (i = 0; i < r->value.ptrarray->len; i++) { - d (printf ("adding match: %s\n", (gchar *) g_ptr_array_index (r->value.ptrarray, i))); - g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1)); - } + return result; +} - for (i = 0; i < summary_set->len; i++) { - gchar *uid = g_ptr_array_index (summary_set, i); - if (g_hash_table_lookup (results, uid)) - count++; - } - g_hash_table_destroy (results); - } +static gboolean +match_words_1message (CamelDataWrapper *object, + struct _camel_search_words *words, + guint32 *mask, + GCancellable *cancellable) +{ + CamelDataWrapper *containee; + gint truth = FALSE; + gint parts, i; - camel_sexp_result_free (search->sexp, r); + if (g_cancellable_is_cancelled (cancellable)) + return FALSE; - } else { - CamelStore *parent_store; - const gchar *full_name; - GError *local_error = NULL; + containee = camel_medium_get_content (CAMEL_MEDIUM (object)); - full_name = camel_folder_get_full_name (search->folder); - parent_store = camel_folder_get_parent_store (search->folder); + if (containee == NULL) + return FALSE; - /* Sync the db, so that we search the db for changes */ - camel_folder_summary_save_to_db (search->folder->summary, error); + /* using the object types is more accurate than using the mime/types */ + if (CAMEL_IS_MULTIPART (containee)) { + parts = camel_multipart_get_number (CAMEL_MULTIPART (containee)); + for (i = 0; i < parts && truth == FALSE; i++) { + CamelDataWrapper *part = (CamelDataWrapper *) camel_multipart_get_part (CAMEL_MULTIPART (containee), i); + if (part) + truth = match_words_1message (part, words, mask, cancellable); + } + } else if (CAMEL_IS_MIME_MESSAGE (containee)) { + /* for messages we only look at its contents */ + truth = match_words_1message ((CamelDataWrapper *) containee, words, mask, cancellable); + } else if (camel_content_type_is (CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")) { + /* for all other text parts, we look inside, otherwise we dont care */ + CamelStream *stream; + GByteArray *byte_array; - dd (printf ("sexp is : [%s]\n", expr)); - sql_query = camel_sexp_to_sql_sexp (expr); - tmp1 = camel_db_sqlize_string (full_name); - tmp = g_strdup_printf ("SELECT COUNT (*) FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query ? sql_query:""); - camel_db_free_sqlized_string (tmp1); - g_free (sql_query); - dd (printf ("Equivalent sql %s\n", tmp)); + byte_array = g_byte_array_new (); + stream = camel_stream_mem_new_with_byte_array (byte_array); - cdb = (CamelDB *) (parent_store->cdb_r); - camel_db_count_message_info (cdb, tmp, &count, &local_error); - if (local_error != NULL) { - const gchar *message = local_error->message; - if (strncmp (message, "no such table", 13) == 0) { - d (g_warning ("Error during searching %s: %s\n", tmp, message)); - /* Suppress no such table */ - g_clear_error (&local_error); + /* FIXME The match should be part of a stream op */ + camel_data_wrapper_decode_to_stream_sync ( + containee, stream, cancellable, NULL); + camel_stream_write (stream, "", 1, NULL, NULL); + for (i = 0; i < words->len; i++) { + /* FIXME: This is horridly slow, and should use a real search algorithm */ + if (camel_ustrstrcase ((const gchar *) byte_array->data, words->words[i]->word) != NULL) { + *mask |= (1 << i); + /* shortcut a match */ + if (*mask == (1 << (words->len)) - 1) + return TRUE; } - g_propagate_error (error, local_error); } - g_free (tmp); - } -fail: - /* these might be allocated by match-threads */ - if (p->threads) - camel_folder_thread_messages_unref (p->threads); - if (p->threads_hash) - g_hash_table_destroy (p->threads_hash); - if (search->summary_set) - g_ptr_array_free (search->summary_set, TRUE); - if (search->summary) - camel_folder_free_summary (search->folder, search->summary); - - p->cancellable = NULL; - p->error = NULL; - p->threads = NULL; - p->threads_hash = NULL; - search->folder = NULL; - search->summary = NULL; - search->summary_set = NULL; - search->current = NULL; - search->body_index = NULL; + g_object_unref (stream); + } - return count; + return truth; } -/** - * camel_folder_search_search: - * @search: - * @expr: - * @uids: to search against, NULL for all uid's. - * @cancellable: a #GCancellable - * @error: return location for a #GError, or %NULL - * - * Run a search. Search must have had Folder already set on it, and - * it must implement summaries. - * - * Returns: - **/ -GPtrArray * -camel_folder_search_search (CamelFolderSearch *search, - const gchar *expr, - GPtrArray *uids, - GCancellable *cancellable, - GError **error) +static gboolean +match_words_message (CamelFolder *folder, + const gchar *uid, + struct _camel_search_words *words, + GCancellable *cancellable, + GError **error) { - CamelSExpResult *r; - GPtrArray *matches = NULL, *summary_set; - gint i; - CamelDB *cdb; - gchar *sql_query, *tmp, *tmp1; - GHashTable *results; - - CamelFolderSearchPrivate *p; - - g_return_val_if_fail (search != NULL, NULL); - - p = search->priv; + guint32 mask; + CamelMimeMessage *msg; + gint truth = FALSE; if (g_cancellable_set_error_if_cancelled (cancellable, error)) - goto fail; - - if (!expr || !*expr) - expr = "(match-all)"; - - g_assert (search->folder); - - p->cancellable = cancellable; - p->error = error; - - /* We route body-contains / thread based search and uid search through memory and not via db. */ - if (uids || do_search_in_memory (search->folder, expr)) { - /* setup our search list only contains those we're interested in */ - search->summary = camel_folder_get_summary (search->folder); + return truth; - if (uids) { - GHashTable *uids_hash = g_hash_table_new (g_str_hash, g_str_equal); + msg = camel_folder_get_message_sync (folder, uid, cancellable, error); + if (msg) { + mask = 0; + truth = match_words_1message ((CamelDataWrapper *) msg, words, &mask, cancellable); + g_object_unref (msg); + } - summary_set = search->summary_set = g_ptr_array_new (); - for (i = 0; i < uids->len; i++) - g_hash_table_insert (uids_hash, uids->pdata[i], uids->pdata[i]); - for (i = 0; i < search->summary->len; i++) - if (g_hash_table_lookup (uids_hash, search->summary->pdata[i])) - g_ptr_array_add (search->summary_set, search->summary->pdata[i]); - g_hash_table_destroy (uids_hash); - } else { - if (search->folder->summary) - camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL); - summary_set = search->summary; - } + return truth; +} - /* only re-parse if the search has changed */ - if (search->last_search == NULL - || strcmp (search->last_search, expr)) { - camel_sexp_input_text (search->sexp, expr, strlen (expr)); - if (camel_sexp_parse (search->sexp) == -1) { - g_set_error ( - error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("Cannot parse search expression: %s:\n%s"), - camel_sexp_error (search->sexp), expr); - goto fail; - } +static GPtrArray * +match_words_messages (CamelFolderSearch *search, + struct _camel_search_words *words, + GCancellable *cancellable, + GError **error) +{ + gint i; + GPtrArray *matches = g_ptr_array_new (); - g_free (search->last_search); - search->last_search = g_strdup (expr); - } - r = camel_sexp_eval (search->sexp); - if (r == NULL) { - g_set_error ( - error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("Error executing search expression: %s:\n%s"), - camel_sexp_error (search->sexp), expr); - goto fail; - } + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return matches; - matches = g_ptr_array_new (); + if (search->body_index) { + GPtrArray *indexed; + struct _camel_search_words *simple; - /* now create a folder summary to return?? */ - if (r->type == CAMEL_SEXP_RES_ARRAY_PTR) { - d (printf ("got result\n")); + simple = camel_search_words_simple (words); + indexed = match_words_index (search, simple, cancellable, error); + camel_search_words_free (simple); - /* reorder result in summary order */ - results = g_hash_table_new (g_str_hash, g_str_equal); - for (i = 0; i < r->value.ptrarray->len; i++) { - d (printf ("adding match: %s\n", (gchar *) g_ptr_array_index (r->value.ptrarray, i))); - g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1)); - } + for (i = 0; i < indexed->len && !g_cancellable_is_cancelled (cancellable); i++) { + const gchar *uid = g_ptr_array_index (indexed, i); - for (i = 0; i < summary_set->len; i++) { - gchar *uid = g_ptr_array_index (summary_set, i); - if (g_hash_table_lookup (results, uid)) - g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (uid)); - } - g_hash_table_destroy (results); + if (match_words_message ( + search->folder, uid, words, + cancellable, error)) + g_ptr_array_add (matches, (gchar *) uid); } - camel_sexp_result_free (search->sexp, r); - + g_ptr_array_free (indexed, TRUE); } else { - CamelStore *parent_store; - const gchar *full_name; - GError *local_error = NULL; - - full_name = camel_folder_get_full_name (search->folder); - parent_store = camel_folder_get_parent_store (search->folder); - - /* Sync the db, so that we search the db for changes */ - camel_folder_summary_save_to_db (search->folder->summary, error); + GPtrArray *v = search->summary_set ? search->summary_set : search->summary; - dd (printf ("sexp is : [%s]\n", expr)); - sql_query = camel_sexp_to_sql_sexp (expr); - tmp1 = camel_db_sqlize_string (full_name); - tmp = g_strdup_printf ("SELECT uid FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query ? sql_query:""); - camel_db_free_sqlized_string (tmp1); - g_free (sql_query); - dd (printf ("Equivalent sql %s\n", tmp)); + for (i = 0; i < v->len && !g_cancellable_is_cancelled (cancellable); i++) { + gchar *uid = g_ptr_array_index (v, i); - matches = g_ptr_array_new (); - cdb = (CamelDB *) (parent_store->cdb_r); - camel_db_select ( - cdb, tmp, (CamelDBSelectCB) - read_uid_callback, matches, &local_error); - if (local_error != NULL) { - const gchar *message = local_error->message; - if (strncmp (message, "no such table", 13) == 0) { - d (g_warning ("Error during searching %s: %s\n", tmp, message)); - /* Suppress no such table */ - g_clear_error (&local_error); - } else - g_propagate_error (error, local_error); + if (match_words_message ( + search->folder, uid, words, + cancellable, error)) + g_ptr_array_add (matches, (gchar *) uid); } - g_free (tmp); - } -fail: - /* these might be allocated by match-threads */ - if (p->threads) - camel_folder_thread_messages_unref (p->threads); - if (p->threads_hash) - g_hash_table_destroy (p->threads_hash); - if (search->summary_set) - g_ptr_array_free (search->summary_set, TRUE); - if (search->summary) - camel_folder_free_summary (search->folder, search->summary); + return matches; +} - p->cancellable = NULL; - p->error = NULL; - p->threads = NULL; - p->threads_hash = NULL; - search->folder = NULL; - search->summary = NULL; - search->summary_set = NULL; - search->current = NULL; - search->body_index = NULL; +static gchar * +get_full_header (CamelMimeMessage *message) +{ + CamelMimePart *mp = CAMEL_MIME_PART (message); + GString *str = g_string_new (""); + struct _camel_header_raw *h; - if (error && *error) { - camel_folder_search_free_result (search, matches); - matches = NULL; + for (h = mp->headers; h; h = h->next) { + if (h->value != NULL) { + g_string_append (str, h->name); + if (isspace (h->value[0])) + g_string_append (str, ":"); + else + g_string_append (str, ": "); + g_string_append (str, h->value); + g_string_append_c (str, '\n'); + } } - return matches; + return g_string_free (str, FALSE); } -void -camel_folder_search_free_result (CamelFolderSearch *search, - GPtrArray *result) +static gint +read_uid_callback (gpointer ref, + gint ncol, + gchar **cols, + gchar **name) { - if (!result) - return; + GPtrArray *matches; - g_ptr_array_foreach (result, (GFunc) camel_pstring_free, NULL); - g_ptr_array_free (result, TRUE); + matches = (GPtrArray *) ref; + + g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (cols[0])); + return 0; } -/* dummy function, returns false always, or an empty match array */ -static CamelSExpResult * -search_dummy (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +static void +folder_search_dispose (GObject *object) { - CamelSExpResult *r; + CamelFolderSearch *search = CAMEL_FOLDER_SEARCH (object); - if (search->current == NULL) { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); - r->value.boolean = FALSE; - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); + if (search->sexp != NULL) { + g_object_unref (search->sexp); + search->sexp = NULL; } - return r; + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (camel_folder_search_parent_class)->dispose (object); +} + +static void +folder_search_finalize (GObject *object) +{ + CamelFolderSearch *search = CAMEL_FOLDER_SEARCH (object); + + g_free (search->last_search); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (camel_folder_search_parent_class)->finalize (object); } -/* impelemnt an 'array not', i.e. everything in the summary, not in the supplied array */ +/* implement an 'array not', i.e. everything in the summary, not in the supplied array */ static CamelSExpResult * -search_not (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_not (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { CamelSExpResult *r; gint i; @@ -736,7 +538,7 @@ search_not (struct _CamelSExp *f, GPtrArray *v = argv[0]->value.ptrarray; const gchar *uid; - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); r->value.ptrarray = g_ptr_array_new (); /* not against a single message?*/ @@ -779,11 +581,11 @@ search_not (struct _CamelSExp *f, if (argv[0]->type == CAMEL_SEXP_RES_BOOL) res = !argv[0]->value.boolean; - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); r->value.boolean = res; } } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); r->value.boolean = TRUE; } @@ -791,10 +593,10 @@ search_not (struct _CamelSExp *f, } static CamelSExpResult * -search_match_all (struct _CamelSExp *f, - gint argc, - struct _CamelSExpTerm **argv, - CamelFolderSearch *search) +folder_search_match_all (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search) { gint i; CamelSExpResult *r, *r1; @@ -809,28 +611,28 @@ search_match_all (struct _CamelSExp *f, if (search->current) { d (printf ("matching against 1 message: %s\n", camel_message_info_subject (search->current))); - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); r->value.boolean = FALSE; if (argc > 0) { - r1 = camel_sexp_term_eval (f, argv[0]); + r1 = camel_sexp_term_eval (sexp, argv[0]); if (r1->type == CAMEL_SEXP_RES_BOOL) { r->value.boolean = r1->value.boolean; } else { g_warning ("invalid syntax, matches require a single bool result"); /* Translators: The '%s' is an element type name, part of an expressing language */ error_msg = g_strdup_printf (_("(%s) requires a single bool result"), "match-all"); - camel_sexp_fatal_error (f, error_msg); + camel_sexp_fatal_error (sexp, error_msg); g_free (error_msg); } - camel_sexp_result_free (f, r1); + camel_sexp_result_free (sexp, r1); } else { r->value.boolean = TRUE; } return r; } - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); r->value.ptrarray = g_ptr_array_new (); if (search->summary == NULL) { @@ -855,7 +657,7 @@ search_match_all (struct _CamelSExp *f, uid = camel_message_info_uid (search->current); if (argc > 0) { - r1 = camel_sexp_term_eval (f, argv[0]); + r1 = camel_sexp_term_eval (sexp, argv[0]); if (r1->type == CAMEL_SEXP_RES_BOOL) { if (r1->value.boolean) g_ptr_array_add (r->value.ptrarray, (gchar *) uid); @@ -863,10 +665,10 @@ search_match_all (struct _CamelSExp *f, g_warning ("invalid syntax, matches require a single bool result"); /* Translators: The '%s' is an element type name, part of an expressing language */ error_msg = g_strdup_printf (_("(%s) requires a single bool result"), "match-all"); - camel_sexp_fatal_error (f, error_msg); + camel_sexp_fatal_error (sexp, error_msg); g_free (error_msg); } - camel_sexp_result_free (f, r1); + camel_sexp_result_free (sexp, r1); } else { g_ptr_array_add (r->value.ptrarray, (gchar *) uid); } @@ -876,43 +678,11 @@ search_match_all (struct _CamelSExp *f, return r; } -static void -fill_thread_table (struct _CamelFolderThreadNode *root, - GHashTable *id_hash) -{ - while (root) { - g_hash_table_insert (id_hash, (gchar *) camel_message_info_uid (root->message), root); - if (root->child) - fill_thread_table (root->child, id_hash); - root = root->next; - } -} - -static void -add_thread_results (struct _CamelFolderThreadNode *root, - GHashTable *result_hash) -{ - while (root) { - g_hash_table_insert (result_hash, (gchar *) camel_message_info_uid (root->message), GINT_TO_POINTER (1)); - if (root->child) - add_thread_results (root->child, result_hash); - root = root->next; - } -} - -static void -add_results (gchar *uid, - gpointer dummy, - GPtrArray *result) -{ - g_ptr_array_add (result, uid); -} - static CamelSExpResult * -search_match_threads (struct _CamelSExp *f, - gint argc, - struct _CamelSExpTerm **argv, - CamelFolderSearch *search) +folder_search_match_threads (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search) { CamelSExpResult *r; CamelFolderSearchPrivate *p = search->priv; @@ -921,7 +691,7 @@ search_match_threads (struct _CamelSExp *f, gchar *error_msg; if (g_cancellable_is_cancelled (search->priv->cancellable)) { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); r->value.ptrarray = g_ptr_array_new (); return r; } @@ -930,22 +700,22 @@ search_match_threads (struct _CamelSExp *f, if (search->current) { /* Translators: Each '%s' is an element type name, part of an expressing language */ error_msg = g_strdup_printf (_("(%s) not allowed inside %s"), "match-threads", "match-all"); - camel_sexp_fatal_error (f, error_msg); + camel_sexp_fatal_error (sexp, error_msg); g_free (error_msg); } if (argc == 0) { /* Translators: The '%s' is an element type name, part of an expressing language */ error_msg = g_strdup_printf (_("(%s) requires a match type string"), "match-threads"); - camel_sexp_fatal_error (f, error_msg); + camel_sexp_fatal_error (sexp, error_msg); g_free (error_msg); } - r = camel_sexp_term_eval (f, argv[0]); + r = camel_sexp_term_eval (sexp, argv[0]); if (r->type != CAMEL_SEXP_RES_STRING) { /* Translators: The '%s' is an element type name, part of an expressing language */ error_msg = g_strdup_printf (_("(%s) requires a match type string"), "match-threads"); - camel_sexp_fatal_error (f, error_msg); + camel_sexp_fatal_error (sexp, error_msg); g_free (error_msg); } @@ -960,20 +730,20 @@ search_match_threads (struct _CamelSExp *f, type = 3; else if (!strcmp (r->value.string, "single")) type = 4; - camel_sexp_result_free (f, r); + camel_sexp_result_free (sexp, r); /* behave as (begin does */ r = NULL; for (i = 1; i < argc; i++) { if (r) - camel_sexp_result_free (f, r); - r = camel_sexp_term_eval (f, argv[i]); + camel_sexp_result_free (sexp, r); + r = camel_sexp_term_eval (sexp, argv[i]); } if (r == NULL || r->type != CAMEL_SEXP_RES_ARRAY_PTR) { /* Translators: The '%s' is an element type name, part of an expressing language */ error_msg = g_strdup_printf (_("(%s) expects an array result"), "match-threads"); - camel_sexp_fatal_error (f, error_msg); + camel_sexp_fatal_error (sexp, error_msg); g_free (error_msg); } @@ -983,7 +753,7 @@ search_match_threads (struct _CamelSExp *f, if (search->folder == NULL) { /* Translators: The '%s' is an element type name, part of an expressing language */ error_msg = g_strdup_printf (_("(%s) requires the folder set"), "match-threads"); - camel_sexp_fatal_error (f, error_msg); + camel_sexp_fatal_error (sexp, error_msg); g_free (error_msg); } @@ -997,7 +767,7 @@ search_match_threads (struct _CamelSExp *f, results = g_hash_table_new (g_str_hash, g_str_equal); for (i = 0; i < r->value.ptrarray->len && !g_cancellable_is_cancelled (search->priv->cancellable); i++) { - struct _CamelFolderThreadNode *node, *scan; + CamelFolderThreadNode *node, *scan; if (type != 4) g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1)); @@ -1017,206 +787,209 @@ search_match_threads (struct _CamelSExp *f, scan = scan->parent; g_hash_table_insert (results, (gchar *) camel_message_info_uid (scan->message), GINT_TO_POINTER (1)); } - } else if (type == 1) { - while (node && node->parent) - node = node->parent; + } else if (type == 1) { + while (node && node->parent) + node = node->parent; + } + g_hash_table_insert (results, (gchar *) camel_message_info_uid (node->message), GINT_TO_POINTER (1)); + if (node->child) + add_thread_results (node->child, results); + } + } + camel_sexp_result_free (sexp, r); + + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); + + g_hash_table_foreach (results, (GHFunc) add_results, r->value.ptrarray); + g_hash_table_destroy (results); + + return r; +} + +static CamelSExpResult * +folder_search_body_contains (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) +{ + gint i, j; + GError **error = search->priv->error; + struct _camel_search_words *words; + CamelSExpResult *r; + struct IterData lambdafoo; + + if (search->current) { + gint truth = FALSE; + + if (argc == 1 && argv[0]->value.string[0] == 0) { + truth = TRUE; + } else { + for (i = 0; i < argc && !truth && !g_cancellable_is_cancelled (search->priv->cancellable); i++) { + if (argv[i]->type == CAMEL_SEXP_RES_STRING) { + words = camel_search_words_split ((const guchar *) argv[i]->value.string); + truth = TRUE; + if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) { + for (j = 0; j < words->len && truth; j++) + truth = match_message_index (search->body_index, camel_message_info_uid (search->current), words->words[j]->word, error); + } else { + /* TODO: cache current message incase of multiple body search terms */ + truth = match_words_message (search->folder, camel_message_info_uid (search->current), words, search->priv->cancellable, error); + } + camel_search_words_free (words); + } + } + } + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + r->value.boolean = truth; + } else { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); + + if (argc == 1 && argv[0]->value.string[0] == 0) { + GPtrArray *v = search->summary_set ? search->summary_set : search->summary; + + for (i = 0; i < v->len && !g_cancellable_is_cancelled (search->priv->cancellable); i++) { + gchar *uid = g_ptr_array_index (v, i); + + g_ptr_array_add (r->value.ptrarray, uid); + } + } else { + GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal); + GPtrArray *matches; + + for (i = 0; i < argc && !g_cancellable_is_cancelled (search->priv->cancellable); i++) { + if (argv[i]->type == CAMEL_SEXP_RES_STRING) { + words = camel_search_words_split ((const guchar *) argv[i]->value.string); + if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) { + matches = match_words_index (search, words, search->priv->cancellable, error); + } else { + matches = match_words_messages (search, words, search->priv->cancellable, error); + } + for (j = 0; j < matches->len; j++) { + g_hash_table_insert (ht, matches->pdata[j], matches->pdata[j]); + } + g_ptr_array_free (matches, TRUE); + camel_search_words_free (words); + } } - g_hash_table_insert (results, (gchar *) camel_message_info_uid (node->message), GINT_TO_POINTER (1)); - if (node->child) - add_thread_results (node->child, results); + lambdafoo.uids = r->value.ptrarray; + g_hash_table_foreach (ht, (GHFunc) htor, &lambdafoo); + g_hash_table_destroy (ht); } } - camel_sexp_result_free (f, r); - - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); - - g_hash_table_foreach (results, (GHFunc) add_results, r->value.ptrarray); - g_hash_table_destroy (results); return r; } -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); -} - static CamelSExpResult * -check_header (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search, - camel_search_match_t how) +folder_search_body_regex (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { CamelSExpResult *r; - gint truth = FALSE; + CamelMimeMessage *msg = get_current_message (search); - r (printf ("executing check-header %d\n", how)); + if (msg) { + regex_t pattern; - /* are we inside a match-all? */ - if (search->current && argc > 1 - && argv[0]->type == CAMEL_SEXP_RES_STRING - && !g_cancellable_is_cancelled (search->priv->cancellable)) { - gchar *headername; - const gchar *header = NULL, *charset = NULL; - gchar strbuf[32]; - gint i, j; - camel_search_t type = CAMEL_SEARCH_TYPE_ASIS; - struct _camel_search_words *words; - CamelMimeMessage *message = NULL; - struct _camel_header_raw *raw_header; + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); - /* only a subset of headers are supported .. */ - headername = argv[0]->value.string; - if (!g_ascii_strcasecmp (headername, "subject")) { - header = camel_message_info_subject (search->current); - } else if (!g_ascii_strcasecmp (headername, "date")) { - /* FIXME: not a very useful form of the date */ - sprintf (strbuf, "%d", (gint) camel_message_info_date_sent (search->current)); - header = strbuf; - } else if (!g_ascii_strcasecmp (headername, "from")) { - header = camel_message_info_from (search->current); - type = CAMEL_SEARCH_TYPE_ADDRESS; - } else if (!g_ascii_strcasecmp (headername, "to")) { - header = camel_message_info_to (search->current); - type = CAMEL_SEARCH_TYPE_ADDRESS; - } else if (!g_ascii_strcasecmp (headername, "cc")) { - header = camel_message_info_cc (search->current); - type = CAMEL_SEARCH_TYPE_ADDRESS; - } else if (!g_ascii_strcasecmp (headername, "x-camel-mlist")) { - header = camel_message_info_mlist (search->current); - type = CAMEL_SEARCH_TYPE_MLIST; - } else { - message = get_current_message (search); - if (message) { - CamelContentType *ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message)); + if (!g_cancellable_is_cancelled (search->priv->cancellable) && + camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->error) == 0) { + r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) msg, &pattern); + regfree (&pattern); + } else + r->value.boolean = FALSE; - if (ct) { - charset = camel_content_type_param (ct, "charset"); - charset = camel_iconv_charset_name (charset); - } - } - } + g_object_unref (msg); + } else { + regex_t pattern; - if (header == NULL) - header = ""; + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); - /* performs an OR of all words */ - for (i = 1; i < argc && !truth; i++) { - if (argv[i]->type == CAMEL_SEXP_RES_STRING) { - if (argv[i]->value.string[0] == 0) { - truth = TRUE; - } else if (how == CAMEL_SEARCH_MATCH_CONTAINS) { - /* doesn't make sense to split words on anything but contains i.e. we can't have an ending match different words */ - words = camel_search_words_split ((const guchar *) argv[i]->value.string); - truth = TRUE; - for (j = 0; j < words->len && truth; j++) { - if (message) { - for (raw_header = ((CamelMimePart *) message)->headers; raw_header; raw_header = raw_header->next) { - /* empty name means any header */ - if (!headername || !*headername || !g_ascii_strcasecmp (raw_header->name, headername)) { - if (camel_search_header_match (raw_header->value, words->words[j]->word, how, type, charset)) - break; - } - } + if (!g_cancellable_is_cancelled (search->priv->cancellable) && + camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->error) == 0) { + gint i; + GPtrArray *v = search->summary_set ? search->summary_set : search->summary; + CamelMimeMessage *message; - truth = raw_header != NULL; - } else - truth = camel_search_header_match (header, words->words[j]->word, how, type, charset); + 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); + if (message) { + if (camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern)) { + g_ptr_array_add (r->value.ptrarray, uid); } - camel_search_words_free (words); - } else { - if (message) { - for (raw_header = ((CamelMimePart *) message)->headers; raw_header && !truth; raw_header = raw_header->next) { - /* empty name means any header */ - if (!headername || !*headername || !g_ascii_strcasecmp (raw_header->name, headername)) { - truth = camel_search_header_match (raw_header->value, argv[i]->value.string, how, type, charset); - } - } - } else - truth = camel_search_header_match (header, argv[i]->value.string, how, type, charset); + + g_object_unref (message); } } - } - if (message) - g_object_unref (message); + regfree (&pattern); + } } - /* TODO: else, find all matches */ - - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); - r->value.boolean = truth; return r; } -/* -static void -l_printf (gchar *node) -{ -printf ("%s\t", node); -} -*/ - static CamelSExpResult * -search_header_contains (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_header_contains (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS); + return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS); } static CamelSExpResult * -search_header_matches (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_header_matches (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT); + return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT); } static CamelSExpResult * -search_header_starts_with (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_header_starts_with (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS); + return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS); } static CamelSExpResult * -search_header_ends_with (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_header_ends_with (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS); + return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS); } static CamelSExpResult * -search_header_exists (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_header_exists (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { CamelSExpResult *r; r (printf ("executing header-exists\n")); if (search->current) { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) r->value.boolean = camel_medium_get_header (CAMEL_MEDIUM (search->current), argv[0]->value.string) != NULL; } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); r->value.ptrarray = g_ptr_array_new (); } @@ -1224,19 +997,19 @@ search_header_exists (struct _CamelSExp *f, } static CamelSExpResult * -search_header_soundex (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_header_soundex (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_SOUNDEX); + return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_SOUNDEX); } static CamelSExpResult * -search_header_regex (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_header_regex (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { CamelSExpResult *r; CamelMimeMessage *msg; @@ -1247,7 +1020,7 @@ search_header_regex (struct _CamelSExp *f, regex_t pattern; const gchar *contents; - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING && (contents = camel_medium_get_header (CAMEL_MEDIUM (msg), argv[0]->value.string)) @@ -1259,727 +1032,917 @@ search_header_regex (struct _CamelSExp *f, g_object_unref (msg); } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); r->value.ptrarray = g_ptr_array_new (); } return r; } -static gchar * -get_full_header (CamelMimeMessage *message) +static CamelSExpResult * +folder_search_header_full_regex (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - CamelMimePart *mp = CAMEL_MIME_PART (message); - GString *str = g_string_new (""); - struct _camel_header_raw *h; + CamelSExpResult *r; + CamelMimeMessage *msg; - for (h = mp->headers; h; h = h->next) { - if (h->value != NULL) { - g_string_append (str, h->name); - if (isspace (h->value[0])) - g_string_append (str, ":"); - else - g_string_append (str, ": "); - g_string_append (str, h->value); - g_string_append_c (str, '\n'); - } + msg = get_current_message (search); + + if (msg) { + regex_t pattern; + + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + + if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->error) == 0) { + gchar *contents; + + contents = get_full_header (msg); + r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0; + + g_free (contents); + regfree (&pattern); + } else + r->value.boolean = FALSE; + + g_object_unref (msg); + } else { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); } - return g_string_free (str, FALSE); + return r; } static CamelSExpResult * -search_header_full_regex (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_user_tag (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { + const gchar *value = NULL; CamelSExpResult *r; - CamelMimeMessage *msg; - msg = get_current_message (search); + r (printf ("executing user-tag\n")); - if (msg) { - regex_t pattern; + if (search->current && argc == 1) + value = camel_message_info_user_tag (search->current, argv[0]->value.string); + + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_STRING); + r->value.string = g_strdup (value ? value : ""); + + return r; +} + +static CamelSExpResult * +folder_search_user_flag (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) +{ + CamelSExpResult *r; + gint i; + + r (printf ("executing user-flag\n")); + + /* are we inside a match-all? */ + if (search->current) { + gint truth = FALSE; + /* performs an OR of all words */ + for (i = 0; i < argc && !truth; i++) { + if (argv[i]->type == CAMEL_SEXP_RES_STRING + && camel_message_info_user_flag (search->current, argv[i]->value.string)) { + truth = TRUE; + break; + } + } + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + r->value.boolean = truth; + } else { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); + } - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); + return r; +} + +static CamelSExpResult * +folder_search_system_flag (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) +{ + CamelSExpResult *r; - if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->error) == 0) { - gchar *contents; + r (printf ("executing system-flag\n")); - contents = get_full_header (msg); - r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0; + if (search->current) { + gboolean truth = FALSE; - g_free (contents); - regfree (&pattern); - } else - r->value.boolean = FALSE; + if (argc == 1) + truth = camel_system_flag_get (camel_message_info_flags (search->current), argv[0]->value.string); - g_object_unref (msg); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + r->value.boolean = truth; } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); r->value.ptrarray = g_ptr_array_new (); } return r; } -/* this is just to OR results together */ -struct IterData { - gint count; - GPtrArray *uids; -}; - -/* or, store all unique values */ -static void -htor (gchar *key, - gint value, - struct IterData *iter_data) +static CamelSExpResult * +folder_search_get_sent_date (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - g_ptr_array_add (iter_data->uids, key); -} + CamelSExpResult *r; -/* and, only store duplicates */ -static void -htand (gchar *key, - gint value, - struct IterData *iter_data) -{ - if (value == iter_data->count) - g_ptr_array_add (iter_data->uids, key); -} + r (printf ("executing get-sent-date\n")); -static gint -match_message_index (CamelIndex *idx, - const gchar *uid, - const gchar *match, - GError **error) -{ - CamelIndexCursor *wc, *nc; - const gchar *word, *name; - gint truth = FALSE; + /* are we inside a match-all? */ + if (search->current) { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT); - wc = camel_index_words (idx); - if (wc) { - while (!truth && (word = camel_index_cursor_next (wc))) { - if (camel_ustrstrcase (word,match) != NULL) { - /* perf: could have the wc cursor return the name cursor */ - nc = camel_index_find (idx, word); - if (nc) { - while (!truth && (name = camel_index_cursor_next (nc))) - truth = strcmp (name, uid) == 0; - g_object_unref (nc); - } - } - } - g_object_unref (wc); + r->value.number = camel_message_info_date_sent (search->current); + } else { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); } - return truth; + return r; } -/* - "one two" "three" "four five" - * - * one and two - * or - * three - * or - * four and five - */ - -/* returns messages which contain all words listed in words */ -static GPtrArray * -match_words_index (CamelFolderSearch *search, - struct _camel_search_words *words, - GCancellable *cancellable, - GError **error) +static CamelSExpResult * +folder_search_get_received_date (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - GPtrArray *result = g_ptr_array_new (); - struct IterData lambdafoo; - CamelIndexCursor *wc, *nc; - const gchar *word, *name; - gint i; - - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - return result; - - /* we can have a maximum of 32 words, as we use it as the AND mask */ - - wc = camel_index_words (search->body_index); - if (wc) { - GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal); + CamelSExpResult *r; - while ((word = camel_index_cursor_next (wc))) { - for (i = 0; i < words->len; i++) { - if (camel_ustrstrcase (word, words->words[i]->word) != NULL) { - /* perf: could have the wc cursor return the name cursor */ - nc = camel_index_find (search->body_index, word); - if (nc) { - while ((name = camel_index_cursor_next (nc))) { - gint mask; + r (printf ("executing get-received-date\n")); - mask = (GPOINTER_TO_INT (g_hash_table_lookup (ht, name))) | (1 << i); - g_hash_table_insert (ht, (gchar *) camel_pstring_peek (name), GINT_TO_POINTER (mask)); - } - g_object_unref (nc); - } - } - } - } - g_object_unref (wc); + /* are we inside a match-all? */ + if (search->current) { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT); - lambdafoo.uids = result; - lambdafoo.count = (1 << words->len) - 1; - g_hash_table_foreach (ht, (GHFunc) htand, &lambdafoo); - g_hash_table_destroy (ht); + r->value.number = camel_message_info_date_received (search->current); + } else { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); } - return result; + return r; } -static gboolean -match_words_1message (CamelDataWrapper *object, - struct _camel_search_words *words, - guint32 *mask, - GCancellable *cancellable) +static CamelSExpResult * +folder_search_get_current_date (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - CamelDataWrapper *containee; - gint truth = FALSE; - gint parts, i; - - if (g_cancellable_is_cancelled (cancellable)) - return FALSE; + CamelSExpResult *r; - containee = camel_medium_get_content (CAMEL_MEDIUM (object)); + r (printf ("executing get-current-date\n")); - if (containee == NULL) - return FALSE; + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT); + r->value.number = time (NULL); + return r; +} - /* using the object types is more accurate than using the mime/types */ - if (CAMEL_IS_MULTIPART (containee)) { - parts = camel_multipart_get_number (CAMEL_MULTIPART (containee)); - for (i = 0; i < parts && truth == FALSE; i++) { - CamelDataWrapper *part = (CamelDataWrapper *) camel_multipart_get_part (CAMEL_MULTIPART (containee), i); - if (part) - truth = match_words_1message (part, words, mask, cancellable); - } - } else if (CAMEL_IS_MIME_MESSAGE (containee)) { - /* for messages we only look at its contents */ - truth = match_words_1message ((CamelDataWrapper *) containee, words, mask, cancellable); - } else if (camel_content_type_is (CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")) { - /* for all other text parts, we look inside, otherwise we dont care */ - CamelStream *stream; - GByteArray *byte_array; +static CamelSExpResult * +folder_search_get_relative_months (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) +{ + CamelSExpResult *r; - byte_array = g_byte_array_new (); - stream = camel_stream_mem_new_with_byte_array (byte_array); + r (printf ("executing get-relative-months\n")); - /* FIXME The match should be part of a stream op */ - camel_data_wrapper_decode_to_stream_sync ( - containee, stream, cancellable, NULL); - camel_stream_write (stream, "", 1, NULL, NULL); - for (i = 0; i < words->len; i++) { - /* FIXME: This is horridly slow, and should use a real search algorithm */ - if (camel_ustrstrcase ((const gchar *) byte_array->data, words->words[i]->word) != NULL) { - *mask |= (1 << i); - /* shortcut a match */ - if (*mask == (1 << (words->len)) - 1) - return TRUE; - } - } + if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_INT) { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + r->value.boolean = FALSE; - g_object_unref (stream); + g_debug ("%s: Expecting 1 argument, an integer, but got %d arguments", G_STRFUNC, argc); + } else { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT); + r->value.number = camel_folder_search_util_add_months (time (NULL), argv[0]->value.number); } - return truth; + return r; } -static gboolean -match_words_message (CamelFolder *folder, - const gchar *uid, - struct _camel_search_words *words, - GCancellable *cancellable, - GError **error) +static CamelSExpResult * +folder_search_get_size (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - guint32 mask; - CamelMimeMessage *msg; - gint truth = FALSE; + CamelSExpResult *r; - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - return truth; + r (printf ("executing get-size\n")); - msg = camel_folder_get_message_sync (folder, uid, cancellable, error); - if (msg) { - mask = 0; - truth = match_words_1message ((CamelDataWrapper *) msg, words, &mask, cancellable); - g_object_unref (msg); + /* are we inside a match-all? */ + if (search->current) { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT); + r->value.number = camel_message_info_size (search->current) / 1024; + } else { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); } - return truth; + return r; } -static GPtrArray * -match_words_messages (CamelFolderSearch *search, - struct _camel_search_words *words, - GCancellable *cancellable, - GError **error) +static CamelSExpResult * +folder_search_uid (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { + CamelSExpResult *r; gint i; - GPtrArray *matches = g_ptr_array_new (); - - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - return matches; - - if (search->body_index) { - GPtrArray *indexed; - struct _camel_search_words *simple; - simple = camel_search_words_simple (words); - indexed = match_words_index (search, simple, cancellable, error); - camel_search_words_free (simple); + r (printf ("executing uid\n")); - for (i = 0; i < indexed->len && !g_cancellable_is_cancelled (cancellable); i++) { - const gchar *uid = g_ptr_array_index (indexed, i); + /* are we inside a match-all? */ + if (search->current) { + gint truth = FALSE; + const gchar *uid = camel_message_info_uid (search->current); - if (match_words_message ( - search->folder, uid, words, - cancellable, error)) - g_ptr_array_add (matches, (gchar *) uid); + /* performs an OR of all words */ + for (i = 0; i < argc && !truth; i++) { + if (argv[i]->type == CAMEL_SEXP_RES_STRING + && !strcmp (uid, argv[i]->value.string)) { + truth = TRUE; + break; + } } - - g_ptr_array_free (indexed, TRUE); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + r->value.boolean = truth; } else { - GPtrArray *v = search->summary_set ? search->summary_set : search->summary; - - for (i = 0; i < v->len && !g_cancellable_is_cancelled (cancellable); i++) { - gchar *uid = g_ptr_array_index (v, i); - - if (match_words_message ( - search->folder, uid, words, - cancellable, error)) - g_ptr_array_add (matches, (gchar *) uid); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); + for (i = 0; i < argc; i++) { + if (argv[i]->type == CAMEL_SEXP_RES_STRING) + g_ptr_array_add (r->value.ptrarray, argv[i]->value.string); } } - return matches; + return r; } static CamelSExpResult * -search_body_contains (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_message_location (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { - gint i, j; - GError **error = search->priv->error; - struct _camel_search_words *words; CamelSExpResult *r; - struct IterData lambdafoo; + gboolean same = FALSE; - if (search->current) { - gint truth = FALSE; + if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) { + if (argv[0]->value.string && search->folder) { + CamelStore *store; + const gchar *name; + const gchar *uid; + gchar *uri; - if (argc == 1 && argv[0]->value.string[0] == 0) { - truth = TRUE; - } else { - for (i = 0; i < argc && !truth && !g_cancellable_is_cancelled (search->priv->cancellable); i++) { - if (argv[i]->type == CAMEL_SEXP_RES_STRING) { - words = camel_search_words_split ((const guchar *) argv[i]->value.string); - truth = TRUE; - if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) { - for (j = 0; j < words->len && truth; j++) - truth = match_message_index (search->body_index, camel_message_info_uid (search->current), words->words[j]->word, error); - } else { - /* TODO: cache current message incase of multiple body search terms */ - truth = match_words_message (search->folder, camel_message_info_uid (search->current), words, search->priv->cancellable, error); - } - camel_search_words_free (words); - } - } + /* FIXME Folder URI formats are Evolution-specific + * knowledge and doesn't belong here! */ + store = camel_folder_get_parent_store (search->folder); + name = camel_folder_get_full_name (search->folder); + uid = camel_service_get_uid (CAMEL_SERVICE (store)); + + uri = g_strdup_printf ("folder://%s/%s", uid, name); + same = g_str_equal (uri, argv[0]->value.string); + g_free (uri); } - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); - r->value.boolean = truth; + } + + if (search->current) { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + r->value.boolean = same ? TRUE : FALSE; } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); r->value.ptrarray = g_ptr_array_new (); - if (argc == 1 && argv[0]->value.string[0] == 0) { + if (same) { + /* all matches */ + gint i; GPtrArray *v = search->summary_set ? search->summary_set : search->summary; - for (i = 0; i < v->len && !g_cancellable_is_cancelled (search->priv->cancellable); i++) { + for (i = 0; i < v->len; i++) { gchar *uid = g_ptr_array_index (v, i); g_ptr_array_add (r->value.ptrarray, uid); } - } else { - GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal); - GPtrArray *matches; - - for (i = 0; i < argc && !g_cancellable_is_cancelled (search->priv->cancellable); i++) { - if (argv[i]->type == CAMEL_SEXP_RES_STRING) { - words = camel_search_words_split ((const guchar *) argv[i]->value.string); - if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) { - matches = match_words_index (search, words, search->priv->cancellable, error); - } else { - matches = match_words_messages (search, words, search->priv->cancellable, error); - } - for (j = 0; j < matches->len; j++) { - g_hash_table_insert (ht, matches->pdata[j], matches->pdata[j]); - } - g_ptr_array_free (matches, TRUE); - camel_search_words_free (words); - } - } - lambdafoo.uids = r->value.ptrarray; - g_hash_table_foreach (ht, (GHFunc) htor, &lambdafoo); - g_hash_table_destroy (ht); } } return r; } +static void +camel_folder_search_class_init (CamelFolderSearchClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (CamelFolderSearchPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = folder_search_dispose; + object_class->finalize = folder_search_finalize; + + class->not_ = folder_search_not; + class->match_all = folder_search_match_all; + class->match_threads = folder_search_match_threads; + class->body_contains = folder_search_body_contains; + class->body_regex = folder_search_body_regex; + class->header_contains = folder_search_header_contains; + class->header_matches = folder_search_header_matches; + class->header_starts_with = folder_search_header_starts_with; + class->header_ends_with = folder_search_header_ends_with; + class->header_exists = folder_search_header_exists; + class->header_soundex = folder_search_header_soundex; + class->header_regex = folder_search_header_regex; + class->header_full_regex = folder_search_header_full_regex; + class->user_tag = folder_search_user_tag; + class->user_flag = folder_search_user_flag; + class->system_flag = folder_search_system_flag; + class->get_sent_date = folder_search_get_sent_date; + class->get_received_date = folder_search_get_received_date; + class->get_current_date = folder_search_get_current_date; + class->get_relative_months = folder_search_get_relative_months; + class->get_size = folder_search_get_size; + class->uid = folder_search_uid; + class->message_location = folder_search_message_location; +} + +static void +camel_folder_search_init (CamelFolderSearch *search) +{ + search->priv = CAMEL_FOLDER_SEARCH_GET_PRIVATE (search); + search->sexp = camel_sexp_new (); +} + +static struct { + const gchar *name; + gint offset; + gint flags; /* 0x02 = immediate, 0x01 = always enter */ +} builtins[] = { + /* these have default implementations in e-sexp */ + { "and", G_STRUCT_OFFSET (CamelFolderSearchClass, and_), 2 }, + { "or", G_STRUCT_OFFSET (CamelFolderSearchClass, or_), 2 }, + /* we need to override this one though to implement an 'array not' */ + { "not", G_STRUCT_OFFSET (CamelFolderSearchClass, not_), 0 }, + { "<", G_STRUCT_OFFSET (CamelFolderSearchClass, lt), 2 }, + { ">", G_STRUCT_OFFSET (CamelFolderSearchClass, gt), 2 }, + { "=", G_STRUCT_OFFSET (CamelFolderSearchClass, eq), 2 }, + + /* these we have to use our own default if there is none */ + /* they should all be defined in the language? so it parses, or should they not?? */ + { "match-all", G_STRUCT_OFFSET (CamelFolderSearchClass, match_all), 3 }, + { "match-threads", G_STRUCT_OFFSET (CamelFolderSearchClass, match_threads), 3 }, + { "body-contains", G_STRUCT_OFFSET (CamelFolderSearchClass, body_contains), 1 }, + { "body-regex", G_STRUCT_OFFSET (CamelFolderSearchClass, body_regex), 1 }, + { "header-contains", G_STRUCT_OFFSET (CamelFolderSearchClass, header_contains), 1 }, + { "header-matches", G_STRUCT_OFFSET (CamelFolderSearchClass, header_matches), 1 }, + { "header-starts-with", G_STRUCT_OFFSET (CamelFolderSearchClass, header_starts_with), 1 }, + { "header-ends-with", G_STRUCT_OFFSET (CamelFolderSearchClass, header_ends_with), 1 }, + { "header-exists", G_STRUCT_OFFSET (CamelFolderSearchClass, header_exists), 1 }, + { "header-soundex", G_STRUCT_OFFSET (CamelFolderSearchClass, header_soundex), 1 }, + { "header-regex", G_STRUCT_OFFSET (CamelFolderSearchClass, header_regex), 1 }, + { "header-full-regex", G_STRUCT_OFFSET (CamelFolderSearchClass, header_full_regex), 1 }, + { "user-tag", G_STRUCT_OFFSET (CamelFolderSearchClass, user_tag), 1 }, + { "user-flag", G_STRUCT_OFFSET (CamelFolderSearchClass, user_flag), 1 }, + { "system-flag", G_STRUCT_OFFSET (CamelFolderSearchClass, system_flag), 1 }, + { "get-sent-date", G_STRUCT_OFFSET (CamelFolderSearchClass, get_sent_date), 1 }, + { "get-received-date", G_STRUCT_OFFSET (CamelFolderSearchClass, get_received_date), 1 }, + { "get-current-date", G_STRUCT_OFFSET (CamelFolderSearchClass, get_current_date), 1 }, + { "get-relative-months", G_STRUCT_OFFSET (CamelFolderSearchClass, get_relative_months), 1 }, + { "get-size", G_STRUCT_OFFSET (CamelFolderSearchClass, get_size), 1 }, + { "uid", G_STRUCT_OFFSET (CamelFolderSearchClass, uid), 1 }, + { "message-location", G_STRUCT_OFFSET (CamelFolderSearchClass, message_location), 1 }, +}; + +/* dummy function, returns false always, or an empty match array */ static CamelSExpResult * -search_body_regex (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +folder_search_dummy (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search) { CamelSExpResult *r; - CamelMimeMessage *msg = get_current_message (search); - if (msg) { - regex_t pattern; + if (search->current == NULL) { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL); + r->value.boolean = FALSE; + } else { + r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR); + r->value.ptrarray = g_ptr_array_new (); + } - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); + return r; +} - if (!g_cancellable_is_cancelled (search->priv->cancellable) && - camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->error) == 0) { - r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) msg, &pattern); - regfree (&pattern); - } else - r->value.boolean = FALSE; +void +camel_folder_search_construct (CamelFolderSearch *search) +{ + gint i; + CamelFolderSearchClass *class; - g_object_unref (msg); - } else { - regex_t pattern; + class = CAMEL_FOLDER_SEARCH_GET_CLASS (search); - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); + for (i = 0; i < G_N_ELEMENTS (builtins); i++) { + gpointer func; + /* c is sure messy sometimes */ + func = *((gpointer *)(((gchar *) class) + builtins[i].offset)); + if (func == NULL && builtins[i].flags&1) { + g_warning ("Search class doesn't implement '%s' method: %s", builtins[i].name, G_OBJECT_TYPE_NAME (search)); + func = (gpointer) folder_search_dummy; + } + if (func != NULL) { + if (builtins[i].flags&2) { + camel_sexp_add_ifunction (search->sexp, 0, builtins[i].name, (CamelSExpIFunc) func, search); + } else { + camel_sexp_add_function (search->sexp, 0, builtins[i].name, (CamelSExpFunc) func, search); + } + } + } +} - if (!g_cancellable_is_cancelled (search->priv->cancellable) && - camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->error) == 0) { - gint i; - GPtrArray *v = search->summary_set ? search->summary_set : search->summary; - CamelMimeMessage *message; +/** + * camel_folder_search_new: + * + * Create a new CamelFolderSearch object. + * + * A CamelFolderSearch is a subclassable, extensible s-exp + * evaluator which enforces a particular set of s-expressions. + * Particular methods may be overriden by an implementation to + * implement a search for any sort of backend. + * + * Returns: A new CamelFolderSearch widget. + **/ +CamelFolderSearch * +camel_folder_search_new (void) +{ + CamelFolderSearch *new; - for (i = 0; i < v->len && !g_cancellable_is_cancelled (search->priv->cancellable); i++) { - gchar *uid = g_ptr_array_index (v, i); + new = g_object_new (CAMEL_TYPE_FOLDER_SEARCH, NULL); + camel_folder_search_construct (new); + + return new; +} + +/** + * camel_folder_search_set_folder: + * @search: + * @folder: A folder. + * + * Set the folder attribute of the search. This is currently unused, but + * could be used to perform a slow-search when indexes and so forth are not + * available. Or for use by subclasses. + **/ +void +camel_folder_search_set_folder (CamelFolderSearch *search, + CamelFolder *folder) +{ + g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search)); + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + + search->folder = folder; +} + +/** + * camel_folder_search_set_summary: + * @search: + * @summary: An array of CamelMessageInfo pointers. + * + * Set the array of summary objects representing the span of the search. + * + * If this is not set, then a subclass must provide the functions + * for searching headers and for the match-all operator. + **/ +void +camel_folder_search_set_summary (CamelFolderSearch *search, + GPtrArray *summary) +{ + g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search)); - message = camel_folder_get_message_sync ( - search->folder, uid, search->priv->cancellable, NULL); - if (message) { - if (camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern)) { - g_ptr_array_add (r->value.ptrarray, uid); - } + search->summary = summary; +} - g_object_unref (message); - } - } +/** + * camel_folder_search_set_body_index: + * @search: + * @body_index: + * + * Set the index representing the contents of all messages + * in this folder. If this is not set, then the folder implementation + * should sub-class the CamelFolderSearch and provide its own + * body-contains function. + **/ +void +camel_folder_search_set_body_index (CamelFolderSearch *search, + CamelIndex *body_index) +{ + g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search)); - regfree (&pattern); - } + if (body_index != NULL) { + g_return_if_fail (CAMEL_IS_INDEX (body_index)); + g_object_ref (body_index); } - return r; + if (search->body_index != NULL) + g_object_unref (search->body_index); + + search->body_index = body_index; } -static CamelSExpResult * -search_user_flag (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +static gboolean +do_search_in_memory (CamelFolder *search_in_folder, + const gchar *expr) { - CamelSExpResult *r; + /* if the expression contains any of these tokens, then perform a memory search, instead of the SQL one */ + const gchar *in_memory_tokens[] = { + "body-contains", + "body-regex", + "match-threads", + "message-location", + "header-soundex", + "header-regex", + "header-full-regex", + "header-contains", + "header-has-words", + NULL }; gint i; - r (printf ("executing user-flag\n")); + if (search_in_folder && + search_in_folder->summary && + (search_in_folder->summary->flags & CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY) != 0) + return TRUE; - /* are we inside a match-all? */ - if (search->current) { - gint truth = FALSE; - /* performs an OR of all words */ - for (i = 0; i < argc && !truth; i++) { - if (argv[i]->type == CAMEL_SEXP_RES_STRING - && camel_message_info_user_flag (search->current, argv[i]->value.string)) { - truth = TRUE; - break; - } - } - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); - r->value.boolean = truth; - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); + if (!expr) + return FALSE; + + for (i = 0; in_memory_tokens[i]; i++) { + if (strstr (expr, in_memory_tokens[i])) + return TRUE; } - return r; + return FALSE; } -static CamelSExpResult * -search_system_flag (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) +/** + * camel_folder_search_count: + * @search: + * @expr: + * @cancellable: a #GCancellable + * @error: return location for a #GError, or %NULL + * + * Run a search. Search must have had Folder already set on it, and + * it must implement summaries. + * + * Returns: Number of messages that match the query. + * + * Since: 2.26 + **/ + +guint32 +camel_folder_search_count (CamelFolderSearch *search, + const gchar *expr, + GCancellable *cancellable, + GError **error) { CamelSExpResult *r; + GPtrArray *summary_set; + gint i; + CamelDB *cdb; + gchar *sql_query, *tmp, *tmp1; + GHashTable *results; + guint32 count = 0; - r (printf ("executing system-flag\n")); + CamelFolderSearchPrivate *p; - if (search->current) { - gboolean truth = FALSE; + g_return_val_if_fail (search != NULL, 0); - if (argc == 1) - truth = camel_system_flag_get (camel_message_info_flags (search->current), argv[0]->value.string); + p = search->priv; - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); - r->value.boolean = truth; - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); - } + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto fail; - return r; -} + if (!expr || !*expr) + expr = "(match-all)"; -static CamelSExpResult * -search_user_tag (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) -{ - const gchar *value = NULL; - CamelSExpResult *r; + g_assert (search->folder); - r (printf ("executing user-tag\n")); + p->cancellable = cancellable; + p->error = error; - if (search->current && argc == 1) - value = camel_message_info_user_tag (search->current, argv[0]->value.string); + /* We route body-contains search and thread based search through memory and not via db. */ + if (do_search_in_memory (search->folder, expr)) { + /* setup our search list only contains those we're interested in */ + search->summary = camel_folder_get_summary (search->folder); + if (search->folder->summary) + camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL); - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING); - r->value.string = g_strdup (value ? value : ""); + summary_set = search->summary; - return r; -} + /* only re-parse if the search has changed */ + if (search->last_search == NULL + || strcmp (search->last_search, expr)) { + camel_sexp_input_text (search->sexp, expr, strlen (expr)); + if (camel_sexp_parse (search->sexp) == -1) { + g_set_error ( + error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, + _("Cannot parse search expression: %s:\n%s"), + camel_sexp_error (search->sexp), expr); + goto fail; + } -static CamelSExpResult * -search_get_sent_date (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *s) -{ - CamelSExpResult *r; + g_free (search->last_search); + search->last_search = g_strdup (expr); + } + r = camel_sexp_eval (search->sexp); + if (r == NULL) { + g_set_error ( + error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, + _("Error executing search expression: %s:\n%s"), + camel_sexp_error (search->sexp), expr); + goto fail; + } - r (printf ("executing get-sent-date\n")); + /* now create a folder summary to return?? */ + if (r->type == CAMEL_SEXP_RES_ARRAY_PTR) { + d (printf ("got result\n")); - /* are we inside a match-all? */ - if (s->current) { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT); + /* reorder result in summary order */ + results = g_hash_table_new (g_str_hash, g_str_equal); + for (i = 0; i < r->value.ptrarray->len; i++) { + d (printf ("adding match: %s\n", (gchar *) g_ptr_array_index (r->value.ptrarray, i))); + g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1)); + } - r->value.number = camel_message_info_date_sent (s->current); - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); - } + for (i = 0; i < summary_set->len; i++) { + gchar *uid = g_ptr_array_index (summary_set, i); + if (g_hash_table_lookup (results, uid)) + count++; + } + g_hash_table_destroy (results); + } - return r; -} + camel_sexp_result_free (search->sexp, r); -static CamelSExpResult * -search_get_received_date (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *s) -{ - CamelSExpResult *r; + } else { + CamelStore *parent_store; + const gchar *full_name; + GError *local_error = NULL; - r (printf ("executing get-received-date\n")); + full_name = camel_folder_get_full_name (search->folder); + parent_store = camel_folder_get_parent_store (search->folder); - /* are we inside a match-all? */ - if (s->current) { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT); + /* Sync the db, so that we search the db for changes */ + camel_folder_summary_save_to_db (search->folder->summary, error); - r->value.number = camel_message_info_date_received (s->current); - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); - } + dd (printf ("sexp is : [%s]\n", expr)); + sql_query = camel_sexp_to_sql_sexp (expr); + tmp1 = camel_db_sqlize_string (full_name); + tmp = g_strdup_printf ("SELECT COUNT (*) FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query ? sql_query:""); + camel_db_free_sqlized_string (tmp1); + g_free (sql_query); + dd (printf ("Equivalent sql %s\n", tmp)); - return r; -} + cdb = (CamelDB *) (parent_store->cdb_r); + camel_db_count_message_info (cdb, tmp, &count, &local_error); + if (local_error != NULL) { + const gchar *message = local_error->message; + if (strncmp (message, "no such table", 13) == 0) { + d (g_warning ("Error during searching %s: %s\n", tmp, message)); + /* Suppress no such table */ + g_clear_error (&local_error); + } + g_propagate_error (error, local_error); + } + g_free (tmp); + } -static CamelSExpResult * -search_get_current_date (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *s) -{ - CamelSExpResult *r; +fail: + /* these might be allocated by match-threads */ + if (p->threads) + camel_folder_thread_messages_unref (p->threads); + if (p->threads_hash) + g_hash_table_destroy (p->threads_hash); + if (search->summary_set) + g_ptr_array_free (search->summary_set, TRUE); + if (search->summary) + camel_folder_free_summary (search->folder, search->summary); - r (printf ("executing get-current-date\n")); + p->cancellable = NULL; + p->error = NULL; + p->threads = NULL; + p->threads_hash = NULL; + search->folder = NULL; + search->summary = NULL; + search->summary_set = NULL; + search->current = NULL; + search->body_index = NULL; - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT); - r->value.number = time (NULL); - return r; + return count; } -static CamelSExpResult * -search_get_relative_months (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *s) +/** + * camel_folder_search_search: + * @search: + * @expr: + * @uids: to search against, NULL for all uid's. + * @cancellable: a #GCancellable + * @error: return location for a #GError, or %NULL + * + * Run a search. Search must have had Folder already set on it, and + * it must implement summaries. + * + * Returns: + **/ +GPtrArray * +camel_folder_search_search (CamelFolderSearch *search, + const gchar *expr, + GPtrArray *uids, + GCancellable *cancellable, + GError **error) { CamelSExpResult *r; + GPtrArray *matches = NULL, *summary_set; + gint i; + CamelDB *cdb; + gchar *sql_query, *tmp, *tmp1; + GHashTable *results; - r (printf ("executing get-relative-months\n")); - - if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_INT) { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); - r->value.boolean = FALSE; + CamelFolderSearchPrivate *p; - g_debug ("%s: Expecting 1 argument, an integer, but got %d arguments", G_STRFUNC, argc); - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT); - r->value.number = camel_folder_search_util_add_months (time (NULL), argv[0]->value.number); - } + g_return_val_if_fail (search != NULL, NULL); - return r; -} + p = search->priv; -static CamelSExpResult * -search_get_size (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *s) -{ - CamelSExpResult *r; + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto fail; - r (printf ("executing get-size\n")); + if (!expr || !*expr) + expr = "(match-all)"; - /* are we inside a match-all? */ - if (s->current) { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT); - r->value.number = camel_message_info_size (s->current) / 1024; - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); - } + g_assert (search->folder); - return r; -} + p->cancellable = cancellable; + p->error = error; -static CamelSExpResult * -search_uid (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) -{ - CamelSExpResult *r; - gint i; + /* We route body-contains / thread based search and uid search through memory and not via db. */ + if (uids || do_search_in_memory (search->folder, expr)) { + /* setup our search list only contains those we're interested in */ + search->summary = camel_folder_get_summary (search->folder); - r (printf ("executing uid\n")); + if (uids) { + GHashTable *uids_hash = g_hash_table_new (g_str_hash, g_str_equal); - /* are we inside a match-all? */ - if (search->current) { - gint truth = FALSE; - const gchar *uid = camel_message_info_uid (search->current); + summary_set = search->summary_set = g_ptr_array_new (); + for (i = 0; i < uids->len; i++) + g_hash_table_insert (uids_hash, uids->pdata[i], uids->pdata[i]); + for (i = 0; i < search->summary->len; i++) + if (g_hash_table_lookup (uids_hash, search->summary->pdata[i])) + g_ptr_array_add (search->summary_set, search->summary->pdata[i]); + g_hash_table_destroy (uids_hash); + } else { + if (search->folder->summary) + camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL); + summary_set = search->summary; + } - /* performs an OR of all words */ - for (i = 0; i < argc && !truth; i++) { - if (argv[i]->type == CAMEL_SEXP_RES_STRING - && !strcmp (uid, argv[i]->value.string)) { - truth = TRUE; - break; + /* only re-parse if the search has changed */ + if (search->last_search == NULL + || strcmp (search->last_search, expr)) { + camel_sexp_input_text (search->sexp, expr, strlen (expr)); + if (camel_sexp_parse (search->sexp) == -1) { + g_set_error ( + error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, + _("Cannot parse search expression: %s:\n%s"), + camel_sexp_error (search->sexp), expr); + goto fail; } + + g_free (search->last_search); + search->last_search = g_strdup (expr); } - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); - r->value.boolean = truth; - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); - for (i = 0; i < argc; i++) { - if (argv[i]->type == CAMEL_SEXP_RES_STRING) - g_ptr_array_add (r->value.ptrarray, argv[i]->value.string); + r = camel_sexp_eval (search->sexp); + if (r == NULL) { + g_set_error ( + error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, + _("Error executing search expression: %s:\n%s"), + camel_sexp_error (search->sexp), expr); + goto fail; } - } - return r; -} + matches = g_ptr_array_new (); -static gint -read_uid_callback (gpointer ref, - gint ncol, - gchar **cols, - gchar **name) -{ - GPtrArray *matches; + /* now create a folder summary to return?? */ + if (r->type == CAMEL_SEXP_RES_ARRAY_PTR) { + d (printf ("got result\n")); - matches = (GPtrArray *) ref; + /* reorder result in summary order */ + results = g_hash_table_new (g_str_hash, g_str_equal); + for (i = 0; i < r->value.ptrarray->len; i++) { + d (printf ("adding match: %s\n", (gchar *) g_ptr_array_index (r->value.ptrarray, i))); + g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1)); + } - g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (cols[0])); - return 0; -} + for (i = 0; i < summary_set->len; i++) { + gchar *uid = g_ptr_array_index (summary_set, i); + if (g_hash_table_lookup (results, uid)) + g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (uid)); + } + g_hash_table_destroy (results); + } -static CamelSExpResult * -search_message_location (struct _CamelSExp *f, - gint argc, - struct _CamelSExpResult **argv, - CamelFolderSearch *search) -{ - CamelSExpResult *r; - gboolean same = FALSE; + camel_sexp_result_free (search->sexp, r); - if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) { - if (argv[0]->value.string && search->folder) { - CamelStore *store; - const gchar *name; - const gchar *uid; - gchar *uri; + } else { + CamelStore *parent_store; + const gchar *full_name; + GError *local_error = NULL; - /* FIXME Folder URI formats are Evolution-specific - * knowledge and doesn't belong here! */ - store = camel_folder_get_parent_store (search->folder); - name = camel_folder_get_full_name (search->folder); - uid = camel_service_get_uid (CAMEL_SERVICE (store)); + full_name = camel_folder_get_full_name (search->folder); + parent_store = camel_folder_get_parent_store (search->folder); - uri = g_strdup_printf ("folder://%s/%s", uid, name); - same = g_str_equal (uri, argv[0]->value.string); - g_free (uri); + /* Sync the db, so that we search the db for changes */ + camel_folder_summary_save_to_db (search->folder->summary, error); + + dd (printf ("sexp is : [%s]\n", expr)); + sql_query = camel_sexp_to_sql_sexp (expr); + tmp1 = camel_db_sqlize_string (full_name); + tmp = g_strdup_printf ("SELECT uid FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query ? sql_query:""); + camel_db_free_sqlized_string (tmp1); + g_free (sql_query); + dd (printf ("Equivalent sql %s\n", tmp)); + + matches = g_ptr_array_new (); + cdb = (CamelDB *) (parent_store->cdb_r); + camel_db_select ( + cdb, tmp, (CamelDBSelectCB) + read_uid_callback, matches, &local_error); + if (local_error != NULL) { + const gchar *message = local_error->message; + if (strncmp (message, "no such table", 13) == 0) { + d (g_warning ("Error during searching %s: %s\n", tmp, message)); + /* Suppress no such table */ + g_clear_error (&local_error); + } else + g_propagate_error (error, local_error); } - } + g_free (tmp); - if (search->current) { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL); - r->value.boolean = same ? TRUE : FALSE; - } else { - r = camel_sexp_result_new (f, CAMEL_SEXP_RES_ARRAY_PTR); - r->value.ptrarray = g_ptr_array_new (); + } - if (same) { - /* all matches */ - gint i; - GPtrArray *v = search->summary_set ? search->summary_set : search->summary; +fail: + /* these might be allocated by match-threads */ + if (p->threads) + camel_folder_thread_messages_unref (p->threads); + if (p->threads_hash) + g_hash_table_destroy (p->threads_hash); + if (search->summary_set) + g_ptr_array_free (search->summary_set, TRUE); + if (search->summary) + camel_folder_free_summary (search->folder, search->summary); - for (i = 0; i < v->len; i++) { - gchar *uid = g_ptr_array_index (v, i); + p->cancellable = NULL; + p->error = NULL; + p->threads = NULL; + p->threads_hash = NULL; + search->folder = NULL; + search->summary = NULL; + search->summary_set = NULL; + search->current = NULL; + search->body_index = NULL; - g_ptr_array_add (r->value.ptrarray, uid); - } - } + if (error && *error) { + camel_folder_search_free_result (search, matches); + matches = NULL; } - return r; + return matches; +} + +void +camel_folder_search_free_result (CamelFolderSearch *search, + GPtrArray *result) +{ + if (!result) + return; + + g_ptr_array_foreach (result, (GFunc) camel_pstring_free, NULL); + g_ptr_array_free (result, TRUE); } /** diff --git a/camel/camel-folder-search.h b/camel/camel-folder-search.h index 55d0822..1116ca7 100644 --- a/camel/camel-folder-search.h +++ b/camel/camel-folder-search.h @@ -74,97 +74,230 @@ struct _CamelFolderSearch { struct _CamelFolderSearchClass { CamelObjectClass parent_class; - /* general bool/comparison options, usually these wont need to be set, unless it is compiling into another language */ - CamelSExpResult * (*and_)(CamelSExp *f, gint argc, CamelSExpTerm **argv, CamelFolderSearch *s); - CamelSExpResult * (*or_)(CamelSExp *f, gint argc, CamelSExpTerm **argv, CamelFolderSearch *s); - CamelSExpResult * (*not_)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - CamelSExpResult * (*lt)(CamelSExp *f, gint argc, CamelSExpTerm **argv, CamelFolderSearch *s); - CamelSExpResult * (*gt)(CamelSExp *f, gint argc, CamelSExpTerm **argv, CamelFolderSearch *s); - CamelSExpResult * (*eq)(CamelSExp *f, gint argc, CamelSExpTerm **argv, CamelFolderSearch *s); - - /* search options */ - /* (match-all [boolean expression]) Apply match to all messages */ - CamelSExpResult * (*match_all)(CamelSExp *f, gint argc, CamelSExpTerm **argv, CamelFolderSearch *s); - - /* (match-threads "type" [array expression]) add all related threads */ - CamelSExpResult * (*match_threads)(CamelSExp *f, gint argc, CamelSExpTerm **argv, CamelFolderSearch *s); - - /* (body-contains "string1" "string2" ...) Returns a list of matches, or true if in single-message mode */ - CamelSExpResult * (*body_contains)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (body-regex "regex") Returns a list of matches, or true if in single-message mode */ - CamelSExpResult * (*body_regex)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (header-contains "headername" "string1" ...) List of matches, or true if in single-message mode */ - CamelSExpResult * (*header_contains)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); + /* General bool/comparison options. Usually these won't need + * to be set, unless it is compiling into another language. */ + CamelSExpResult * (*and_) (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search); + CamelSExpResult * (*or_) (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search); + CamelSExpResult * (*not_) (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + CamelSExpResult * (*lt) (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search); + CamelSExpResult * (*gt) (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search); + CamelSExpResult * (*eq) (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search); + + /* Search Options */ + + /* (match-all [boolean expression]) + * Apply match to all messages. */ + CamelSExpResult * (*match_all) (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search); + + /* (match-threads "type" [array expression]) + * Add all related threads. */ + CamelSExpResult * (*match_threads) + (CamelSExp *sexp, + gint argc, + CamelSExpTerm **argv, + CamelFolderSearch *search); + + /* (body-contains "string1" "string2" ...) + * Returns a list of matches, or true if in single-message mode. */ + CamelSExpResult * (*body_contains) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (body-regex "regex") + * Returns a list of matches, or true if in single-message mode. */ + CamelSExpResult * (*body_regex) (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (header-contains "headername" "string1" ...) + * List of matches, or true if in single-message mode. */ + CamelSExpResult * (*header_contains) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); /* (header-matches "headername" "string") */ - CamelSExpResult * (*header_matches)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); + CamelSExpResult * (*header_matches) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); /* (header-starts-with "headername" "string") */ - CamelSExpResult * (*header_starts_with)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); + CamelSExpResult * (*header_starts_with) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); /* (header-ends-with "headername" "string") */ - CamelSExpResult * (*header_ends_with)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); + CamelSExpResult * (*header_ends_with) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); /* (header-exists "headername") */ - CamelSExpResult * (*header_exists)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); + CamelSExpResult * (*header_exists) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); /* (header-soundex "headername" "string") */ - CamelSExpResult * (*header_soundex)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); + CamelSExpResult * (*header_soundex) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); /* (header-regex "headername" "regex_string") */ - CamelSExpResult * (*header_regex)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); + CamelSExpResult * (*header_regex) (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); /* (header-full-regex "regex") */ - CamelSExpResult * (*header_full_regex)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (user-flag "flagname" "flagname" ...) If one of user-flag set */ - CamelSExpResult * (*user_flag)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (user-tag "flagname") Returns the value of a user tag. Can only be used in match-all */ - CamelSExpResult * (*user_tag)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (system-flag "flagname") Returns the value of a system flag. Can only be used in match-all */ - CamelSExpResult * (*system_flag)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (get-sent-date) Retrieve the date that the message was sent on as a time_t */ - CamelSExpResult * (*get_sent_date)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (get-received-date) Retrieve the date that the message was received on as a time_t */ - CamelSExpResult * (*get_received_date)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (get-current-date) Retrieve 'now' as a time_t */ - CamelSExpResult * (*get_current_date)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (get-relative-months) Retrieve relative seconds from 'now' and specified number of months as a time_t */ - CamelSExpResult * (*get_relative_months)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (get-size) Retrieve message size as an gint (in kilobytes) */ - CamelSExpResult * (*get_size)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (uid "uid" ...) True if the uid is in the list */ - CamelSExpResult * (*uid)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - - /* (message-location "folder_string") True if the message is in the folder's full name "folder_string" */ - CamelSExpResult * (*message_location)(CamelSExp *f, gint argc, CamelSExpResult **argv, CamelFolderSearch *s); - + CamelSExpResult * (*header_full_regex) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (user-flag "flagname" "flagname" ...) + * If one of user-flag set. */ + CamelSExpResult * (*user_flag) (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (user-tag "flagname") + * Returns the value of a user tag. Can only be used in match-all. */ + CamelSExpResult * (*user_tag) (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (system-flag "flagname") + * Returns the value of a system flag. + * Can only be used in match-all. */ + CamelSExpResult * (*system_flag) (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (get-sent-date) + * Retrieve the date that the message was sent on as a time_t. */ + CamelSExpResult * (*get_sent_date) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (get-received-date) + * Retrieve the date that the message was received on as a time_t. */ + CamelSExpResult * (*get_received_date) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (get-current-date) + * Retrieve 'now' as a time_t. */ + CamelSExpResult * (*get_current_date) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (get-relative-months) + * Retrieve relative seconds from 'now' and + * specified number of months as a time_t. */ + CamelSExpResult * (*get_relative_months) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (get-size) + * Retrieve message size as an gint (in kilobytes). */ + CamelSExpResult * (*get_size) (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (uid "uid" ...) + * True if the uid is in the list. */ + CamelSExpResult * (*uid) (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); + + /* (message-location "folder_string") + * True if the message is in the folder's full name "folder_string". */ + CamelSExpResult * (*message_location) + (CamelSExp *sexp, + gint argc, + CamelSExpResult **argv, + CamelFolderSearch *search); }; -GType camel_folder_search_get_type (void); -CamelFolderSearch *camel_folder_search_new (void); -void camel_folder_search_construct (CamelFolderSearch *search); - -/* This stuff currently gets cleared when you run a search ... what on earth was i thinking ... */ -void camel_folder_search_set_folder (CamelFolderSearch *search, CamelFolder *folder); -void camel_folder_search_set_summary (CamelFolderSearch *search, GPtrArray *summary); -void camel_folder_search_set_body_index (CamelFolderSearch *search, CamelIndex *body_index); - -GPtrArray *camel_folder_search_search (CamelFolderSearch *search, const gchar *expr, GPtrArray *uids, GCancellable *cancellable, GError **error); -guint32 camel_folder_search_count (CamelFolderSearch *search, const gchar *expr, GCancellable *cancellable, GError **error); -void camel_folder_search_free_result (CamelFolderSearch *search, GPtrArray *); - -time_t camel_folder_search_util_add_months (time_t t, gint months); +GType camel_folder_search_get_type (void) G_GNUC_CONST; +CamelFolderSearch * + camel_folder_search_new (void); +void camel_folder_search_construct (CamelFolderSearch *search); + +/* XXX This stuff currently gets cleared when you run a search. + * What on earth was i thinking ... */ +void camel_folder_search_set_folder (CamelFolderSearch *search, + CamelFolder *folder); +void camel_folder_search_set_summary (CamelFolderSearch *search, + GPtrArray *summary); +void camel_folder_search_set_body_index + (CamelFolderSearch *search, + CamelIndex *body_index); + +GPtrArray * camel_folder_search_search (CamelFolderSearch *search, + const gchar *expr, + GPtrArray *uids, + GCancellable *cancellable, + GError **error); +guint32 camel_folder_search_count (CamelFolderSearch *search, + const gchar *expr, + GCancellable *cancellable, + GError **error); +void camel_folder_search_free_result (CamelFolderSearch *search, + GPtrArray *); + +/* XXX This belongs in a general utility file. */ +time_t camel_folder_search_util_add_months + (time_t t, + gint months); G_END_DECLS -- 2.7.4