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