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