Search goodness. Make use of the new sexp parser.
[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.h>
39 #include <glib/gi18n-lib.h>
40
41 #include <libedataserver/e-memory.h>
42
43 #include "camel-exception.h"
44 #include "camel-folder-search.h"
45 #include "camel-folder-thread.h"
46 #include "camel-medium.h"
47 #include "camel-mime-message.h"
48 #include "camel-multipart.h"
49 #include "camel-search-private.h"
50 #include "camel-stream-mem.h"
51 #include "camel-db.h"
52 #include "camel-store.h"
53 #include "camel-vee-folder.h"
54 #include "camel-string-utils.h"
55 #include "camel-search-sql.h"
56
57 #define d(x) 
58 #define r(x) 
59
60 struct _CamelFolderSearchPrivate {
61         GHashTable *mempool_hash;
62         CamelException *ex;
63
64         CamelFolderThread *threads;
65         GHashTable *threads_hash;
66 };
67
68 #define _PRIVATE(o) (((CamelFolderSearch *)(o))->priv)
69
70 static ESExpResult *search_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
71
72 static ESExpResult *search_header_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
73 static ESExpResult *search_header_matches(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
74 static ESExpResult *search_header_starts_with(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
75 static ESExpResult *search_header_ends_with(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
76 static ESExpResult *search_header_exists(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
77 static ESExpResult *search_match_all(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search);
78 static ESExpResult *search_match_threads(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *s);
79 static ESExpResult *search_body_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
80 static ESExpResult *search_user_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
81 static ESExpResult *search_user_tag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
82 static ESExpResult *search_system_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
83 static ESExpResult *search_get_sent_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
84 static ESExpResult *search_get_received_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
85 static ESExpResult *search_get_current_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
86 static ESExpResult *search_get_size(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
87 static ESExpResult *search_uid(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
88
89 static ESExpResult *search_dummy(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
90
91 static void camel_folder_search_class_init (CamelFolderSearchClass *klass);
92 static void camel_folder_search_init       (CamelFolderSearch *obj);
93 static void camel_folder_search_finalize   (CamelObject *obj);
94
95 static int read_uid_callback (void * ref, int ncol, char ** cols, char **name);
96
97 static CamelObjectClass *camel_folder_search_parent;
98
99 static void
100 camel_folder_search_class_init (CamelFolderSearchClass *klass)
101 {
102         camel_folder_search_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
103
104         klass->not = search_not;
105
106         klass->match_all = search_match_all;
107         klass->match_threads = search_match_threads;
108         klass->body_contains = search_body_contains;
109         klass->header_contains = search_header_contains;
110         klass->header_matches = search_header_matches;
111         klass->header_starts_with = search_header_starts_with;
112         klass->header_ends_with = search_header_ends_with;
113         klass->header_exists = search_header_exists;
114         klass->user_tag = search_user_tag;
115         klass->user_flag = search_user_flag;
116         klass->system_flag = search_system_flag;
117         klass->get_sent_date = search_get_sent_date;
118         klass->get_received_date = search_get_received_date;
119         klass->get_current_date = search_get_current_date;
120         klass->get_size = search_get_size;
121         klass->uid = search_uid;
122 }
123
124 static void
125 camel_folder_search_init (CamelFolderSearch *obj)
126 {
127         struct _CamelFolderSearchPrivate *p;
128
129         p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
130
131         obj->sexp = e_sexp_new();
132
133         /* use a hash of mempools to associate the returned uid lists with
134            the backing mempool.  yes pretty weird, but i didn't want to change
135            the api just yet */
136
137         p->mempool_hash = g_hash_table_new(0, 0);
138 }
139
140 static void
141 free_mempool(void *key, void *value, void *data)
142 {
143         GPtrArray *uids = key;
144         EMemPool *pool = value;
145
146         g_warning("Search closed with outstanding result unfreed: %p", (void *) uids);
147
148         g_ptr_array_free(uids, TRUE);
149         e_mempool_destroy(pool);
150 }
151
152 static void
153 camel_folder_search_finalize (CamelObject *obj)
154 {
155         CamelFolderSearch *search = (CamelFolderSearch *)obj;
156         struct _CamelFolderSearchPrivate *p = _PRIVATE(obj);
157
158         if (search->sexp)
159                 e_sexp_unref(search->sexp);
160
161         g_free(search->last_search);
162         g_hash_table_foreach(p->mempool_hash, free_mempool, obj);
163         g_hash_table_destroy(p->mempool_hash);
164         g_free(p);
165 }
166
167 CamelType
168 camel_folder_search_get_type (void)
169 {
170         static CamelType type = CAMEL_INVALID_TYPE;
171         
172         if (type == CAMEL_INVALID_TYPE) {
173                 type = camel_type_register (camel_object_get_type (), "CamelFolderSearch",
174                                             sizeof (CamelFolderSearch),
175                                             sizeof (CamelFolderSearchClass),
176                                             (CamelObjectClassInitFunc) camel_folder_search_class_init,
177                                             NULL,
178                                             (CamelObjectInitFunc) camel_folder_search_init,
179                                             (CamelObjectFinalizeFunc) camel_folder_search_finalize);
180         }
181         
182         return type;
183 }
184
185 #ifdef offsetof
186 #define CAMEL_STRUCT_OFFSET(type, field)        ((gint) offsetof (type, field))
187 #else
188 #define CAMEL_STRUCT_OFFSET(type, field)        ((gint) ((gchar*) &((type *) 0)->field))
189 #endif
190
191 static struct {
192         char *name;
193         int offset;
194         int flags;              /* 0x02 = immediate, 0x01 = always enter */
195 } builtins[] = {
196         /* these have default implementations in e-sexp */
197         { "and", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, and), 2 },
198         { "or", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, or), 2 },
199         /* we need to override this one though to implement an 'array not' */
200         { "not", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, not), 0 },
201         { "<", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, lt), 2 },
202         { ">", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, gt), 2 },
203         { "=", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, eq), 2 },
204
205         /* these we have to use our own default if there is none */
206         /* they should all be defined in the language? so it parses, or should they not?? */
207         { "match-all", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, match_all), 3 },
208         { "match-threads", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, match_threads), 3 },
209         { "body-contains", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, body_contains), 1 },
210         { "header-contains", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_contains), 1 },
211         { "header-matches", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_matches), 1 },
212         { "header-starts-with", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_starts_with), 1 },
213         { "header-ends-with", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_ends_with), 1 },
214         { "header-exists", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_exists), 1 },
215         { "user-tag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, user_tag), 1 },
216         { "user-flag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, user_flag), 1 },
217         { "system-flag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, system_flag), 1 },
218         { "get-sent-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_sent_date), 1 },
219         { "get-received-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_received_date), 1 },
220         { "get-current-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_current_date), 1 },
221         { "get-size", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_size), 1 },
222         { "uid", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, uid), 1 },
223 };
224
225 void
226 camel_folder_search_construct (CamelFolderSearch *search)
227 {
228         int i;
229         CamelFolderSearchClass *klass = (CamelFolderSearchClass *)CAMEL_OBJECT_GET_CLASS(search);
230
231         for (i=0;i<sizeof(builtins)/sizeof(builtins[0]);i++) {
232                 void *func;
233                 /* c is sure messy sometimes */
234                 func = *((void **)(((char *)klass)+builtins[i].offset));
235                 if (func == NULL && builtins[i].flags&1) {
236                         g_warning("Search class doesn't implement '%s' method: %s", builtins[i].name, camel_type_to_name(CAMEL_OBJECT_GET_CLASS(search)));
237                         func = (void *)search_dummy;
238                 }
239                 if (func != NULL) {
240                         if (builtins[i].flags&2) {
241                                 e_sexp_add_ifunction(search->sexp, 0, builtins[i].name, (ESExpIFunc *)func, search);
242                         } else {
243                                 e_sexp_add_function(search->sexp, 0, builtins[i].name, (ESExpFunc *)func, search);
244                         }
245                 }
246         }
247 }
248
249 /**
250  * camel_folder_search_new:
251  *
252  * Create a new CamelFolderSearch object.
253  * 
254  * A CamelFolderSearch is a subclassable, extensible s-exp
255  * evaluator which enforces a particular set of s-expressions.
256  * Particular methods may be overriden by an implementation to
257  * implement a search for any sort of backend.
258  *
259  * Return value: A new CamelFolderSearch widget.
260  **/
261 CamelFolderSearch *
262 camel_folder_search_new (void)
263 {
264         CamelFolderSearch *new = CAMEL_FOLDER_SEARCH (camel_object_new (camel_folder_search_get_type ()));
265
266         camel_folder_search_construct(new);
267         return new;
268 }
269
270 /**
271  * camel_folder_search_set_folder:
272  * @search:
273  * @folder: A folder.
274  * 
275  * Set the folder attribute of the search.  This is currently unused, but
276  * could be used to perform a slow-search when indexes and so forth are not
277  * available.  Or for use by subclasses.
278  **/
279 void
280 camel_folder_search_set_folder(CamelFolderSearch *search, CamelFolder *folder)
281 {
282         search->folder = folder;
283 }
284
285 /**
286  * camel_folder_search_set_summary:
287  * @search: 
288  * @summary: An array of CamelMessageInfo pointers.
289  * 
290  * Set the array of summary objects representing the span of the search.
291  *
292  * If this is not set, then a subclass must provide the functions
293  * for searching headers and for the match-all operator.
294  **/
295 void
296 camel_folder_search_set_summary(CamelFolderSearch *search, GPtrArray *summary)
297 {
298         search->summary = summary;
299 }
300
301 /**
302  * camel_folder_search_set_body_index:
303  * @search: 
304  * @index: 
305  * 
306  * Set the index representing the contents of all messages
307  * in this folder.  If this is not set, then the folder implementation
308  * should sub-class the CamelFolderSearch and provide its own
309  * body-contains function.
310  **/
311 void
312 camel_folder_search_set_body_index(CamelFolderSearch *search, CamelIndex *index)
313 {
314         if (search->body_index)
315                 camel_object_unref((CamelObject *)search->body_index);
316         search->body_index = index;
317         if (index)
318                 camel_object_ref((CamelObject *)index);
319 }
320
321 /**
322  * camel_folder_search_execute_expression:
323  * @search: 
324  * @expr: 
325  * @ex: 
326  * 
327  * Execute the search expression @expr, returning an array of
328  * all matches as a GPtrArray of uid's of matching messages.
329  *
330  * Note that any settings such as set_body_index(), set_folder(),
331  * and so on are reset to #NULL once the search has completed.
332  *
333  * TODO: The interface should probably return summary items instead
334  * (since they are much more useful to any client).
335  * 
336  * Return value: A GPtrArray of strings of all matching messages.
337  * This must only be freed by camel_folder_search_free_result.
338  **/
339 GPtrArray *
340 camel_folder_search_execute_expression(CamelFolderSearch *search, const char *expr, CamelException *ex)
341 {
342         ESExpResult *r;
343         GPtrArray *matches;
344         int i;
345         GHashTable *results;
346         EMemPool *pool;
347         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
348
349         p->ex = ex;
350
351         /* only re-parse if the search has changed */
352         if (search->last_search == NULL
353             || strcmp(search->last_search, expr)) {
354                 e_sexp_input_text(search->sexp, expr, strlen(expr));
355                 if (e_sexp_parse(search->sexp) == -1) {
356                         camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
357                         return NULL;
358                 }
359
360                 g_free(search->last_search);
361                 search->last_search = g_strdup(expr);
362         }
363         r = e_sexp_eval(search->sexp);
364         if (r == NULL) {
365                 if (!camel_exception_is_set(ex))
366                         camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
367                 return NULL;
368         }
369
370         matches = g_ptr_array_new();
371
372         /* now create a folder summary to return?? */
373         if (r->type == ESEXP_RES_ARRAY_PTR) {
374                 d(printf("got result ...\n"));
375                 /* we use a mempool to store the strings, packed in tight as possible, and freed together */
376                 /* because the strings are often short (like <8 bytes long), we would be wasting appx 50%
377                    of memory just storing the size tag that malloc assigns us and alignment padding, so this
378                    gets around that (and is faster to allocate and free as a bonus) */
379                 pool = e_mempool_new(512, 256, E_MEMPOOL_ALIGN_BYTE);
380                 if (search->summary) {
381                         /* reorder result in summary order */
382                         results = g_hash_table_new(g_str_hash, g_str_equal);
383                         for (i=0;i<r->value.ptrarray->len;i++) {
384                                 d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
385                                 g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
386                         }
387                         for (i=0;i<search->summary->len;i++) {
388                                 char *uid = g_ptr_array_index(search->summary, i);
389                                 if (g_hash_table_lookup(results, uid)) {
390                                         g_ptr_array_add(matches, e_mempool_strdup(pool, uid));
391                                 }
392                         }
393                         g_hash_table_destroy(results);
394                 } else {
395                         for (i=0;i<r->value.ptrarray->len;i++) {
396                                 d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
397                                 g_ptr_array_add(matches, e_mempool_strdup(pool, g_ptr_array_index(r->value.ptrarray, i)));
398                         }
399                 }
400                 /* instead of putting the mempool_hash in the structure, we keep the api clean by
401                    putting a reference to it in a hashtable.  Lets us do some debugging and catch
402                    unfree'd results as well. */
403                 g_hash_table_insert(p->mempool_hash, matches, pool);
404         } else {
405                 g_warning("Search returned an invalid result type");
406         }
407
408         e_sexp_result_free(search->sexp, r);
409
410         if (p->threads)
411                 camel_folder_thread_messages_unref(p->threads);
412         if (p->threads_hash)
413                 g_hash_table_destroy(p->threads_hash);
414
415         p->threads = NULL;
416         p->threads_hash = NULL;
417         search->folder = NULL;
418         search->summary = NULL;
419         search->current = NULL;
420         search->body_index = NULL;
421
422         return matches;
423 }
424
425 /**
426  * camel_folder_search_search:
427  * @search: 
428  * @expr: 
429  * @uids: to search against, NULL for all uid's.
430  * @ex: 
431  * 
432  * Run a search.  Search must have had Folder already set on it, and
433  * it must implement summaries.
434  * 
435  * Return value: 
436  **/
437 GPtrArray *
438 camel_folder_search_search(CamelFolderSearch *search, const char *expr, GPtrArray *uids, CamelException *ex)
439 {
440         ESExpResult *r;
441         GPtrArray *matches = NULL, *summary_set;
442         int i;
443         CamelDB *cdb;
444         char *sql_query, *tmp, *tmp1;
445
446         /*
447            GHashTable *results;
448            EMemPool *pool;
449          */
450
451         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
452
453         g_assert(search->folder);
454         
455         /* Sync the db, so that we search the db for changes */
456         camel_folder_summary_save_to_db (search->folder->summary, ex);
457
458         p->ex = ex;
459
460         /* setup our search list only contains those we're interested in */
461         search->summary = camel_folder_get_summary(search->folder);
462
463         if (uids) {
464                 GHashTable *uids_hash = g_hash_table_new(g_str_hash, g_str_equal);
465
466                 summary_set = search->summary_set = g_ptr_array_new();
467                 for (i=0;i<uids->len;i++)
468                         g_hash_table_insert(uids_hash, uids->pdata[i], uids->pdata[i]);
469                 for (i=0;i<search->summary->len;i++)
470                         if (g_hash_table_lookup(uids_hash, search->summary->pdata[i]))
471                                 g_ptr_array_add(search->summary_set, search->summary->pdata[i]);
472                 g_hash_table_destroy(uids_hash);
473         } else {
474                 summary_set = search->summary;
475         }
476
477         /* only re-parse if the search has changed */
478         if (search->last_search == NULL
479             || strcmp(search->last_search, expr)) {
480                 e_sexp_input_text(search->sexp, expr, strlen(expr));
481                 if (e_sexp_parse(search->sexp) == -1) {
482                         camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
483                         goto fail;
484                 }
485
486                 g_free(search->last_search);
487                 search->last_search = g_strdup(expr);
488         }
489         //r = e_sexp_eval(search->sexp);
490         if (0 && r == NULL) {
491                 if (!camel_exception_is_set(ex))
492                         camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
493                 goto fail;
494         }
495
496         d(printf ("sexp is : [%s]\n", expr));
497         sql_query = camel_sexp_to_sql (expr);
498         tmp1 = camel_db_sqlize_string(search->folder->full_name);
499         tmp = g_strdup_printf ("SELECT uid FROM %s WHERE %s", tmp1,  sql_query);
500         camel_db_free_sqlized_string (tmp1);
501         g_free (sql_query);
502         d(printf("Equivalent sql %s\n", tmp));
503         
504         matches = g_ptr_array_new();
505         cdb = (CamelDB *) (search->folder->cdb);
506         camel_db_select (cdb, tmp, (CamelDBSelectCB) read_uid_callback, matches, ex);
507         g_free(tmp);
508
509
510         #if 0
511         //e_sexp_result_free(search->sexp, r);
512
513         /* now create a folder summary to return?? */
514         if (r->type == ESEXP_RES_ARRAY_PTR) {
515                 d(printf("got result ...\n"));
516
517                 /* we use a mempool to store the strings, packed in tight as possible, and freed together */
518                 /* because the strings are often short (like <8 bytes long), we would be wasting appx 50%
519                    of memory just storing the size tag that malloc assigns us and alignment padding, so this
520                    gets around that (and is faster to allocate and free as a bonus) */
521                 pool = e_mempool_new(512, 256, E_MEMPOOL_ALIGN_BYTE);
522                 /* reorder result in summary order */
523                 results = g_hash_table_new(g_str_hash, g_str_equal);
524                 for (i=0;i<r->value.ptrarray->len;i++) {
525                         d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
526                         g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
527                 }
528
529                 for (i=0;i<summary_set->len;i++) {
530                         CamelMessageInfo *info = g_ptr_array_index(summary_set, i);
531                         char *uid = (char *)camel_message_info_uid(info);
532                         if (g_hash_table_lookup(results, uid))
533                                 g_ptr_array_add(matches, e_mempool_strdup(pool, uid));
534                 }
535                 g_hash_table_destroy(results);
536
537                 /* instead of putting the mempool_hash in the structure, we keep the api clean by
538                    putting a reference to it in a hashtable.  Lets us do some debugging and catch
539                    unfree'd results as well. */
540                 g_hash_table_insert(p->mempool_hash, matches, pool);
541         } else {
542                 g_warning("Search returned an invalid result type");
543         }
544
545         e_sexp_result_free(search->sexp, r);
546         #endif
547
548 fail:
549         /* these might be allocated by match-threads */
550         if (p->threads)
551                 camel_folder_thread_messages_unref(p->threads);
552         if (p->threads_hash)
553                 g_hash_table_destroy(p->threads_hash);
554         if (search->summary_set)
555                 g_ptr_array_free(search->summary_set, TRUE);
556         camel_folder_free_summary(search->folder, search->summary);
557
558         p->threads = NULL;
559         p->threads_hash = NULL;
560         search->folder = NULL;
561         search->summary = NULL;
562         search->summary_set = NULL;
563         search->current = NULL;
564         search->body_index = NULL;
565
566         return matches;
567 }
568
569 void camel_folder_search_free_result(CamelFolderSearch *search, GPtrArray *result)
570 {
571 #if 0
572         int i;
573         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
574         EMemPool *pool;
575
576         pool = g_hash_table_lookup(p->mempool_hash, result);
577         if (pool) {
578                 e_mempool_destroy(pool);
579                 g_hash_table_remove(p->mempool_hash, result);
580         } else {
581                 for (i=0;i<result->len;i++)
582                         g_free(g_ptr_array_index(result, i));
583         }
584 #endif
585         g_ptr_array_foreach (result, (GFunc) camel_pstring_free, NULL);
586         g_ptr_array_free(result, TRUE);
587 }
588
589 /* dummy function, returns false always, or an empty match array */
590 static ESExpResult *
591 search_dummy(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
592 {
593         ESExpResult *r;
594
595         if (search->current == NULL) {
596                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
597                 r->value.bool = FALSE;
598         } else {
599                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
600                 r->value.ptrarray = g_ptr_array_new();
601         }
602
603         return r;
604 }
605
606 /* impelemnt an 'array not', i.e. everything in the summary, not in the supplied array */
607 static ESExpResult *
608 search_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
609 {
610         ESExpResult *r;
611         int i;
612
613         if (argc>0) {
614                 if (argv[0]->type == ESEXP_RES_ARRAY_PTR) {
615                         GPtrArray *v = argv[0]->value.ptrarray;
616                         const char *uid;
617
618                         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
619                         r->value.ptrarray = g_ptr_array_new();
620
621                         /* not against a single message?*/
622                         if (search->current) {
623                                 int found = FALSE;
624
625                                 uid = camel_message_info_uid(search->current);
626                                 for (i=0;!found && i<v->len;i++) {
627                                         if (strcmp(uid, v->pdata[i]) == 0)
628                                                 found = TRUE;
629                                 }
630
631                                 if (!found)
632                                         g_ptr_array_add(r->value.ptrarray, (char *)uid);
633                         } else if (search->summary == NULL) {
634                                 g_warning("No summary set, 'not' against an array requires a summary");
635                         } else {
636                                 /* 'not' against the whole summary */
637                                 GHashTable *have = g_hash_table_new(g_str_hash, g_str_equal);
638                                 char **s;
639                                 char **m;
640
641                                 s = (char **)v->pdata;
642                                 for (i=0;i<v->len;i++)
643                                         g_hash_table_insert(have, s[i], s[i]);
644
645                                 v = search->summary_set?search->summary_set:search->summary;
646                                 m = (char **)v->pdata;
647                                 for (i=0;i<v->len;i++) {
648                                         char *uid = m[i];
649
650                                         if (g_hash_table_lookup(have, uid) == NULL)
651                                                 g_ptr_array_add(r->value.ptrarray, uid);
652                                 }
653                                 g_hash_table_destroy(have);
654                         }
655                 } else {
656                         int res = TRUE;
657
658                         if (argv[0]->type == ESEXP_RES_BOOL)
659                                 res = ! argv[0]->value.bool;
660
661                         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
662                         r->value.bool = res;
663                 }
664         } else {
665                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
666                 r->value.bool = TRUE;
667         }
668
669         return r;
670 }
671
672 static ESExpResult *
673 search_match_all(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
674 {
675         ESExpResult *r, *r1;
676         gchar *error_msg;
677
678         if (argc>1) {
679                 g_warning("match-all only takes a single argument, other arguments ignored");
680         }
681
682         /* we are only matching a single message?  or already inside a match-all? */
683         if (search->current) {
684                 d(printf("matching against 1 message: %s\n", camel_message_info_subject(search->current)));
685
686                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
687                 r->value.bool = FALSE;
688
689                 if (argc>0) {
690                         r1 = e_sexp_term_eval(f, argv[0]);
691                         if (r1->type == ESEXP_RES_BOOL) {
692                                 r->value.bool = r1->value.bool;
693                         } else {
694                                 g_warning("invalid syntax, matches require a single bool result");
695                                 error_msg = g_strdup_printf(_("(%s) requires a single bool result"), "match-all");
696                                 e_sexp_fatal_error(f, error_msg);
697                                 g_free(error_msg);
698                         }
699                         e_sexp_result_free(f, r1);
700                 } else {
701                         r->value.bool = TRUE;
702                 }
703                 return r;
704         }
705
706         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
707         r->value.ptrarray = g_ptr_array_new();
708
709         if (search->summary == NULL) {
710                 /* TODO: make it work - e.g. use the folder and so forth for a slower search */
711                 g_warning("No summary supplied, match-all doesn't work with no summary");
712                 g_assert(0);
713                 return r;
714         }
715
716 #if 0
717         v = search->summary_set?search->summary_set:search->summary;
718         
719         if (v->len - g_hash_table_size (search->folder->summary->loaded_infos) > 50 && !CAMEL_IS_VEE_FOLDER (search->folder)) {
720                 /* Load the DB contents. FIXME this 100 needs to be a better threshold to reload from DB. */
721                 #warning "handle exception"
722                 camel_folder_summary_reload_from_db (search->folder->summary, NULL);
723         } 
724
725 #endif  
726         e_sexp_term_eval (f, argv [0]);
727 #if 0
728         for (i=0;i<v->len;i++) {
729                 const char *uid;
730
731                 search->current = camel_folder_summary_uid (search->folder->summary, v->pdata[i]);
732                 uid = camel_message_info_uid(search->current);
733
734                 if (argc>0) {
735                         r1 = e_sexp_term_eval(f, argv[0]);
736                         if (r1->type == ESEXP_RES_BOOL) {
737                                 if (r1->value.bool)
738                                         g_ptr_array_add(r->value.ptrarray, (char *)uid);
739                         } else {
740                                 g_warning("invalid syntax, matches require a single bool result");
741                                 error_msg = g_strdup_printf(_("(%s) requires a single bool result"), "match-all");
742                                 e_sexp_fatal_error(f, error_msg);
743                                 g_free(error_msg);
744                         }
745                         e_sexp_result_free(f, r1);
746                 } else {
747                         g_ptr_array_add(r->value.ptrarray, (char *)uid);
748                 }
749                 camel_message_info_free (search->current);
750         }
751 #endif  
752         search->current = NULL;
753         return r;
754 }
755
756 //FIXME Check threads mis
757 static void
758 fill_thread_table(struct _CamelFolderThreadNode *root, GHashTable *id_hash)
759 {
760         while (root) {
761                 g_hash_table_insert(id_hash, (char *)camel_message_info_uid(root->message), root);
762                 if (root->child)
763                         fill_thread_table(root->child, id_hash);
764                 root = root->next;
765         }
766 }
767
768 static void
769 add_thread_results(struct _CamelFolderThreadNode *root, GHashTable *result_hash)
770 {
771         while (root) {
772                 g_hash_table_insert(result_hash, (char *)camel_message_info_uid(root->message), GINT_TO_POINTER (1));
773                 if (root->child)
774                         add_thread_results(root->child, result_hash);
775                 root = root->next;
776         }
777 }
778
779 static void
780 add_results(char *uid, void *dummy, GPtrArray *result)
781 {
782         g_ptr_array_add(result, uid);
783 }
784
785 static ESExpResult *
786 search_match_threads(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
787 {
788         ESExpResult *r;
789         struct _CamelFolderSearchPrivate *p = search->priv;
790         int i, type;
791         GHashTable *results;
792         gchar *error_msg;
793
794         /* not supported in match-all */
795         if (search->current) {
796                 error_msg = g_strdup_printf(_("(%s) not allowed inside %s"), "match-threads", "match-all");
797                 e_sexp_fatal_error(f, error_msg);
798                 g_free(error_msg);
799         }
800
801         if (argc == 0) {
802                 error_msg = g_strdup_printf(_("(%s) requires a match type string"), "match-threads");
803                 e_sexp_fatal_error(f, error_msg);
804                 g_free(error_msg);
805         }
806
807         r = e_sexp_term_eval(f, argv[0]);
808         if (r->type != ESEXP_RES_STRING) {
809                 error_msg = g_strdup_printf(_("(%s) requires a match type string"), "match-threads");
810                 e_sexp_fatal_error(f, error_msg);
811                 g_free(error_msg);
812         }
813
814         type = 0;
815         if (!strcmp(r->value.string, "none"))
816                 type = 0;
817         else if (!strcmp(r->value.string, "all"))
818                 type = 1;
819         else if (!strcmp(r->value.string, "replies"))
820                 type = 2;
821         else if (!strcmp(r->value.string, "replies_parents"))
822                 type = 3;
823         else if (!strcmp(r->value.string, "single"))
824                 type = 4;
825         e_sexp_result_free(f, r);
826
827         /* behave as (begin does */
828         r = NULL;
829         for (i=1;i<argc;i++) {
830                 if (r)
831                         e_sexp_result_free(f, r);
832                 r = e_sexp_term_eval(f, argv[i]);
833         }
834
835         if (r == NULL || r->type != ESEXP_RES_ARRAY_PTR) {
836                 error_msg = g_strdup_printf(_("(%s) expects an array result"), "match-threads");
837                 e_sexp_fatal_error(f, error_msg);
838                 g_free(error_msg);
839         }
840
841         if (type == 0)
842                 return r;
843
844         if (search->folder == NULL) {
845                 error_msg = g_strdup_printf(_("(%s) requires the folder set"), "match-threads");
846                 e_sexp_fatal_error(f, error_msg);
847                 g_free(error_msg);
848         }
849
850         /* cache this, so we only have to re-calculate once per search at most */
851         #warning "make search threads work well. Not sure if that works"
852         if (p->threads == NULL) {
853                 p->threads = camel_folder_thread_messages_new(search->folder, NULL, TRUE);
854                 p->threads_hash = g_hash_table_new(g_str_hash, g_str_equal);
855
856                 fill_thread_table(p->threads->tree, p->threads_hash);
857         }
858
859         results = g_hash_table_new(g_str_hash, g_str_equal);
860         for (i=0;i<r->value.ptrarray->len;i++) {
861                 struct _CamelFolderThreadNode *node, *scan;
862
863                 if (type != 4)
864                         g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER(1));
865
866                 node = g_hash_table_lookup(p->threads_hash, (char *)g_ptr_array_index(r->value.ptrarray, i));
867                 if (node == NULL) /* this shouldn't happen but why cry over spilt milk */
868                         continue;
869
870                 /* select messages in thread according to search criteria */
871                 if (type == 4) {
872                         if (node->child == NULL && node->parent == NULL)
873                                 g_hash_table_insert(results, (char *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
874                 } else {
875                         if (type == 3) {
876                                 scan = node;
877                                 while (scan && scan->parent) {
878                                         scan = scan->parent;
879                                         g_hash_table_insert(results, (char *)camel_message_info_uid(scan->message), GINT_TO_POINTER(1));
880                                 }
881                         } else if (type == 1) {
882                                 while (node && node->parent)
883                                         node = node->parent;
884                         }
885                         g_hash_table_insert(results, (char *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
886                         if (node->child)
887                                 add_thread_results(node->child, results);
888                 }
889         }
890         e_sexp_result_free(f, r);
891
892         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
893         r->value.ptrarray = g_ptr_array_new();
894
895         g_hash_table_foreach(results, (GHFunc)add_results, r->value.ptrarray);
896         g_hash_table_destroy(results);
897
898         return r;
899 }
900
901 static ESExpResult *
902 check_header (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search, camel_search_match_t how)
903 {
904         ESExpResult *r;
905         int truth = FALSE;
906
907         r(printf("executing check-header %d\n", how));
908
909         /* are we inside a match-all? */
910         if (search->current && argc>1
911             && argv[0]->type == ESEXP_RES_STRING) {
912                 char *headername;
913                 const char *header = NULL;
914                 char strbuf[32];
915                 int i, j;
916                 camel_search_t type = CAMEL_SEARCH_TYPE_ASIS;
917                 struct _camel_search_words *words;
918
919                 /* only a subset of headers are supported .. */
920                 headername = argv[0]->value.string;
921                 if (!g_ascii_strcasecmp(headername, "subject")) {
922                         header = camel_message_info_subject(search->current);
923                 } else if (!g_ascii_strcasecmp(headername, "date")) {
924                         /* FIXME: not a very useful form of the date */
925                         sprintf(strbuf, "%d", (int)camel_message_info_date_sent(search->current));
926                         header = strbuf;
927                 } else if (!g_ascii_strcasecmp(headername, "from")) {
928                         header = camel_message_info_from(search->current);
929                         type = CAMEL_SEARCH_TYPE_ADDRESS;
930                 } else if (!g_ascii_strcasecmp(headername, "to")) {
931                         header = camel_message_info_to(search->current);
932                         type = CAMEL_SEARCH_TYPE_ADDRESS;
933                 } else if (!g_ascii_strcasecmp(headername, "cc")) {
934                         header = camel_message_info_cc(search->current);
935                         type = CAMEL_SEARCH_TYPE_ADDRESS;
936                 } else if (!g_ascii_strcasecmp(headername, "x-camel-mlist")) {
937                         header = camel_message_info_mlist(search->current);
938                         type = CAMEL_SEARCH_TYPE_MLIST;
939                 } else {
940                         e_sexp_resultv_free(f, argc, argv);
941                         e_sexp_fatal_error(f, _("Performing query on unknown header: %s"), headername);
942                 }
943
944                 if (header == NULL)
945                         header = "";
946
947                 /* performs an OR of all words */
948                 for (i=1;i<argc && !truth;i++) {
949                         if (argv[i]->type == ESEXP_RES_STRING) {
950                                 if (argv[i]->value.string[0] == 0) {
951                                         truth = TRUE;
952                                 } else if (how == CAMEL_SEARCH_MATCH_CONTAINS) {
953                                         /* doesn't make sense to split words on anything but contains i.e. we can't have an ending match different words */
954                                         words = camel_search_words_split((const unsigned char *) argv[i]->value.string);
955                                         truth = TRUE;
956                                         for (j=0;j<words->len && truth;j++) {
957                                                 truth = camel_search_header_match(header, words->words[j]->word, how, type, NULL);
958                                         }
959                                         camel_search_words_free(words);
960                                 } else {
961                                         truth = camel_search_header_match(header, argv[i]->value.string, how, type, NULL);
962                                 }
963                         }
964                 }
965         }
966         /* TODO: else, find all matches */
967
968         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
969         r->value.bool = truth;
970
971         return r;
972 }
973
974 /*
975 static void 
976 l_printf(char *node)
977 {
978 printf("%s\t", node);
979 }
980 */
981
982 static ESExpResult *
983 search_header_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
984 {
985         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS);
986 }
987
988 static ESExpResult *
989 search_header_matches(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
990 {
991         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT);
992 }
993
994 static ESExpResult *
995 search_header_starts_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
996 {
997         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS);
998 }
999
1000 static ESExpResult *
1001 search_header_ends_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1002 {
1003         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS);
1004 }
1005
1006 static ESExpResult *
1007 search_header_exists (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1008 {
1009         ESExpResult *r;
1010         
1011         r(printf ("executing header-exists\n"));
1012         
1013         if (search->current) {
1014                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1015                 if (argc == 1 && argv[0]->type == ESEXP_RES_STRING)
1016                         r->value.bool = camel_medium_get_header(CAMEL_MEDIUM(search->current), argv[0]->value.string) != NULL;
1017                 
1018         } else {
1019                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1020                 r->value.ptrarray = g_ptr_array_new();
1021         }
1022         
1023         return r;
1024 }
1025
1026 /* this is just to OR results together */
1027 struct _glib_sux_donkeys {
1028         int count;
1029         GPtrArray *uids;
1030 };
1031
1032 /* or, store all unique values */
1033 static void
1034 g_lib_sux_htor(char *key, int value, struct _glib_sux_donkeys *fuckup)
1035 {
1036         g_ptr_array_add(fuckup->uids, key);
1037 }
1038
1039 /* and, only store duplicates */
1040 static void
1041 g_lib_sux_htand(char *key, int value, struct _glib_sux_donkeys *fuckup)
1042 {
1043         if (value == fuckup->count)
1044                 g_ptr_array_add(fuckup->uids, key);
1045 }
1046
1047 static int
1048 match_message_index(CamelIndex *idx, const char *uid, const char *match, CamelException *ex)
1049 {
1050         CamelIndexCursor *wc, *nc;
1051         const char *word, *name;
1052         int truth = FALSE;
1053
1054         wc = camel_index_words(idx);
1055         if (wc) {
1056                 while (!truth && (word = camel_index_cursor_next(wc))) {
1057                         if (camel_ustrstrcase(word,match) != NULL) {
1058                                 /* perf: could have the wc cursor return the name cursor */
1059                                 nc = camel_index_find(idx, word);
1060                                 if (nc) {
1061                                         while (!truth && (name = camel_index_cursor_next(nc)))
1062                                                 truth = strcmp(name, uid) == 0;
1063                                         camel_object_unref((CamelObject *)nc);
1064                                 }
1065                         }
1066                 }
1067                 camel_object_unref((CamelObject *)wc);
1068         }
1069
1070         return truth;
1071 }
1072
1073 /*
1074  "one two" "three" "four five"
1075
1076   one and two
1077 or
1078   three
1079 or
1080   four and five
1081 */
1082
1083 /* returns messages which contain all words listed in words */
1084 static GPtrArray *
1085 match_words_index(CamelFolderSearch *search, struct _camel_search_words *words, CamelException *ex)
1086 {
1087         GPtrArray *result = g_ptr_array_new();
1088         GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
1089         struct _glib_sux_donkeys lambdafoo;
1090         CamelIndexCursor *wc, *nc;
1091         const char *word, *name;
1092         int i;
1093
1094         /* we can have a maximum of 32 words, as we use it as the AND mask */
1095                         
1096         wc = camel_index_words(search->body_index);
1097         if (wc) {
1098                 while ((word = camel_index_cursor_next(wc))) {
1099                         for (i=0;i<words->len;i++) {
1100                                 if (camel_ustrstrcase(word, words->words[i]->word) != NULL) {
1101                                         /* perf: could have the wc cursor return the name cursor */
1102                                         nc = camel_index_find(search->body_index, word);
1103                                         if (nc) {
1104                                                 while ((name = camel_index_cursor_next(nc))) {
1105                                                                 int mask;
1106
1107                                                                 mask = (GPOINTER_TO_INT(g_hash_table_lookup(ht, name))) | (1<<i);
1108                                                                 g_hash_table_insert(ht, (char *)name, GINT_TO_POINTER(mask));
1109                                                 }
1110                                                 camel_object_unref((CamelObject *)nc);
1111                                         }
1112                                 }
1113                         }
1114                 }
1115                 camel_object_unref((CamelObject *)wc);
1116
1117                 lambdafoo.uids = result;
1118                 lambdafoo.count = (1<<words->len) - 1;
1119                 g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htand, &lambdafoo);
1120                 g_hash_table_destroy(ht);
1121         }
1122
1123         return result;
1124 }
1125
1126 static gboolean
1127 match_words_1message (CamelDataWrapper *object, struct _camel_search_words *words, guint32 *mask)
1128 {
1129         CamelDataWrapper *containee;
1130         int truth = FALSE;
1131         int parts, i;
1132         
1133         containee = camel_medium_get_content_object (CAMEL_MEDIUM (object));
1134         
1135         if (containee == NULL)
1136                 return FALSE;
1137         
1138         /* using the object types is more accurate than using the mime/types */
1139         if (CAMEL_IS_MULTIPART (containee)) {
1140                 parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
1141                 for (i = 0; i < parts && truth == FALSE; i++) {
1142                         CamelDataWrapper *part = (CamelDataWrapper *)camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
1143                         if (part)
1144                                 truth = match_words_1message(part, words, mask);
1145                 }
1146         } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
1147                 /* for messages we only look at its contents */
1148                 truth = match_words_1message((CamelDataWrapper *)containee, words, mask);
1149         } else if (camel_content_type_is(CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")) {
1150                 /* for all other text parts, we look inside, otherwise we dont care */
1151                 CamelStreamMem *mem = (CamelStreamMem *)camel_stream_mem_new ();
1152
1153                 /* FIXME: The match should be part of a stream op */
1154                 camel_data_wrapper_decode_to_stream (containee, CAMEL_STREAM (mem));
1155                 camel_stream_write (CAMEL_STREAM (mem), "", 1);
1156                 for (i=0;i<words->len;i++) {
1157                         /* FIXME: This is horridly slow, and should use a real search algorithm */
1158                         if (camel_ustrstrcase((const char *) mem->buffer->data, words->words[i]->word) != NULL) {
1159                                 *mask |= (1<<i);
1160                                 /* shortcut a match */
1161                                 if (*mask == (1<<(words->len))-1)
1162                                         return TRUE;
1163                         }
1164                 }
1165                 
1166                 camel_object_unref (mem);
1167         }
1168         
1169         return truth;
1170 }
1171
1172 static gboolean
1173 match_words_message(CamelFolder *folder, const char *uid, struct _camel_search_words *words, CamelException *ex)
1174 {
1175         guint32 mask;
1176         CamelMimeMessage *msg;
1177         CamelException x = CAMEL_EXCEPTION_INITIALISER;
1178         int truth;
1179
1180         msg = camel_folder_get_message(folder, uid, &x);
1181         if (msg) {
1182                 mask = 0;
1183                 truth = match_words_1message((CamelDataWrapper *)msg, words, &mask);
1184                 camel_object_unref((CamelObject *)msg);
1185         } else {
1186                 camel_exception_clear(&x);
1187                 truth = FALSE;
1188         }
1189
1190         return truth;
1191 }
1192
1193 static GPtrArray *
1194 match_words_messages(CamelFolderSearch *search, struct _camel_search_words *words, CamelException *ex)
1195 {
1196         int i;
1197         GPtrArray *matches = g_ptr_array_new();
1198
1199         if (search->body_index) {
1200                 GPtrArray *indexed;
1201                 struct _camel_search_words *simple;
1202
1203                 simple = camel_search_words_simple(words);
1204                 indexed = match_words_index(search, simple, ex);
1205                 camel_search_words_free(simple);
1206
1207                 for (i=0;i<indexed->len;i++) {
1208                         const char *uid = g_ptr_array_index(indexed, i);
1209                         
1210                         if (match_words_message(search->folder, uid, words, ex))
1211                                 g_ptr_array_add(matches, (char *)uid);
1212                 }
1213                 
1214                 g_ptr_array_free(indexed, TRUE);
1215         } else {
1216                 GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1217
1218                 for (i=0;i<v->len;i++) {
1219                         char *uid  = g_ptr_array_index(v, i);
1220                         
1221                         if (match_words_message(search->folder, uid, words, ex))
1222                                 g_ptr_array_add(matches, (char *)uid);
1223                 }
1224         }
1225
1226         return matches;
1227 }
1228
1229 static ESExpResult *
1230 search_body_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1231 {
1232         int i, j;
1233         CamelException *ex = search->priv->ex;
1234         struct _camel_search_words *words;
1235         ESExpResult *r;
1236         struct _glib_sux_donkeys lambdafoo;
1237
1238         if (search->current) {  
1239                 int truth = FALSE;
1240
1241                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1242                         truth = TRUE;
1243                 } else {
1244                         for (i=0;i<argc && !truth;i++) {
1245                                 if (argv[i]->type == ESEXP_RES_STRING) {
1246                                         words = camel_search_words_split((const unsigned char *) argv[i]->value.string);
1247                                         truth = TRUE;
1248                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1249                                                 for (j=0;j<words->len && truth;j++)
1250                                                         truth = match_message_index(search->body_index, camel_message_info_uid(search->current), words->words[j]->word, ex);
1251                                         } else {
1252                                                 /* TODO: cache current message incase of multiple body search terms */
1253                                                 truth = match_words_message(search->folder, camel_message_info_uid(search->current), words, ex);
1254                                         }
1255                                         camel_search_words_free(words);
1256                                 }
1257                         }
1258                 }
1259                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1260                 r->value.bool = truth;
1261         } else {
1262                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1263                 r->value.ptrarray = g_ptr_array_new();
1264
1265                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1266                         GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1267
1268                         for (i=0;i<v->len;i++) {
1269                                 char *uid = g_ptr_array_index(v, i);
1270
1271                                 g_ptr_array_add(r->value.ptrarray, uid);
1272                         }
1273                 } else {
1274                         GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
1275                         GPtrArray *matches;
1276
1277                         for (i=0;i<argc;i++) {
1278                                 if (argv[i]->type == ESEXP_RES_STRING) {
1279                                         words = camel_search_words_split((const unsigned char *) argv[i]->value.string);
1280                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1281                                                 matches = match_words_index(search, words, ex);
1282                                         } else {
1283                                                 matches = match_words_messages(search, words, ex);
1284                                         }
1285                                         for (j=0;j<matches->len;j++)
1286                                                 g_hash_table_insert(ht, matches->pdata[j], matches->pdata[j]);
1287                                         g_ptr_array_free(matches, TRUE);
1288                                         camel_search_words_free(words);
1289                                 }
1290                         }
1291                         lambdafoo.uids = r->value.ptrarray;
1292                         g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htor, &lambdafoo);
1293                         g_hash_table_destroy(ht);
1294                 }
1295         }
1296
1297         return r;
1298 }
1299
1300 static ESExpResult *
1301 search_user_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1302 {
1303         ESExpResult *r;
1304         int i;
1305
1306         r(printf("executing user-flag\n"));
1307
1308         /* are we inside a match-all? */
1309         if (search->current) {
1310                 int truth = FALSE;
1311                 /* performs an OR of all words */
1312                 for (i=0;i<argc && !truth;i++) {
1313                         if (argv[i]->type == ESEXP_RES_STRING
1314                             && camel_message_info_user_flag(search->current, argv[i]->value.string)) {
1315                                 truth = TRUE;
1316                                 break;
1317                         }
1318                 }
1319                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1320                 r->value.bool = truth;
1321         } else {
1322                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1323                 r->value.ptrarray = g_ptr_array_new();
1324         }
1325
1326         return r;
1327 }
1328
1329 static ESExpResult *
1330 search_system_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1331 {
1332         ESExpResult *r;
1333         
1334         r(printf ("executing system-flag\n"));
1335         
1336         if (search->current) {
1337                 gboolean truth = FALSE;
1338                 
1339                 if (argc == 1)
1340                         truth = camel_system_flag_get (camel_message_info_flags(search->current), argv[0]->value.string);
1341                 
1342                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1343                 r->value.bool = truth;
1344         } else {
1345                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1346                 r->value.ptrarray = g_ptr_array_new ();
1347         }
1348         
1349         return r;
1350 }
1351
1352 static ESExpResult *
1353 search_user_tag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1354 {
1355         const char *value = NULL;
1356         ESExpResult *r;
1357         
1358         r(printf("executing user-tag\n"));
1359         
1360         if (argc == 1)
1361                 value = camel_message_info_user_tag(search->current, argv[0]->value.string);
1362         
1363         r = e_sexp_result_new(f, ESEXP_RES_STRING);
1364         r->value.string = g_strdup (value ? value : "");
1365         
1366         return r;
1367 }
1368
1369 static ESExpResult *
1370 search_get_sent_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1371 {
1372         ESExpResult *r;
1373
1374         r(printf("executing get-sent-date\n"));
1375
1376         /* are we inside a match-all? */
1377         if (s->current) {
1378                 r = e_sexp_result_new(f, ESEXP_RES_INT);
1379
1380                 r->value.number = camel_message_info_date_sent(s->current);
1381         } else {
1382                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1383                 r->value.ptrarray = g_ptr_array_new ();
1384         }
1385
1386         return r;
1387 }
1388
1389 static ESExpResult *
1390 search_get_received_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1391 {
1392         ESExpResult *r;
1393
1394         r(printf("executing get-received-date\n"));
1395
1396         /* are we inside a match-all? */
1397         if (s->current) {
1398                 r = e_sexp_result_new(f, ESEXP_RES_INT);
1399
1400                 r->value.number = camel_message_info_date_received(s->current);
1401         } else {
1402                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1403                 r->value.ptrarray = g_ptr_array_new ();
1404         }
1405
1406         return r;
1407 }
1408
1409 static ESExpResult *
1410 search_get_current_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1411 {
1412         ESExpResult *r;
1413
1414         r(printf("executing get-current-date\n"));
1415
1416         r = e_sexp_result_new(f, ESEXP_RES_INT);
1417         r->value.number = time (NULL);
1418         return r;
1419 }
1420
1421 static ESExpResult *
1422 search_get_size (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1423 {
1424         ESExpResult *r;
1425         
1426         r(printf("executing get-size\n"));
1427         
1428         /* are we inside a match-all? */
1429         if (s->current) {
1430                 r = e_sexp_result_new (f, ESEXP_RES_INT);
1431                 r->value.number = camel_message_info_size(s->current) / 1024;
1432         } else {
1433                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1434                 r->value.ptrarray = g_ptr_array_new ();
1435         }
1436         
1437         return r;
1438 }
1439
1440 static ESExpResult *
1441 search_uid(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1442 {
1443         ESExpResult *r;
1444         int i;
1445
1446         r(printf("executing uid\n"));
1447
1448         /* are we inside a match-all? */
1449         if (search->current) {
1450                 int truth = FALSE;
1451                 const char *uid = camel_message_info_uid(search->current);
1452
1453                 /* performs an OR of all words */
1454                 for (i=0;i<argc && !truth;i++) {
1455                         if (argv[i]->type == ESEXP_RES_STRING
1456                             && !strcmp(uid, argv[i]->value.string)) {
1457                                 truth = TRUE;
1458                                 break;
1459                         }
1460                 }
1461                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1462                 r->value.bool = truth;
1463         } else {
1464                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1465                 r->value.ptrarray = g_ptr_array_new();
1466                 for (i=0;i<argc;i++) {
1467                         if (argv[i]->type == ESEXP_RES_STRING)
1468                                 g_ptr_array_add(r->value.ptrarray, argv[i]->value.string);
1469                 }
1470         }
1471
1472         return r;
1473 }
1474
1475 static int 
1476 read_uid_callback (void * ref, int ncol, char ** cols, char **name)
1477 {
1478         GPtrArray *matches;
1479
1480         matches = (GPtrArray *) ref;
1481
1482 #if 0
1483
1484         int i;
1485         for (i = 0; i < ncol; ++i) {
1486                 if ( !strcmp (name [i], "uid") ) 
1487                         g_ptr_array_add (matches, g_strdup (cols [i]));
1488         }
1489 #else
1490         g_ptr_array_add (matches, (GFunc) camel_pstring_strdup (cols [0]));
1491 #endif
1492         return 0;
1493 }