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