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