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