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