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