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