Coding style cleanup.
[platform/upstream/evolution-data-server.git] / camel / camel-folder-search.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  *  Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /* This is a helper class for folders to implement the search function.
23    It implements enough to do basic searches on folders that can provide
24    an in-memory summary and a body index. */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 /* POSIX requires <sys/types.h> be included before <regex.h> */
31 #include <sys/types.h>
32
33 #include <ctype.h>
34 #include <regex.h>
35 #include <stdio.h>
36 #include <string.h>
37
38 #include <glib/gi18n-lib.h>
39
40 #include "camel-folder-search.h"
41 #include "camel-folder-thread.h"
42 #include "camel-iconv.h"
43 #include "camel-medium.h"
44 #include "camel-mime-message.h"
45 #include "camel-multipart.h"
46 #include "camel-search-private.h"
47 #include "camel-stream-mem.h"
48 #include "camel-db.h"
49 #include "camel-debug.h"
50 #include "camel-store.h"
51 #include "camel-vee-folder.h"
52 #include "camel-string-utils.h"
53 #include "camel-search-sql.h"
54 #include "camel-search-sql-sexp.h"
55
56 #define d(x)
57 #define r(x)
58 #define dd(x) if (camel_debug("search")) x
59
60 struct _CamelFolderSearchPrivate {
61         GError **error;
62
63         CamelFolderThread *threads;
64         GHashTable *threads_hash;
65 };
66
67 static ESExpResult *search_not (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
68
69 static ESExpResult *search_header_contains (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
70 static ESExpResult *search_header_matches (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
71 static ESExpResult *search_header_starts_with (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
72 static ESExpResult *search_header_ends_with (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
73 static ESExpResult *search_header_exists (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
74 static ESExpResult *search_header_soundex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
75 static ESExpResult *search_header_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
76 static ESExpResult *search_header_full_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
77 static ESExpResult *search_match_all (struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search);
78 static ESExpResult *search_match_threads (struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *s);
79 static ESExpResult *search_body_contains (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
80 static ESExpResult *search_body_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
81 static ESExpResult *search_user_flag (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
82 static ESExpResult *search_user_tag (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
83 static ESExpResult *search_system_flag (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
84 static ESExpResult *search_get_sent_date (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
85 static ESExpResult *search_get_received_date (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
86 static ESExpResult *search_get_current_date (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
87 static ESExpResult *search_get_size (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
88 static ESExpResult *search_uid (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
89 static ESExpResult *search_message_location (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
90
91 static ESExpResult *search_dummy (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
92
93 static gint read_uid_callback (gpointer  ref, gint ncol, gchar ** cols, gchar **name);
94
95 G_DEFINE_TYPE (CamelFolderSearch, camel_folder_search, CAMEL_TYPE_OBJECT)
96
97 static void
98 folder_search_dispose (GObject *object)
99 {
100         CamelFolderSearch *search = CAMEL_FOLDER_SEARCH (object);
101
102         if (search->sexp != NULL) {
103                 e_sexp_unref (search->sexp);
104                 search->sexp = NULL;
105         }
106
107         /* Chain up to parent's dispose() method. */
108         G_OBJECT_CLASS (camel_folder_search_parent_class)->dispose (object);
109 }
110
111 static void
112 folder_search_finalize (GObject *object)
113 {
114         CamelFolderSearch *search = CAMEL_FOLDER_SEARCH (object);
115
116         g_free (search->last_search);
117
118         /* Chain up to parent's finalize() method. */
119         G_OBJECT_CLASS (camel_folder_search_parent_class)->finalize (object);
120 }
121
122 static void
123 camel_folder_search_class_init (CamelFolderSearchClass *class)
124 {
125         GObjectClass *object_class;
126
127         g_type_class_add_private (class, sizeof (CamelFolderSearchPrivate));
128
129         object_class = G_OBJECT_CLASS (class);
130         object_class->dispose = folder_search_dispose;
131         object_class->finalize = folder_search_finalize;
132
133         class->not = search_not;
134         class->match_all = search_match_all;
135         class->match_threads = search_match_threads;
136         class->body_contains = search_body_contains;
137         class->body_regex = search_body_regex;
138         class->header_contains = search_header_contains;
139         class->header_matches = search_header_matches;
140         class->header_starts_with = search_header_starts_with;
141         class->header_ends_with = search_header_ends_with;
142         class->header_exists = search_header_exists;
143         class->header_soundex = search_header_soundex;
144         class->header_regex = search_header_regex;
145         class->header_full_regex = search_header_full_regex;
146         class->user_tag = search_user_tag;
147         class->user_flag = search_user_flag;
148         class->system_flag = search_system_flag;
149         class->get_sent_date = search_get_sent_date;
150         class->get_received_date = search_get_received_date;
151         class->get_current_date = search_get_current_date;
152         class->get_size = search_get_size;
153         class->uid = search_uid;
154         class->message_location = search_message_location;
155 }
156
157 static void
158 camel_folder_search_init (CamelFolderSearch *search)
159 {
160         search->priv = G_TYPE_INSTANCE_GET_PRIVATE (
161                 search, CAMEL_TYPE_FOLDER_SEARCH, CamelFolderSearchPrivate);
162         search->sexp = e_sexp_new ();
163 }
164
165 static struct {
166         const gchar *name;
167         gint offset;
168         gint flags;             /* 0x02 = immediate, 0x01 = always enter */
169 } builtins[] = {
170         /* these have default implementations in e-sexp */
171         { "and", G_STRUCT_OFFSET(CamelFolderSearchClass, and), 2 },
172         { "or", G_STRUCT_OFFSET(CamelFolderSearchClass, or), 2 },
173         /* we need to override this one though to implement an 'array not' */
174         { "not", G_STRUCT_OFFSET(CamelFolderSearchClass, not), 0 },
175         { "<", G_STRUCT_OFFSET(CamelFolderSearchClass, lt), 2 },
176         { ">", G_STRUCT_OFFSET(CamelFolderSearchClass, gt), 2 },
177         { "=", G_STRUCT_OFFSET(CamelFolderSearchClass, eq), 2 },
178
179         /* these we have to use our own default if there is none */
180         /* they should all be defined in the language? so it parses, or should they not?? */
181         { "match-all", G_STRUCT_OFFSET(CamelFolderSearchClass, match_all), 3 },
182         { "match-threads", G_STRUCT_OFFSET(CamelFolderSearchClass, match_threads), 3 },
183         { "body-contains", G_STRUCT_OFFSET(CamelFolderSearchClass, body_contains), 1 },
184         { "body-regex",  G_STRUCT_OFFSET(CamelFolderSearchClass, body_regex), 1  },
185         { "header-contains", G_STRUCT_OFFSET(CamelFolderSearchClass, header_contains), 1 },
186         { "header-matches", G_STRUCT_OFFSET(CamelFolderSearchClass, header_matches), 1 },
187         { "header-starts-with", G_STRUCT_OFFSET(CamelFolderSearchClass, header_starts_with), 1 },
188         { "header-ends-with", G_STRUCT_OFFSET(CamelFolderSearchClass, header_ends_with), 1 },
189         { "header-exists", G_STRUCT_OFFSET(CamelFolderSearchClass, header_exists), 1 },
190         { "header-soundex", G_STRUCT_OFFSET(CamelFolderSearchClass, header_soundex), 1 },
191         { "header-regex", G_STRUCT_OFFSET(CamelFolderSearchClass, header_regex), 1 },
192         { "header-full-regex", G_STRUCT_OFFSET(CamelFolderSearchClass, header_full_regex), 1 },
193         { "user-tag", G_STRUCT_OFFSET(CamelFolderSearchClass, user_tag), 1 },
194         { "user-flag", G_STRUCT_OFFSET(CamelFolderSearchClass, user_flag), 1 },
195         { "system-flag", G_STRUCT_OFFSET(CamelFolderSearchClass, system_flag), 1 },
196         { "get-sent-date", G_STRUCT_OFFSET(CamelFolderSearchClass, get_sent_date), 1 },
197         { "get-received-date", G_STRUCT_OFFSET(CamelFolderSearchClass, get_received_date), 1 },
198         { "get-current-date", G_STRUCT_OFFSET(CamelFolderSearchClass, get_current_date), 1 },
199         { "get-size", G_STRUCT_OFFSET(CamelFolderSearchClass, get_size), 1 },
200         { "uid", G_STRUCT_OFFSET(CamelFolderSearchClass, uid), 1 },
201         { "message-location", G_STRUCT_OFFSET(CamelFolderSearchClass, message_location), 1 },
202 };
203
204 void
205 camel_folder_search_construct (CamelFolderSearch *search)
206 {
207         gint i;
208         CamelFolderSearchClass *class;
209
210         class = CAMEL_FOLDER_SEARCH_GET_CLASS (search);
211
212         for (i = 0; i < G_N_ELEMENTS (builtins); i++) {
213                 gpointer func;
214                 /* c is sure messy sometimes */
215                 func = *((gpointer *)(((gchar *)class)+builtins[i].offset));
216                 if (func == NULL && builtins[i].flags&1) {
217                         g_warning("Search class doesn't implement '%s' method: %s", builtins[i].name, G_OBJECT_TYPE_NAME (search));
218                         func = (gpointer)search_dummy;
219                 }
220                 if (func != NULL) {
221                         if (builtins[i].flags&2) {
222                                 e_sexp_add_ifunction (search->sexp, 0, builtins[i].name, (ESExpIFunc *)func, search);
223                         } else {
224                                 e_sexp_add_function (search->sexp, 0, builtins[i].name, (ESExpFunc *)func, search);
225                         }
226                 }
227         }
228 }
229
230 /**
231  * camel_folder_search_new:
232  *
233  * Create a new CamelFolderSearch object.
234  *
235  * A CamelFolderSearch is a subclassable, extensible s-exp
236  * evaluator which enforces a particular set of s-expressions.
237  * Particular methods may be overriden by an implementation to
238  * implement a search for any sort of backend.
239  *
240  * Returns: A new CamelFolderSearch widget.
241  **/
242 CamelFolderSearch *
243 camel_folder_search_new (void)
244 {
245         CamelFolderSearch *new;
246
247         new = g_object_new (CAMEL_TYPE_FOLDER_SEARCH, NULL);
248         camel_folder_search_construct (new);
249
250         return new;
251 }
252
253 /**
254  * camel_folder_search_set_folder:
255  * @search:
256  * @folder: A folder.
257  *
258  * Set the folder attribute of the search.  This is currently unused, but
259  * could be used to perform a slow-search when indexes and so forth are not
260  * available.  Or for use by subclasses.
261  **/
262 void
263 camel_folder_search_set_folder (CamelFolderSearch *search, CamelFolder *folder)
264 {
265         search->folder = folder;
266 }
267
268 /**
269  * camel_folder_search_set_summary:
270  * @search:
271  * @summary: An array of CamelMessageInfo pointers.
272  *
273  * Set the array of summary objects representing the span of the search.
274  *
275  * If this is not set, then a subclass must provide the functions
276  * for searching headers and for the match-all operator.
277  **/
278 void
279 camel_folder_search_set_summary (CamelFolderSearch *search, GPtrArray *summary)
280 {
281         search->summary = summary;
282 }
283
284 /**
285  * camel_folder_search_set_body_index:
286  * @search:
287  * @index:
288  *
289  * Set the index representing the contents of all messages
290  * in this folder.  If this is not set, then the folder implementation
291  * should sub-class the CamelFolderSearch and provide its own
292  * body-contains function.
293  **/
294 void
295 camel_folder_search_set_body_index (CamelFolderSearch *search, CamelIndex *index)
296 {
297         if (search->body_index)
298                 g_object_unref (search->body_index);
299         search->body_index = index;
300         if (index)
301                 g_object_ref (index);
302 }
303
304 /**
305  * camel_folder_search_execute_expression:
306  * @search:
307  * @expr:
308  * @error: return location for a #GError, or %NULL
309  *
310  * Execute the search expression @expr, returning an array of
311  * all matches as a GPtrArray of uid's of matching messages.
312  *
313  * Note that any settings such as set_body_index(), set_folder(),
314  * and so on are reset to #NULL once the search has completed.
315  *
316  * TODO: The interface should probably return summary items instead
317  * (since they are much more useful to any client).
318  *
319  * Returns: A GPtrArray of strings of all matching messages.
320  * This must only be freed by camel_folder_search_free_result.
321  **/
322 GPtrArray *
323 camel_folder_search_execute_expression (CamelFolderSearch *search,
324                                         const gchar *expr,
325                                         GError **error)
326 {
327         ESExpResult *r;
328         GPtrArray *matches;
329         gint i;
330         GHashTable *results;
331         CamelFolderSearchPrivate *p = search->priv;
332
333         p->error = error;
334
335         /* only re-parse if the search has changed */
336         if (search->last_search == NULL
337             || strcmp (search->last_search, expr)) {
338                 e_sexp_input_text (search->sexp, expr, strlen (expr));
339                 if (e_sexp_parse (search->sexp) == -1) {
340                         g_set_error (
341                                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
342                                 _("Cannot parse search expression: %s:\n%s"),
343                                 e_sexp_error (search->sexp), expr);
344                         return NULL;
345                 }
346
347                 g_free (search->last_search);
348                 search->last_search = g_strdup (expr);
349         }
350         r = e_sexp_eval (search->sexp);
351         if (r == NULL) {
352                 g_set_error (
353                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
354                         _("Error executing search expression: %s:\n%s"),
355                         e_sexp_error (search->sexp), expr);
356                 return NULL;
357         }
358
359         matches = g_ptr_array_new ();
360
361         /* now create a folder summary to return?? */
362         if (r->type == ESEXP_RES_ARRAY_PTR) {
363                 d(printf("got result ...\n"));
364                 if (search->summary) {
365                         /* reorder result in summary order */
366                         results = g_hash_table_new (g_str_hash, g_str_equal);
367                         for (i=0;i<r->value.ptrarray->len;i++) {
368                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
369                                 g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1));
370                         }
371                         for (i=0;i<search->summary->len;i++) {
372                                 gchar *uid = g_ptr_array_index (search->summary, i);
373                                 if (g_hash_table_lookup (results, uid)) {
374                                         g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (uid));
375                                 }
376                         }
377                         g_hash_table_destroy (results);
378                 } else {
379                         for (i=0;i<r->value.ptrarray->len;i++) {
380                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
381                                 g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (g_ptr_array_index (r->value.ptrarray, i)));
382                         }
383                 }
384         } else {
385                 g_warning("Search returned an invalid result type");
386         }
387
388         e_sexp_result_free (search->sexp, r);
389
390         if (p->threads)
391                 camel_folder_thread_messages_unref (p->threads);
392         if (p->threads_hash)
393                 g_hash_table_destroy (p->threads_hash);
394
395         p->threads = NULL;
396         p->threads_hash = NULL;
397         search->folder = NULL;
398         search->summary = NULL;
399         search->current = NULL;
400         search->body_index = NULL;
401
402         return matches;
403 }
404
405 /**
406  * camel_folder_search_count:
407  * @search:
408  * @expr:
409  * @uids: to search against, NULL for all uid's.
410  * @error: return location for a #GError, or %NULL
411  *
412  * Run a search.  Search must have had Folder already set on it, and
413  * it must implement summaries.
414  *
415  * Returns: Number of messages that match the query.
416  *
417  * Since: 2.26
418  **/
419
420 guint32
421 camel_folder_search_count (CamelFolderSearch *search,
422                            const gchar *expr,
423                            GError **error)
424 {
425         ESExpResult *r;
426         GPtrArray *summary_set;
427         gint i;
428         CamelDB *cdb;
429         gchar *sql_query, *tmp, *tmp1;
430         GHashTable *results;
431         guint32 count = 0;
432
433         CamelFolderSearchPrivate *p = search->priv;
434
435         g_assert (search->folder);
436
437         p->error = error;
438
439         /* We route body-contains search and thread based search through memory and not via db. */
440         if (strstr((const gchar *) expr, "body-contains") || strstr((const gchar *) expr, "match-threads")) {
441                 /* setup our search list only contains those we're interested in */
442                 search->summary = camel_folder_get_summary (search->folder);
443
444                 summary_set = search->summary;
445
446                 /* only re-parse if the search has changed */
447                 if (search->last_search == NULL
448                     || strcmp (search->last_search, expr)) {
449                         e_sexp_input_text (search->sexp, expr, strlen (expr));
450                         if (e_sexp_parse (search->sexp) == -1) {
451                                 g_set_error (
452                                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
453                                         _("Cannot parse search expression: %s:\n%s"),
454                                         e_sexp_error (search->sexp), expr);
455                                 goto fail;
456                         }
457
458                         g_free (search->last_search);
459                         search->last_search = g_strdup (expr);
460                 }
461                 r = e_sexp_eval (search->sexp);
462                 if (r == NULL) {
463                         g_set_error (
464                                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
465                                 _("Error executing search expression: %s:\n%s"),
466                                 e_sexp_error (search->sexp), expr);
467                         goto fail;
468                 }
469
470                 /* now create a folder summary to return?? */
471                 if (r->type == ESEXP_RES_ARRAY_PTR) {
472                         d(printf("got result\n"));
473
474                         /* reorder result in summary order */
475                         results = g_hash_table_new (g_str_hash, g_str_equal);
476                         for (i=0;i<r->value.ptrarray->len;i++) {
477                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
478                                 g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1));
479                         }
480
481                         for (i=0;i<summary_set->len;i++) {
482                                 gchar *uid = g_ptr_array_index (summary_set, i);
483                                 if (g_hash_table_lookup (results, uid))
484                                         count++;
485                         }
486                         g_hash_table_destroy (results);
487                 }
488
489                 e_sexp_result_free (search->sexp, r);
490
491         } else {
492                 CamelStore *parent_store;
493                 const gchar *full_name;
494                 GError *local_error = NULL;
495
496                 full_name = camel_folder_get_full_name (search->folder);
497                 parent_store = camel_folder_get_parent_store (search->folder);
498
499                 /* Sync the db, so that we search the db for changes */
500                 camel_folder_summary_save_to_db (search->folder->summary, error);
501
502                 dd(printf ("sexp is : [%s]\n", expr));
503                 if (g_getenv("SQL_SEARCH_OLD"))
504                         sql_query = camel_sexp_to_sql (expr);
505                 else
506                         sql_query = camel_sexp_to_sql_sexp (expr);
507                 tmp1 = camel_db_sqlize_string (full_name);
508                 tmp = g_strdup_printf ("SELECT COUNT (*) FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query?sql_query:"");
509                 camel_db_free_sqlized_string (tmp1);
510                 g_free (sql_query);
511                 dd(printf("Equivalent sql %s\n", tmp));
512
513                 cdb = (CamelDB *) (parent_store->cdb_r);
514                 camel_db_count_message_info  (cdb, tmp, &count, &local_error);
515                 if (local_error != NULL) {
516                         const gchar *message = local_error->message;
517                         if (strncmp(message, "no such table", 13) == 0) {
518                                 d(g_warning ("Error during searching %s: %s\n", tmp, message));
519                                 /* Suppress no such table */
520                                 g_clear_error (&local_error);
521                         }
522                         g_propagate_error (error, local_error);
523                 }
524                 g_free (tmp);
525         }
526
527 fail:
528         /* these might be allocated by match-threads */
529         if (p->threads)
530                 camel_folder_thread_messages_unref (p->threads);
531         if (p->threads_hash)
532                 g_hash_table_destroy (p->threads_hash);
533         if (search->summary_set)
534                 g_ptr_array_free (search->summary_set, TRUE);
535         if (search->summary)
536                 camel_folder_free_summary (search->folder, search->summary);
537
538         p->threads = NULL;
539         p->threads_hash = NULL;
540         search->folder = NULL;
541         search->summary = NULL;
542         search->summary_set = NULL;
543         search->current = NULL;
544         search->body_index = NULL;
545
546         return count;
547 }
548
549 static gboolean
550 do_search_in_memory (const gchar *expr)
551 {
552         /* if the expression contains any of these tokens, then perform a memory search, instead of the SQL one */
553         const gchar *in_memory_tokens[] = { "body-contains", "body-regex", "match-threads", "message-location", "header-soundex", "header-regex", "header-full-regex", "header-contains", NULL };
554         gint i;
555
556         if (!expr)
557                 return FALSE;
558
559         for (i = 0; in_memory_tokens[i]; i++) {
560                 if (strstr (expr, in_memory_tokens[i]))
561                         return TRUE;
562         }
563
564         return FALSE;
565 }
566
567 /**
568  * camel_folder_search_search:
569  * @search:
570  * @expr:
571  * @uids: to search against, NULL for all uid's.
572  * @error: return location for a #GError, or %NULL
573  *
574  * Run a search.  Search must have had Folder already set on it, and
575  * it must implement summaries.
576  *
577  * Returns:
578  **/
579 GPtrArray *
580 camel_folder_search_search (CamelFolderSearch *search,
581                             const gchar *expr,
582                             GPtrArray *uids,
583                             GError **error)
584 {
585         ESExpResult *r;
586         GPtrArray *matches = NULL, *summary_set;
587         gint i;
588         CamelDB *cdb;
589         gchar *sql_query, *tmp, *tmp1;
590         GHashTable *results;
591
592         CamelFolderSearchPrivate *p = search->priv;
593
594         g_assert (search->folder);
595
596         p->error = error;
597
598         /* We route body-contains / thread based search and uid search through memory and not via db. */
599         if (uids || do_search_in_memory (expr)) {
600                 /* setup our search list only contains those we're interested in */
601                 search->summary = camel_folder_get_summary (search->folder);
602
603                 if (uids) {
604                         GHashTable *uids_hash = g_hash_table_new (g_str_hash, g_str_equal);
605
606                         summary_set = search->summary_set = g_ptr_array_new ();
607                         for (i=0;i<uids->len;i++)
608                                 g_hash_table_insert (uids_hash, uids->pdata[i], uids->pdata[i]);
609                         for (i=0;i<search->summary->len;i++)
610                                 if (g_hash_table_lookup (uids_hash, search->summary->pdata[i]))
611                                         g_ptr_array_add (search->summary_set, search->summary->pdata[i]);
612                         g_hash_table_destroy (uids_hash);
613                 } else {
614                         summary_set = search->summary;
615                 }
616
617                 /* only re-parse if the search has changed */
618                 if (search->last_search == NULL
619                     || strcmp (search->last_search, expr)) {
620                         e_sexp_input_text (search->sexp, expr, strlen (expr));
621                         if (e_sexp_parse (search->sexp) == -1) {
622                                 g_set_error (
623                                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
624                                         _("Cannot parse search expression: %s:\n%s"),
625                                         e_sexp_error (search->sexp), expr);
626                                 goto fail;
627                         }
628
629                         g_free (search->last_search);
630                         search->last_search = g_strdup (expr);
631                 }
632                 r = e_sexp_eval (search->sexp);
633                 if (r == NULL) {
634                         g_set_error (
635                                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
636                                 _("Error executing search expression: %s:\n%s"),
637                                 e_sexp_error (search->sexp), expr);
638                         goto fail;
639                 }
640
641                 matches = g_ptr_array_new ();
642
643                 /* now create a folder summary to return?? */
644                 if (r->type == ESEXP_RES_ARRAY_PTR) {
645                         d(printf("got result\n"));
646
647                         /* reorder result in summary order */
648                         results = g_hash_table_new (g_str_hash, g_str_equal);
649                         for (i=0;i<r->value.ptrarray->len;i++) {
650                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
651                                 g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1));
652                         }
653
654                         for (i=0;i<summary_set->len;i++) {
655                                 gchar *uid = g_ptr_array_index (summary_set, i);
656                                 if (g_hash_table_lookup (results, uid))
657                                         g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (uid));
658                         }
659                         g_hash_table_destroy (results);
660                 }
661
662                 e_sexp_result_free (search->sexp, r);
663
664         } else {
665                 CamelStore *parent_store;
666                 const gchar *full_name;
667                 GError *local_error = NULL;
668
669                 full_name = camel_folder_get_full_name (search->folder);
670                 parent_store = camel_folder_get_parent_store (search->folder);
671
672                 /* Sync the db, so that we search the db for changes */
673                 camel_folder_summary_save_to_db (search->folder->summary, error);
674
675                 dd(printf ("sexp is : [%s]\n", expr));
676                 if (g_getenv("SQL_SEARCH_OLD"))
677                         sql_query = camel_sexp_to_sql (expr);
678                 else
679                         sql_query = camel_sexp_to_sql_sexp (expr);
680                 tmp1 = camel_db_sqlize_string (full_name);
681                 tmp = g_strdup_printf ("SELECT uid FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query?sql_query:"");
682                 camel_db_free_sqlized_string (tmp1);
683                 g_free (sql_query);
684                 dd(printf("Equivalent sql %s\n", tmp));
685
686                 matches = g_ptr_array_new ();
687                 cdb = (CamelDB *) (parent_store->cdb_r);
688                 camel_db_select (
689                         cdb, tmp, (CamelDBSelectCB)
690                         read_uid_callback, matches, &local_error);
691                 if (local_error != NULL) {
692                         const gchar *message = local_error->message;
693                         if (strncmp(message, "no such table", 13) == 0) {
694                                 d(g_warning ("Error during searching %s: %s\n", tmp, message));
695                                 /* Suppress no such table */
696                                 g_clear_error (&local_error);
697                         } else
698                                 g_propagate_error (error, local_error);
699                 }
700                 g_free (tmp);
701
702         }
703
704 fail:
705         /* these might be allocated by match-threads */
706         if (p->threads)
707                 camel_folder_thread_messages_unref (p->threads);
708         if (p->threads_hash)
709                 g_hash_table_destroy (p->threads_hash);
710         if (search->summary_set)
711                 g_ptr_array_free (search->summary_set, TRUE);
712         if (search->summary)
713                 camel_folder_free_summary (search->folder, search->summary);
714
715         p->threads = NULL;
716         p->threads_hash = NULL;
717         search->folder = NULL;
718         search->summary = NULL;
719         search->summary_set = NULL;
720         search->current = NULL;
721         search->body_index = NULL;
722
723         return matches;
724 }
725
726 void camel_folder_search_free_result (CamelFolderSearch *search, GPtrArray *result)
727 {
728         g_ptr_array_foreach (result, (GFunc) camel_pstring_free, NULL);
729         g_ptr_array_free (result, TRUE);
730 }
731
732 /* dummy function, returns false always, or an empty match array */
733 static ESExpResult *
734 search_dummy (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
735 {
736         ESExpResult *r;
737
738         if (search->current == NULL) {
739                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
740                 r->value.boolean = FALSE;
741         } else {
742                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
743                 r->value.ptrarray = g_ptr_array_new ();
744         }
745
746         return r;
747 }
748
749 /* impelemnt an 'array not', i.e. everything in the summary, not in the supplied array */
750 static ESExpResult *
751 search_not (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
752 {
753         ESExpResult *r;
754         gint i;
755
756         if (argc>0) {
757                 if (argv[0]->type == ESEXP_RES_ARRAY_PTR) {
758                         GPtrArray *v = argv[0]->value.ptrarray;
759                         const gchar *uid;
760
761                         r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
762                         r->value.ptrarray = g_ptr_array_new ();
763
764                         /* not against a single message?*/
765                         if (search->current) {
766                                 gint found = FALSE;
767
768                                 uid = camel_message_info_uid (search->current);
769                                 for (i=0;!found && i<v->len;i++) {
770                                         if (strcmp (uid, v->pdata[i]) == 0)
771                                                 found = TRUE;
772                                 }
773
774                                 if (!found)
775                                         g_ptr_array_add (r->value.ptrarray, (gchar *)uid);
776                         } else if (search->summary == NULL) {
777                                 g_warning("No summary set, 'not' against an array requires a summary");
778                         } else {
779                                 /* 'not' against the whole summary */
780                                 GHashTable *have = g_hash_table_new (g_str_hash, g_str_equal);
781                                 gchar **s;
782                                 gchar **m;
783
784                                 s = (gchar **)v->pdata;
785                                 for (i=0;i<v->len;i++)
786                                         g_hash_table_insert (have, s[i], s[i]);
787
788                                 v = search->summary_set?search->summary_set:search->summary;
789                                 m = (gchar **)v->pdata;
790                                 for (i=0;i<v->len;i++) {
791                                         gchar *uid = m[i];
792
793                                         if (g_hash_table_lookup (have, uid) == NULL)
794                                                 g_ptr_array_add (r->value.ptrarray, uid);
795                                 }
796                                 g_hash_table_destroy (have);
797                         }
798                 } else {
799                         gint res = TRUE;
800
801                         if (argv[0]->type == ESEXP_RES_BOOL)
802                                 res = !argv[0]->value.boolean;
803
804                         r = e_sexp_result_new (f, ESEXP_RES_BOOL);
805                         r->value.boolean = res;
806                 }
807         } else {
808                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
809                 r->value.boolean = TRUE;
810         }
811
812         return r;
813 }
814
815 static ESExpResult *
816 search_match_all (struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
817 {
818         gint i;
819         ESExpResult *r, *r1;
820         gchar *error_msg;
821         GPtrArray *v;
822
823         if (argc>1) {
824                 g_warning("match-all only takes a single argument, other arguments ignored");
825         }
826
827         /* we are only matching a single message?  or already inside a match-all? */
828         if (search->current) {
829                 d(printf("matching against 1 message: %s\n", camel_message_info_subject(search->current)));
830
831                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
832                 r->value.boolean = FALSE;
833
834                 if (argc>0) {
835                         r1 = e_sexp_term_eval (f, argv[0]);
836                         if (r1->type == ESEXP_RES_BOOL) {
837                                 r->value.boolean = r1->value.boolean;
838                         } else {
839                                 g_warning("invalid syntax, matches require a single bool result");
840                                 error_msg = g_strdup_printf(_("(%s) requires a single bool result"), "match-all");
841                                 e_sexp_fatal_error (f, error_msg);
842                                 g_free (error_msg);
843                         }
844                         e_sexp_result_free (f, r1);
845                 } else {
846                         r->value.boolean = TRUE;
847                 }
848                 return r;
849         }
850
851         r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
852         r->value.ptrarray = g_ptr_array_new ();
853
854         if (search->summary == NULL) {
855                 /* TODO: make it work - e.g. use the folder and so forth for a slower search */
856                 g_warning("No summary supplied, match-all doesn't work with no summary");
857                 g_assert (0);
858                 return r;
859         }
860
861         v = search->summary_set?search->summary_set:search->summary;
862
863         if (!CAMEL_IS_VEE_FOLDER (search->folder)) {
864                 camel_folder_summary_prepare_fetch_all (search->folder->summary, search->priv->error);
865         }
866
867         for (i=0;i<v->len;i++) {
868                 const gchar *uid;
869
870                 search->current = camel_folder_summary_uid (search->folder->summary, v->pdata[i]);
871                 if (!search->current)
872                         continue;
873                 uid = camel_message_info_uid (search->current);
874
875                 if (argc>0) {
876                         r1 = e_sexp_term_eval (f, argv[0]);
877                         if (r1->type == ESEXP_RES_BOOL) {
878                                 if (r1->value.boolean)
879                                         g_ptr_array_add (r->value.ptrarray, (gchar *)uid);
880                         } else {
881                                 g_warning("invalid syntax, matches require a single bool result");
882                                 error_msg = g_strdup_printf(_("(%s) requires a single bool result"), "match-all");
883                                 e_sexp_fatal_error (f, error_msg);
884                                 g_free (error_msg);
885                         }
886                         e_sexp_result_free (f, r1);
887                 } else {
888                         g_ptr_array_add (r->value.ptrarray, (gchar *)uid);
889                 }
890                 camel_message_info_free (search->current);
891         }
892         search->current = NULL;
893         return r;
894 }
895
896 static void
897 fill_thread_table (struct _CamelFolderThreadNode *root, GHashTable *id_hash)
898 {
899         while (root) {
900                 g_hash_table_insert (id_hash, (gchar *)camel_message_info_uid (root->message), root);
901                 if (root->child)
902                         fill_thread_table (root->child, id_hash);
903                 root = root->next;
904         }
905 }
906
907 static void
908 add_thread_results (struct _CamelFolderThreadNode *root, GHashTable *result_hash)
909 {
910         while (root) {
911                 g_hash_table_insert (result_hash, (gchar *)camel_message_info_uid (root->message), GINT_TO_POINTER (1));
912                 if (root->child)
913                         add_thread_results (root->child, result_hash);
914                 root = root->next;
915         }
916 }
917
918 static void
919 add_results (gchar *uid, gpointer dummy, GPtrArray *result)
920 {
921         g_ptr_array_add (result, uid);
922 }
923
924 static ESExpResult *
925 search_match_threads (struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
926 {
927         ESExpResult *r;
928         CamelFolderSearchPrivate *p = search->priv;
929         gint i, type;
930         GHashTable *results;
931         gchar *error_msg;
932
933         /* not supported in match-all */
934         if (search->current) {
935                 error_msg = g_strdup_printf(_("(%s) not allowed inside %s"), "match-threads", "match-all");
936                 e_sexp_fatal_error (f, error_msg);
937                 g_free (error_msg);
938         }
939
940         if (argc == 0) {
941                 error_msg = g_strdup_printf(_("(%s) requires a match type string"), "match-threads");
942                 e_sexp_fatal_error (f, error_msg);
943                 g_free (error_msg);
944         }
945
946         r = e_sexp_term_eval (f, argv[0]);
947         if (r->type != ESEXP_RES_STRING) {
948                 error_msg = g_strdup_printf(_("(%s) requires a match type string"), "match-threads");
949                 e_sexp_fatal_error (f, error_msg);
950                 g_free (error_msg);
951         }
952
953         type = 0;
954         if (!strcmp(r->value.string, "none"))
955                 type = 0;
956         else if (!strcmp(r->value.string, "all"))
957                 type = 1;
958         else if (!strcmp(r->value.string, "replies"))
959                 type = 2;
960         else if (!strcmp(r->value.string, "replies_parents"))
961                 type = 3;
962         else if (!strcmp(r->value.string, "single"))
963                 type = 4;
964         e_sexp_result_free (f, r);
965
966         /* behave as (begin does */
967         r = NULL;
968         for (i=1;i<argc;i++) {
969                 if (r)
970                         e_sexp_result_free (f, r);
971                 r = e_sexp_term_eval (f, argv[i]);
972         }
973
974         if (r == NULL || r->type != ESEXP_RES_ARRAY_PTR) {
975                 error_msg = g_strdup_printf(_("(%s) expects an array result"), "match-threads");
976                 e_sexp_fatal_error (f, error_msg);
977                 g_free (error_msg);
978         }
979
980         if (type == 0)
981                 return r;
982
983         if (search->folder == NULL) {
984                 error_msg = g_strdup_printf(_("(%s) requires the folder set"), "match-threads");
985                 e_sexp_fatal_error (f, error_msg);
986                 g_free (error_msg);
987         }
988
989         /* cache this, so we only have to re-calculate once per search at most */
990         if (p->threads == NULL) {
991                 p->threads = camel_folder_thread_messages_new (search->folder, NULL, TRUE);
992                 p->threads_hash = g_hash_table_new (g_str_hash, g_str_equal);
993
994                 fill_thread_table (p->threads->tree, p->threads_hash);
995         }
996
997         results = g_hash_table_new (g_str_hash, g_str_equal);
998         for (i=0;i<r->value.ptrarray->len;i++) {
999                 struct _CamelFolderThreadNode *node, *scan;
1000
1001                 if (type != 4)
1002                         g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1));
1003
1004                 node = g_hash_table_lookup (p->threads_hash, (gchar *)g_ptr_array_index (r->value.ptrarray, i));
1005                 if (node == NULL) /* this shouldn't happen but why cry over spilt milk */
1006                         continue;
1007
1008                 /* select messages in thread according to search criteria */
1009                 if (type == 4) {
1010                         if (node->child == NULL && node->parent == NULL)
1011                                 g_hash_table_insert (results, (gchar *)camel_message_info_uid (node->message), GINT_TO_POINTER (1));
1012                 } else {
1013                         if (type == 3) {
1014                                 scan = node;
1015                                 while (scan && scan->parent) {
1016                                         scan = scan->parent;
1017                                         g_hash_table_insert (results, (gchar *)camel_message_info_uid (scan->message), GINT_TO_POINTER (1));
1018                                 }
1019                         } else if (type == 1) {
1020                                 while (node && node->parent)
1021                                         node = node->parent;
1022                         }
1023                         g_hash_table_insert (results, (gchar *)camel_message_info_uid (node->message), GINT_TO_POINTER (1));
1024                         if (node->child)
1025                                 add_thread_results (node->child, results);
1026                 }
1027         }
1028         e_sexp_result_free (f, r);
1029
1030         r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1031         r->value.ptrarray = g_ptr_array_new ();
1032
1033         g_hash_table_foreach (results, (GHFunc)add_results, r->value.ptrarray);
1034         g_hash_table_destroy (results);
1035
1036         return r;
1037 }
1038
1039 static CamelMimeMessage *
1040 get_current_message (CamelFolderSearch *search)
1041 {
1042         if (!search || !search->folder || !search->current)
1043                 return NULL;
1044
1045         /* FIXME Pass a GCancellable */
1046         return camel_folder_get_message_sync (
1047                 search->folder, search->current->uid, NULL, NULL);
1048 }
1049
1050 static ESExpResult *
1051 check_header (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search, camel_search_match_t how)
1052 {
1053         ESExpResult *r;
1054         gint truth = FALSE;
1055
1056         r(printf("executing check-header %d\n", how));
1057
1058         /* are we inside a match-all? */
1059         if (search->current && argc>1
1060             && argv[0]->type == ESEXP_RES_STRING) {
1061                 gchar *headername;
1062                 const gchar *header = NULL, *charset = NULL;
1063                 gchar strbuf[32];
1064                 gint i, j;
1065                 camel_search_t type = CAMEL_SEARCH_TYPE_ASIS;
1066                 struct _camel_search_words *words;
1067                 CamelMimeMessage *message = NULL;
1068                 struct _camel_header_raw *raw_header;
1069
1070                 /* only a subset of headers are supported .. */
1071                 headername = argv[0]->value.string;
1072                 if (!g_ascii_strcasecmp(headername, "subject")) {
1073                         header = camel_message_info_subject (search->current);
1074                 } else if (!g_ascii_strcasecmp(headername, "date")) {
1075                         /* FIXME: not a very useful form of the date */
1076                         sprintf(strbuf, "%d", (gint)camel_message_info_date_sent(search->current));
1077                         header = strbuf;
1078                 } else if (!g_ascii_strcasecmp(headername, "from")) {
1079                         header = camel_message_info_from (search->current);
1080                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1081                 } else if (!g_ascii_strcasecmp(headername, "to")) {
1082                         header = camel_message_info_to (search->current);
1083                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1084                 } else if (!g_ascii_strcasecmp(headername, "cc")) {
1085                         header = camel_message_info_cc (search->current);
1086                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1087                 } else if (!g_ascii_strcasecmp(headername, "x-camel-mlist")) {
1088                         header = camel_message_info_mlist (search->current);
1089                         type = CAMEL_SEARCH_TYPE_MLIST;
1090                 } else {
1091                         message = get_current_message (search);
1092                         if (message) {
1093                                 CamelContentType *ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message));
1094
1095                                 if (ct) {
1096                                         charset = camel_content_type_param (ct, "charset");
1097                                         charset = camel_iconv_charset_name (charset);
1098                                 }
1099                         }
1100                 }
1101
1102                 if (header == NULL)
1103                         header = "";
1104
1105                 /* performs an OR of all words */
1106                 for (i=1;i<argc && !truth;i++) {
1107                         if (argv[i]->type == ESEXP_RES_STRING) {
1108                                 if (argv[i]->value.string[0] == 0) {
1109                                         truth = TRUE;
1110                                 } else if (how == CAMEL_SEARCH_MATCH_CONTAINS) {
1111                                         /* doesn't make sense to split words on anything but contains i.e. we can't have an ending match different words */
1112                                         words = camel_search_words_split ((const guchar *) argv[i]->value.string);
1113                                         truth = TRUE;
1114                                         for (j=0;j<words->len && truth;j++) {
1115                                                 if (message) {
1116                                                         for (raw_header = ((CamelMimePart *)message)->headers; raw_header; raw_header = raw_header->next) {
1117                                                                 if (!g_ascii_strcasecmp (raw_header->name, headername)) {
1118                                                                         if (camel_search_header_match (raw_header->value, words->words[j]->word, how, type, charset))
1119                                                                                 break;;
1120                                                                 }
1121                                                         }
1122
1123                                                         truth = raw_header != NULL;
1124                                                 } else
1125                                                         truth = camel_search_header_match (header, words->words[j]->word, how, type, charset);
1126                                         }
1127                                         camel_search_words_free (words);
1128                                 } else {
1129                                         if (message) {
1130                                                 for (raw_header = ((CamelMimePart *)message)->headers; raw_header && !truth; raw_header = raw_header->next) {
1131                                                         if (!g_ascii_strcasecmp (raw_header->name, headername)) {
1132                                                                 truth = camel_search_header_match (raw_header->value, argv[i]->value.string, how, type, charset);
1133                                                         }
1134                                                 }
1135                                         } else
1136                                                 truth = camel_search_header_match (header, argv[i]->value.string, how, type, charset);
1137                                 }
1138                         }
1139                 }
1140
1141                 if (message)
1142                         g_object_unref (message);
1143         }
1144         /* TODO: else, find all matches */
1145
1146         r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1147         r->value.boolean = truth;
1148
1149         return r;
1150 }
1151
1152 /*
1153 static void
1154 l_printf (gchar *node)
1155 {
1156 printf("%s\t", node);
1157 }
1158 */
1159
1160 static ESExpResult *
1161 search_header_contains (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1162 {
1163         return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS);
1164 }
1165
1166 static ESExpResult *
1167 search_header_matches (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1168 {
1169         return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT);
1170 }
1171
1172 static ESExpResult *
1173 search_header_starts_with (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1174 {
1175         return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS);
1176 }
1177
1178 static ESExpResult *
1179 search_header_ends_with (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1180 {
1181         return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS);
1182 }
1183
1184 static ESExpResult *
1185 search_header_exists (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1186 {
1187         ESExpResult *r;
1188
1189         r(printf ("executing header-exists\n"));
1190
1191         if (search->current) {
1192                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1193                 if (argc == 1 && argv[0]->type == ESEXP_RES_STRING)
1194                         r->value.boolean = camel_medium_get_header (CAMEL_MEDIUM (search->current), argv[0]->value.string) != NULL;
1195
1196         } else {
1197                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1198                 r->value.ptrarray = g_ptr_array_new ();
1199         }
1200
1201         return r;
1202 }
1203
1204 static ESExpResult *
1205 search_header_soundex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1206 {
1207         return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_SOUNDEX);
1208 }
1209
1210 static ESExpResult *
1211 search_header_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1212 {
1213         ESExpResult *r;
1214         CamelMimeMessage *msg;
1215
1216         msg = get_current_message (search);
1217
1218         if (msg) {
1219                 regex_t pattern;
1220                 const gchar *contents;
1221
1222                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1223
1224                 if (argc > 1 && argv[0]->type == ESEXP_RES_STRING
1225                     && (contents = camel_medium_get_header (CAMEL_MEDIUM (msg), argv[0]->value.string))
1226                     && camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE, argc-1, argv+1, search->priv->error) == 0) {
1227                         r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
1228                         regfree (&pattern);
1229                 } else
1230                         r->value.boolean = FALSE;
1231
1232                 g_object_unref (msg);
1233         } else {
1234                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1235                 r->value.ptrarray = g_ptr_array_new ();
1236         }
1237
1238         return r;
1239 }
1240
1241 static gchar *
1242 get_full_header (CamelMimeMessage *message)
1243 {
1244         CamelMimePart *mp = CAMEL_MIME_PART (message);
1245         GString *str = g_string_new ("");
1246         struct _camel_header_raw *h;
1247
1248         for (h = mp->headers; h; h = h->next) {
1249                 if (h->value != NULL) {
1250                         g_string_append (str, h->name);
1251                         if (isspace (h->value[0]))
1252                                 g_string_append (str, ":");
1253                         else
1254                                 g_string_append (str, ": ");
1255                         g_string_append (str, h->value);
1256                         g_string_append_c (str, '\n');
1257                 }
1258         }
1259
1260         return g_string_free (str, FALSE);
1261 }
1262
1263 static ESExpResult *
1264 search_header_full_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1265 {
1266         ESExpResult *r;
1267         CamelMimeMessage *msg;
1268
1269         msg = get_current_message (search);
1270
1271         if (msg) {
1272                 regex_t pattern;
1273
1274                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1275
1276                 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) {
1277                         gchar *contents;
1278
1279                         contents = get_full_header (msg);
1280                         r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
1281
1282                         g_free (contents);
1283                         regfree (&pattern);
1284                 } else
1285                         r->value.boolean = FALSE;
1286
1287                 g_object_unref (msg);
1288         } else {
1289                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1290                 r->value.ptrarray = g_ptr_array_new ();
1291         }
1292
1293         return r;
1294 }
1295
1296 /* this is just to OR results together */
1297 struct IterData {
1298         gint count;
1299         GPtrArray *uids;
1300 };
1301
1302 /* or, store all unique values */
1303 static void
1304 htor (gchar *key, gint value, struct IterData *iter_data)
1305 {
1306         g_ptr_array_add (iter_data->uids, key);
1307 }
1308
1309 /* and, only store duplicates */
1310 static void
1311 htand (gchar *key, gint value, struct IterData *iter_data)
1312 {
1313         if (value == iter_data->count)
1314                 g_ptr_array_add (iter_data->uids, key);
1315 }
1316
1317 static gint
1318 match_message_index (CamelIndex *idx,
1319                      const gchar *uid,
1320                      const gchar *match,
1321                      GError **error)
1322 {
1323         CamelIndexCursor *wc, *nc;
1324         const gchar *word, *name;
1325         gint truth = FALSE;
1326
1327         wc = camel_index_words (idx);
1328         if (wc) {
1329                 while (!truth && (word = camel_index_cursor_next (wc))) {
1330                         if (camel_ustrstrcase (word,match) != NULL) {
1331                                 /* perf: could have the wc cursor return the name cursor */
1332                                 nc = camel_index_find (idx, word);
1333                                 if (nc) {
1334                                         while (!truth && (name = camel_index_cursor_next (nc)))
1335                                                 truth = strcmp (name, uid) == 0;
1336                                         g_object_unref (nc);
1337                                 }
1338                         }
1339                 }
1340                 g_object_unref (wc);
1341         }
1342
1343         return truth;
1344 }
1345
1346 /*
1347  "one two" "three" "four five"
1348
1349   one and two
1350 or
1351   three
1352 or
1353   four and five
1354 */
1355
1356 /* returns messages which contain all words listed in words */
1357 static GPtrArray *
1358 match_words_index (CamelFolderSearch *search,
1359                    struct _camel_search_words *words,
1360                    GError **error)
1361 {
1362         GPtrArray *result = g_ptr_array_new ();
1363         GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal);
1364         struct IterData lambdafoo;
1365         CamelIndexCursor *wc, *nc;
1366         const gchar *word, *name;
1367         gint i;
1368
1369         /* we can have a maximum of 32 words, as we use it as the AND mask */
1370
1371         wc = camel_index_words (search->body_index);
1372         if (wc) {
1373                 while ((word = camel_index_cursor_next (wc))) {
1374                         for (i=0;i<words->len;i++) {
1375                                 if (camel_ustrstrcase (word, words->words[i]->word) != NULL) {
1376                                         /* perf: could have the wc cursor return the name cursor */
1377                                         nc = camel_index_find (search->body_index, word);
1378                                         if (nc) {
1379                                                 while ((name = camel_index_cursor_next (nc))) {
1380                                                                 gint mask;
1381
1382                                                                 mask = (GPOINTER_TO_INT (g_hash_table_lookup (ht, name))) | (1 << i);
1383                                                                 g_hash_table_insert (ht, (gchar *) camel_pstring_peek (name), GINT_TO_POINTER (mask));
1384                                                 }
1385                                                 g_object_unref (nc);
1386                                         }
1387                                 }
1388                         }
1389                 }
1390                 g_object_unref (wc);
1391
1392                 lambdafoo.uids = result;
1393                 lambdafoo.count = (1 << words->len) - 1;
1394                 g_hash_table_foreach (ht, (GHFunc)htand, &lambdafoo);
1395                 g_hash_table_destroy (ht);
1396         }
1397
1398         return result;
1399 }
1400
1401 static gboolean
1402 match_words_1message (CamelDataWrapper *object, struct _camel_search_words *words, guint32 *mask)
1403 {
1404         CamelDataWrapper *containee;
1405         gint truth = FALSE;
1406         gint parts, i;
1407
1408         containee = camel_medium_get_content (CAMEL_MEDIUM (object));
1409
1410         if (containee == NULL)
1411                 return FALSE;
1412
1413         /* using the object types is more accurate than using the mime/types */
1414         if (CAMEL_IS_MULTIPART (containee)) {
1415                 parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
1416                 for (i = 0; i < parts && truth == FALSE; i++) {
1417                         CamelDataWrapper *part = (CamelDataWrapper *)camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
1418                         if (part)
1419                                 truth = match_words_1message (part, words, mask);
1420                 }
1421         } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
1422                 /* for messages we only look at its contents */
1423                 truth = match_words_1message ((CamelDataWrapper *)containee, words, mask);
1424         } else if (camel_content_type_is(CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")) {
1425                 /* for all other text parts, we look inside, otherwise we dont care */
1426                 CamelStream *stream;
1427                 GByteArray *byte_array;
1428
1429                 byte_array = g_byte_array_new ();
1430                 stream = camel_stream_mem_new_with_byte_array (byte_array);
1431
1432                 /* FIXME The match should be part of a stream op */
1433                 /* FIXME Pass a GCancellable and GError here. */
1434                 camel_data_wrapper_decode_to_stream_sync (
1435                         containee, stream, NULL, NULL);
1436                 camel_stream_write (stream, "", 1, NULL, NULL);
1437                 for (i=0;i<words->len;i++) {
1438                         /* FIXME: This is horridly slow, and should use a real search algorithm */
1439                         if (camel_ustrstrcase ((const gchar *) byte_array->data, words->words[i]->word) != NULL) {
1440                                 *mask |= (1 << i);
1441                                 /* shortcut a match */
1442                                 if (*mask == (1 << (words->len))-1)
1443                                         return TRUE;
1444                         }
1445                 }
1446
1447                 g_object_unref (stream);
1448         }
1449
1450         return truth;
1451 }
1452
1453 static gboolean
1454 match_words_message (CamelFolder *folder,
1455                      const gchar *uid,
1456                      struct _camel_search_words *words,
1457                      GCancellable *cancellable,
1458                      GError **error)
1459 {
1460         guint32 mask;
1461         CamelMimeMessage *msg;
1462         gint truth = FALSE;
1463
1464         msg = camel_folder_get_message_sync (folder, uid, cancellable, error);
1465         if (msg) {
1466                 mask = 0;
1467                 truth = match_words_1message ((CamelDataWrapper *)msg, words, &mask);
1468                 g_object_unref (msg);
1469         }
1470
1471         return truth;
1472 }
1473
1474 static GPtrArray *
1475 match_words_messages (CamelFolderSearch *search,
1476                       struct _camel_search_words *words,
1477                       GCancellable *cancellable,
1478                       GError **error)
1479 {
1480         gint i;
1481         GPtrArray *matches = g_ptr_array_new ();
1482
1483         if (search->body_index) {
1484                 GPtrArray *indexed;
1485                 struct _camel_search_words *simple;
1486
1487                 simple = camel_search_words_simple (words);
1488                 indexed = match_words_index (search, simple, error);
1489                 camel_search_words_free (simple);
1490
1491                 for (i=0;i<indexed->len;i++) {
1492                         const gchar *uid = g_ptr_array_index (indexed, i);
1493
1494                         if (match_words_message (
1495                                         search->folder, uid, words,
1496                                         cancellable, error))
1497                                 g_ptr_array_add (matches, (gchar *)uid);
1498                 }
1499
1500                 g_ptr_array_free (indexed, TRUE);
1501         } else {
1502                 GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1503
1504                 for (i=0;i<v->len;i++) {
1505                         gchar *uid  = g_ptr_array_index (v, i);
1506
1507                         if (match_words_message (
1508                                 search->folder, uid, words,
1509                                 cancellable, error))
1510                                 g_ptr_array_add (matches, (gchar *)uid);
1511                 }
1512         }
1513
1514         return matches;
1515 }
1516
1517 static ESExpResult *
1518 search_body_contains (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1519 {
1520         gint i, j;
1521         GError **error = search->priv->error;
1522         struct _camel_search_words *words;
1523         ESExpResult *r;
1524         struct IterData lambdafoo;
1525
1526         if (search->current) {
1527                 gint truth = FALSE;
1528
1529                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1530                         truth = TRUE;
1531                 } else {
1532                         for (i=0;i<argc && !truth;i++) {
1533                                 if (argv[i]->type == ESEXP_RES_STRING) {
1534                                         words = camel_search_words_split ((const guchar *) argv[i]->value.string);
1535                                         truth = TRUE;
1536                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1537                                                 for (j=0;j<words->len && truth;j++)
1538                                                         truth = match_message_index (search->body_index, camel_message_info_uid (search->current), words->words[j]->word, error);
1539                                         } else {
1540                                                 /* TODO: cache current message incase of multiple body search terms */
1541                                                 /* FIXME Pass a GCancellable */
1542                                                 truth = match_words_message (search->folder, camel_message_info_uid (search->current), words, NULL, error);
1543                                         }
1544                                         camel_search_words_free (words);
1545                                 }
1546                         }
1547                 }
1548                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1549                 r->value.boolean = truth;
1550         } else {
1551                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1552                 r->value.ptrarray = g_ptr_array_new ();
1553
1554                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1555                         GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1556
1557                         for (i=0;i<v->len;i++) {
1558                                 gchar *uid = g_ptr_array_index (v, i);
1559
1560                                 g_ptr_array_add (r->value.ptrarray, uid);
1561                         }
1562                 } else {
1563                         GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal);
1564                         GPtrArray *matches;
1565
1566                         for (i=0;i<argc;i++) {
1567                                 if (argv[i]->type == ESEXP_RES_STRING) {
1568                                         words = camel_search_words_split ((const guchar *) argv[i]->value.string);
1569                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1570                                                 matches = match_words_index (search, words, error);
1571                                         } else {
1572                                                 /* FIXME Pass a GCancellable */
1573                                                 matches = match_words_messages (search, words, NULL, error);
1574                                         }
1575                                         for (j=0;j<matches->len;j++) {
1576                                                 g_hash_table_insert (ht, matches->pdata[j], matches->pdata[j]);
1577                                         }
1578                                         g_ptr_array_free (matches, TRUE);
1579                                         camel_search_words_free (words);
1580                                 }
1581                         }
1582                         lambdafoo.uids = r->value.ptrarray;
1583                         g_hash_table_foreach (ht, (GHFunc)htor, &lambdafoo);
1584                         g_hash_table_destroy (ht);
1585                 }
1586         }
1587
1588         return r;
1589 }
1590
1591 static ESExpResult *
1592 search_body_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1593 {
1594         ESExpResult *r;
1595         CamelMimeMessage *msg = get_current_message (search);
1596
1597         if (msg) {
1598                 regex_t pattern;
1599
1600                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1601
1602                 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->error) == 0) {
1603                         r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) msg, &pattern);
1604                         regfree (&pattern);
1605                 } else
1606                         r->value.boolean = FALSE;
1607
1608                 g_object_unref (msg);
1609         } else {
1610                 regex_t pattern;
1611
1612                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1613                 r->value.ptrarray = g_ptr_array_new ();
1614
1615                 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->error) == 0) {
1616                         gint i;
1617                         GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1618                         CamelMimeMessage *message;
1619
1620                         for (i = 0; i < v->len; i++) {
1621                                 gchar *uid = g_ptr_array_index (v, i);
1622
1623                                 /* FIXME Pass a GCancellable */
1624                                 message = camel_folder_get_message_sync (
1625                                         search->folder, uid, NULL, NULL);
1626                                 if (message) {
1627                                         if (camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern)) {
1628                                                 g_ptr_array_add (r->value.ptrarray, uid);
1629                                         }
1630
1631                                         g_object_unref (message);
1632                                 }
1633                         }
1634
1635                         regfree (&pattern);
1636                 }
1637         }
1638
1639         return r;
1640 }
1641
1642 static ESExpResult *
1643 search_user_flag (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1644 {
1645         ESExpResult *r;
1646         gint i;
1647
1648         r(printf("executing user-flag\n"));
1649
1650         /* are we inside a match-all? */
1651         if (search->current) {
1652                 gint truth = FALSE;
1653                 /* performs an OR of all words */
1654                 for (i=0;i<argc && !truth;i++) {
1655                         if (argv[i]->type == ESEXP_RES_STRING
1656                             && camel_message_info_user_flag (search->current, argv[i]->value.string)) {
1657                                 truth = TRUE;
1658                                 break;
1659                         }
1660                 }
1661                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1662                 r->value.boolean = truth;
1663         } else {
1664                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1665                 r->value.ptrarray = g_ptr_array_new ();
1666         }
1667
1668         return r;
1669 }
1670
1671 static ESExpResult *
1672 search_system_flag (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1673 {
1674         ESExpResult *r;
1675
1676         r(printf ("executing system-flag\n"));
1677
1678         if (search->current) {
1679                 gboolean truth = FALSE;
1680
1681                 if (argc == 1)
1682                         truth = camel_system_flag_get (camel_message_info_flags (search->current), argv[0]->value.string);
1683
1684                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1685                 r->value.boolean = truth;
1686         } else {
1687                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1688                 r->value.ptrarray = g_ptr_array_new ();
1689         }
1690
1691         return r;
1692 }
1693
1694 static ESExpResult *
1695 search_user_tag (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1696 {
1697         const gchar *value = NULL;
1698         ESExpResult *r;
1699
1700         r(printf("executing user-tag\n"));
1701
1702         if (search->current && argc == 1)
1703                 value = camel_message_info_user_tag (search->current, argv[0]->value.string);
1704
1705         r = e_sexp_result_new (f, ESEXP_RES_STRING);
1706         r->value.string = g_strdup (value ? value : "");
1707
1708         return r;
1709 }
1710
1711 static ESExpResult *
1712 search_get_sent_date (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1713 {
1714         ESExpResult *r;
1715
1716         r(printf("executing get-sent-date\n"));
1717
1718         /* are we inside a match-all? */
1719         if (s->current) {
1720                 r = e_sexp_result_new (f, ESEXP_RES_INT);
1721
1722                 r->value.number = camel_message_info_date_sent (s->current);
1723         } else {
1724                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1725                 r->value.ptrarray = g_ptr_array_new ();
1726         }
1727
1728         return r;
1729 }
1730
1731 static ESExpResult *
1732 search_get_received_date (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1733 {
1734         ESExpResult *r;
1735
1736         r(printf("executing get-received-date\n"));
1737
1738         /* are we inside a match-all? */
1739         if (s->current) {
1740                 r = e_sexp_result_new (f, ESEXP_RES_INT);
1741
1742                 r->value.number = camel_message_info_date_received (s->current);
1743         } else {
1744                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1745                 r->value.ptrarray = g_ptr_array_new ();
1746         }
1747
1748         return r;
1749 }
1750
1751 static ESExpResult *
1752 search_get_current_date (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1753 {
1754         ESExpResult *r;
1755
1756         r(printf("executing get-current-date\n"));
1757
1758         r = e_sexp_result_new (f, ESEXP_RES_INT);
1759         r->value.number = time (NULL);
1760         return r;
1761 }
1762
1763 static ESExpResult *
1764 search_get_size (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1765 {
1766         ESExpResult *r;
1767
1768         r(printf("executing get-size\n"));
1769
1770         /* are we inside a match-all? */
1771         if (s->current) {
1772                 r = e_sexp_result_new (f, ESEXP_RES_INT);
1773                 r->value.number = camel_message_info_size (s->current) / 1024;
1774         } else {
1775                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1776                 r->value.ptrarray = g_ptr_array_new ();
1777         }
1778
1779         return r;
1780 }
1781
1782 static ESExpResult *
1783 search_uid (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1784 {
1785         ESExpResult *r;
1786         gint i;
1787
1788         r(printf("executing uid\n"));
1789
1790         /* are we inside a match-all? */
1791         if (search->current) {
1792                 gint truth = FALSE;
1793                 const gchar *uid = camel_message_info_uid (search->current);
1794
1795                 /* performs an OR of all words */
1796                 for (i=0;i<argc && !truth;i++) {
1797                         if (argv[i]->type == ESEXP_RES_STRING
1798                             && !strcmp (uid, argv[i]->value.string)) {
1799                                 truth = TRUE;
1800                                 break;
1801                         }
1802                 }
1803                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1804                 r->value.boolean = truth;
1805         } else {
1806                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1807                 r->value.ptrarray = g_ptr_array_new ();
1808                 for (i=0;i<argc;i++) {
1809                         if (argv[i]->type == ESEXP_RES_STRING)
1810                                 g_ptr_array_add (r->value.ptrarray, argv[i]->value.string);
1811                 }
1812         }
1813
1814         return r;
1815 }
1816
1817 static gint
1818 read_uid_callback (gpointer  ref, gint ncol, gchar ** cols, gchar **name)
1819 {
1820         GPtrArray *matches;
1821
1822         matches = (GPtrArray *) ref;
1823
1824         g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (cols[0]));
1825         return 0;
1826 }
1827
1828 static ESExpResult *
1829 search_message_location (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1830 {
1831         CamelStore *parent_store;
1832         ESExpResult *r;
1833         gboolean same = FALSE;
1834
1835         parent_store = camel_folder_get_parent_store (search->folder);
1836
1837         if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
1838                 if (argv[0]->value.string && search->folder && parent_store && camel_folder_get_full_name (search->folder)) {
1839                         /* FIXME Pass a GCancellable */
1840                         CamelFolderInfo *fi = camel_store_get_folder_info_sync (parent_store, camel_folder_get_full_name (search->folder), 0, NULL, NULL);
1841                         if (fi) {
1842                                 same = g_str_equal (fi->uri ? fi->uri : "", argv[0]->value.string);
1843
1844                                 camel_store_free_folder_info (parent_store, fi);
1845                         }
1846                 }
1847         }
1848
1849         if (search->current) {
1850                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1851                 r->value.boolean = same ? TRUE : FALSE;
1852         } else {
1853                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1854                 r->value.ptrarray = g_ptr_array_new ();
1855
1856                 if (same) {
1857                         /* all matches */
1858                         gint i;
1859                         GPtrArray *v = search->summary_set ? search->summary_set : search->summary;
1860
1861                         for (i = 0; i < v->len; i++) {
1862                                 gchar *uid = g_ptr_array_index (v, i);
1863
1864                                 g_ptr_array_add (r->value.ptrarray, uid);
1865                         }
1866                 }
1867         }
1868
1869         return r;
1870 }