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