Merge some cleanup bits from camel-gobject.
[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/gi18n-lib.h>
39
40 #include "camel-exception.h"
41 #include "camel-folder-search.h"
42 #include "camel-folder-thread.h"
43 #include "camel-iconv.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-debug.h"
51 #include "camel-store.h"
52 #include "camel-vee-folder.h"
53 #include "camel-string-utils.h"
54 #include "camel-search-sql.h"
55 #include "camel-search-sql-sexp.h"
56
57 #define d(x)
58 #define r(x)
59 #define dd(x) if (camel_debug("search")) x
60
61 struct _CamelFolderSearchPrivate {
62         CamelException *ex;
63
64         CamelFolderThread *threads;
65         GHashTable *threads_hash;
66 };
67
68 #define _PRIVATE(o) (((CamelFolderSearch *)(o))->priv)
69
70 static ESExpResult *search_not(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
71
72 static ESExpResult *search_header_contains(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
73 static ESExpResult *search_header_matches(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
74 static ESExpResult *search_header_starts_with(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
75 static ESExpResult *search_header_ends_with(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
76 static ESExpResult *search_header_exists(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
77 static ESExpResult *search_header_soundex(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
78 static ESExpResult *search_header_regex(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
79 static ESExpResult *search_header_full_regex(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
80 static ESExpResult *search_match_all(struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search);
81 static ESExpResult *search_match_threads(struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *s);
82 static ESExpResult *search_body_contains(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
83 static ESExpResult *search_body_regex(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
84 static ESExpResult *search_user_flag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
85 static ESExpResult *search_user_tag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
86 static ESExpResult *search_system_flag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
87 static ESExpResult *search_get_sent_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
88 static ESExpResult *search_get_received_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
89 static ESExpResult *search_get_current_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
90 static ESExpResult *search_get_size(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
91 static ESExpResult *search_uid(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
92 static ESExpResult *search_message_location(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
93
94 static ESExpResult *search_dummy(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
95
96 static gint read_uid_callback (gpointer  ref, gint ncol, gchar ** cols, gchar **name);
97
98 static CamelObjectClass *camel_folder_search_parent;
99
100 static void
101 folder_search_finalize (CamelObject *obj)
102 {
103         CamelFolderSearch *search = (CamelFolderSearch *)obj;
104         struct _CamelFolderSearchPrivate *p = _PRIVATE(obj);
105
106         if (search->sexp)
107                 e_sexp_unref(search->sexp);
108
109         g_free(search->last_search);
110         g_free(p);
111 }
112
113 static void
114 camel_folder_search_class_init (CamelFolderSearchClass *class)
115 {
116         camel_folder_search_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
117
118         class->not = search_not;
119
120         class->match_all = search_match_all;
121         class->match_threads = search_match_threads;
122         class->body_contains = search_body_contains;
123         class->body_regex = search_body_regex;
124         class->header_contains = search_header_contains;
125         class->header_matches = search_header_matches;
126         class->header_starts_with = search_header_starts_with;
127         class->header_ends_with = search_header_ends_with;
128         class->header_exists = search_header_exists;
129         class->header_soundex = search_header_soundex;
130         class->header_regex = search_header_regex;
131         class->header_full_regex = search_header_full_regex;
132         class->user_tag = search_user_tag;
133         class->user_flag = search_user_flag;
134         class->system_flag = search_system_flag;
135         class->get_sent_date = search_get_sent_date;
136         class->get_received_date = search_get_received_date;
137         class->get_current_date = search_get_current_date;
138         class->get_size = search_get_size;
139         class->uid = search_uid;
140         class->message_location = search_message_location;
141 }
142
143 static void
144 camel_folder_search_init (CamelFolderSearch *obj)
145 {
146         struct _CamelFolderSearchPrivate *p;
147
148         p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
149
150         obj->sexp = e_sexp_new();
151 }
152
153 CamelType
154 camel_folder_search_get_type (void)
155 {
156         static CamelType type = CAMEL_INVALID_TYPE;
157
158         if (type == CAMEL_INVALID_TYPE) {
159                 type = camel_type_register (camel_object_get_type (), "CamelFolderSearch",
160                                             sizeof (CamelFolderSearch),
161                                             sizeof (CamelFolderSearchClass),
162                                             (CamelObjectClassInitFunc) camel_folder_search_class_init,
163                                             NULL,
164                                             (CamelObjectInitFunc) camel_folder_search_init,
165                                             (CamelObjectFinalizeFunc) folder_search_finalize);
166         }
167
168         return type;
169 }
170
171 static struct {
172         const gchar *name;
173         gint offset;
174         gint flags;             /* 0x02 = immediate, 0x01 = always enter */
175 } builtins[] = {
176         /* these have default implementations in e-sexp */
177         { "and", G_STRUCT_OFFSET(CamelFolderSearchClass, and), 2 },
178         { "or", G_STRUCT_OFFSET(CamelFolderSearchClass, or), 2 },
179         /* we need to override this one though to implement an 'array not' */
180         { "not", G_STRUCT_OFFSET(CamelFolderSearchClass, not), 0 },
181         { "<", G_STRUCT_OFFSET(CamelFolderSearchClass, lt), 2 },
182         { ">", G_STRUCT_OFFSET(CamelFolderSearchClass, gt), 2 },
183         { "=", G_STRUCT_OFFSET(CamelFolderSearchClass, eq), 2 },
184
185         /* these we have to use our own default if there is none */
186         /* they should all be defined in the language? so it parses, or should they not?? */
187         { "match-all", G_STRUCT_OFFSET(CamelFolderSearchClass, match_all), 3 },
188         { "match-threads", G_STRUCT_OFFSET(CamelFolderSearchClass, match_threads), 3 },
189         { "body-contains", G_STRUCT_OFFSET(CamelFolderSearchClass, body_contains), 1 },
190         { "body-regex",  G_STRUCT_OFFSET(CamelFolderSearchClass, body_regex), 1  },
191         { "header-contains", G_STRUCT_OFFSET(CamelFolderSearchClass, header_contains), 1 },
192         { "header-matches", G_STRUCT_OFFSET(CamelFolderSearchClass, header_matches), 1 },
193         { "header-starts-with", G_STRUCT_OFFSET(CamelFolderSearchClass, header_starts_with), 1 },
194         { "header-ends-with", G_STRUCT_OFFSET(CamelFolderSearchClass, header_ends_with), 1 },
195         { "header-exists", G_STRUCT_OFFSET(CamelFolderSearchClass, header_exists), 1 },
196         { "header-soundex", G_STRUCT_OFFSET(CamelFolderSearchClass, header_soundex), 1 },
197         { "header-regex", G_STRUCT_OFFSET(CamelFolderSearchClass, header_regex), 1 },
198         { "header-full-regex", G_STRUCT_OFFSET(CamelFolderSearchClass, header_full_regex), 1 },
199         { "user-tag", G_STRUCT_OFFSET(CamelFolderSearchClass, user_tag), 1 },
200         { "user-flag", G_STRUCT_OFFSET(CamelFolderSearchClass, user_flag), 1 },
201         { "system-flag", G_STRUCT_OFFSET(CamelFolderSearchClass, system_flag), 1 },
202         { "get-sent-date", G_STRUCT_OFFSET(CamelFolderSearchClass, get_sent_date), 1 },
203         { "get-received-date", G_STRUCT_OFFSET(CamelFolderSearchClass, get_received_date), 1 },
204         { "get-current-date", G_STRUCT_OFFSET(CamelFolderSearchClass, get_current_date), 1 },
205         { "get-size", G_STRUCT_OFFSET(CamelFolderSearchClass, get_size), 1 },
206         { "uid", G_STRUCT_OFFSET(CamelFolderSearchClass, uid), 1 },
207         { "message-location", G_STRUCT_OFFSET(CamelFolderSearchClass, message_location), 1 },
208 };
209
210 void
211 camel_folder_search_construct (CamelFolderSearch *search)
212 {
213         gint i;
214         CamelFolderSearchClass *class;
215
216         class = CAMEL_FOLDER_SEARCH_GET_CLASS (search);
217
218         for (i = 0; i < G_N_ELEMENTS (builtins); i++) {
219                 gpointer func;
220                 /* c is sure messy sometimes */
221                 func = *((gpointer *)(((gchar *)class)+builtins[i].offset));
222                 if (func == NULL && builtins[i].flags&1) {
223                         g_warning("Search class doesn't implement '%s' method: %s", builtins[i].name, camel_type_to_name(CAMEL_OBJECT_GET_CLASS(search)));
224                         func = (gpointer)search_dummy;
225                 }
226                 if (func != NULL) {
227                         if (builtins[i].flags&2) {
228                                 e_sexp_add_ifunction(search->sexp, 0, builtins[i].name, (ESExpIFunc *)func, search);
229                         } else {
230                                 e_sexp_add_function(search->sexp, 0, builtins[i].name, (ESExpFunc *)func, search);
231                         }
232                 }
233         }
234 }
235
236 /**
237  * camel_folder_search_new:
238  *
239  * Create a new CamelFolderSearch object.
240  *
241  * A CamelFolderSearch is a subclassable, extensible s-exp
242  * evaluator which enforces a particular set of s-expressions.
243  * Particular methods may be overriden by an implementation to
244  * implement a search for any sort of backend.
245  *
246  * Returns: A new CamelFolderSearch widget.
247  **/
248 CamelFolderSearch *
249 camel_folder_search_new (void)
250 {
251         CamelFolderSearch *new;
252
253         new = CAMEL_FOLDER_SEARCH (camel_object_new (camel_folder_search_get_type ()));
254         camel_folder_search_construct(new);
255
256         return new;
257 }
258
259 /**
260  * camel_folder_search_set_folder:
261  * @search:
262  * @folder: A folder.
263  *
264  * Set the folder attribute of the search.  This is currently unused, but
265  * could be used to perform a slow-search when indexes and so forth are not
266  * available.  Or for use by subclasses.
267  **/
268 void
269 camel_folder_search_set_folder(CamelFolderSearch *search, CamelFolder *folder)
270 {
271         search->folder = folder;
272 }
273
274 /**
275  * camel_folder_search_set_summary:
276  * @search:
277  * @summary: An array of CamelMessageInfo pointers.
278  *
279  * Set the array of summary objects representing the span of the search.
280  *
281  * If this is not set, then a subclass must provide the functions
282  * for searching headers and for the match-all operator.
283  **/
284 void
285 camel_folder_search_set_summary(CamelFolderSearch *search, GPtrArray *summary)
286 {
287         search->summary = summary;
288 }
289
290 /**
291  * camel_folder_search_set_body_index:
292  * @search:
293  * @index:
294  *
295  * Set the index representing the contents of all messages
296  * in this folder.  If this is not set, then the folder implementation
297  * should sub-class the CamelFolderSearch and provide its own
298  * body-contains function.
299  **/
300 void
301 camel_folder_search_set_body_index(CamelFolderSearch *search, CamelIndex *index)
302 {
303         if (search->body_index)
304                 camel_object_unref (search->body_index);
305         search->body_index = index;
306         if (index)
307                 camel_object_ref (index);
308 }
309
310 /**
311  * camel_folder_search_execute_expression:
312  * @search:
313  * @expr:
314  * @ex:
315  *
316  * Execute the search expression @expr, returning an array of
317  * all matches as a GPtrArray of uid's of matching messages.
318  *
319  * Note that any settings such as set_body_index(), set_folder(),
320  * and so on are reset to #NULL once the search has completed.
321  *
322  * TODO: The interface should probably return summary items instead
323  * (since they are much more useful to any client).
324  *
325  * Returns: A GPtrArray of strings of all matching messages.
326  * This must only be freed by camel_folder_search_free_result.
327  **/
328 GPtrArray *
329 camel_folder_search_execute_expression (CamelFolderSearch *search,
330                                         const gchar *expr,
331                                         CamelException *ex)
332 {
333         ESExpResult *r;
334         GPtrArray *matches;
335         gint i;
336         GHashTable *results;
337         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
338
339         p->ex = ex;
340
341         /* only re-parse if the search has changed */
342         if (search->last_search == NULL
343             || strcmp(search->last_search, expr)) {
344                 e_sexp_input_text(search->sexp, expr, strlen(expr));
345                 if (e_sexp_parse(search->sexp) == -1) {
346                         camel_exception_setv (
347                                 ex, 1,
348                                 _("Cannot parse search expression: %s:\n%s"),
349                                 e_sexp_error(search->sexp), expr);
350                         return NULL;
351                 }
352
353                 g_free(search->last_search);
354                 search->last_search = g_strdup(expr);
355         }
356         r = e_sexp_eval(search->sexp);
357         if (r == NULL) {
358                 if (!camel_exception_is_set(ex))
359                         camel_exception_setv (
360                                 ex, 1,
361                                 _("Error executing search expression: %s:\n%s"),
362                                 e_sexp_error(search->sexp), expr);
363                 return NULL;
364         }
365
366         matches = g_ptr_array_new();
367
368         /* now create a folder summary to return?? */
369         if (r->type == ESEXP_RES_ARRAY_PTR) {
370                 d(printf("got result ...\n"));
371                 if (search->summary) {
372                         /* reorder result in summary order */
373                         results = g_hash_table_new(g_str_hash, g_str_equal);
374                         for (i=0;i<r->value.ptrarray->len;i++) {
375                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
376                                 g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
377                         }
378                         for (i=0;i<search->summary->len;i++) {
379                                 gchar *uid = g_ptr_array_index(search->summary, i);
380                                 if (g_hash_table_lookup(results, uid)) {
381                                         g_ptr_array_add(matches, (gpointer) camel_pstring_strdup(uid));
382                                 }
383                         }
384                         g_hash_table_destroy(results);
385                 } else {
386                         for (i=0;i<r->value.ptrarray->len;i++) {
387                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
388                                 g_ptr_array_add(matches, (gpointer) camel_pstring_strdup(g_ptr_array_index(r->value.ptrarray, i)));
389                         }
390                 }
391         } else {
392                 g_warning("Search returned an invalid result type");
393         }
394
395         e_sexp_result_free(search->sexp, r);
396
397         if (p->threads)
398                 camel_folder_thread_messages_unref(p->threads);
399         if (p->threads_hash)
400                 g_hash_table_destroy(p->threads_hash);
401
402         p->threads = NULL;
403         p->threads_hash = NULL;
404         search->folder = NULL;
405         search->summary = NULL;
406         search->current = NULL;
407         search->body_index = NULL;
408
409         return matches;
410 }
411
412 /**
413  * camel_folder_search_count:
414  * @search:
415  * @expr:
416  * @uids: to search against, NULL for all uid's.
417  * @ex:
418  *
419  * Run a search.  Search must have had Folder already set on it, and
420  * it must implement summaries.
421  *
422  * Returns: Number of messages that match the query.
423  *
424  * Since: 2.26
425  **/
426
427 guint32
428 camel_folder_search_count (CamelFolderSearch *search,
429                            const gchar *expr,
430                            CamelException *ex)
431 {
432         ESExpResult *r;
433         GPtrArray *summary_set;
434         gint i;
435         CamelDB *cdb;
436         gchar *sql_query, *tmp, *tmp1;
437         GHashTable *results;
438         guint32 count = 0;
439
440         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
441
442         g_assert(search->folder);
443
444         p->ex = ex;
445
446         /* We route body-contains search and thread based search through memory and not via db. */
447         if (strstr((const gchar *) expr, "body-contains") || strstr((const gchar *) expr, "match-threads")) {
448                 /* setup our search list only contains those we're interested in */
449                 search->summary = camel_folder_get_summary(search->folder);
450
451                 summary_set = search->summary;
452
453                 /* only re-parse if the search has changed */
454                 if (search->last_search == NULL
455                     || strcmp(search->last_search, expr)) {
456                         e_sexp_input_text(search->sexp, expr, strlen(expr));
457                         if (e_sexp_parse(search->sexp) == -1) {
458                                 camel_exception_setv (
459                                         ex, 1,
460                                         _("Cannot parse search expression: %s:\n%s"),
461                                         e_sexp_error(search->sexp), expr);
462                                 goto fail;
463                         }
464
465                         g_free(search->last_search);
466                         search->last_search = g_strdup(expr);
467                 }
468                 r = e_sexp_eval(search->sexp);
469                 if (r == NULL) {
470                         if (!camel_exception_is_set(ex))
471                                 camel_exception_setv (
472                                         ex, 1,
473                                         _("Error executing search expression: %s:\n%s"),
474                                         e_sexp_error(search->sexp), expr);
475                         goto fail;
476                 }
477
478                 /* now create a folder summary to return?? */
479                 if (r->type == ESEXP_RES_ARRAY_PTR) {
480                         d(printf("got result\n"));
481
482                         /* reorder result in summary order */
483                         results = g_hash_table_new(g_str_hash, g_str_equal);
484                         for (i=0;i<r->value.ptrarray->len;i++) {
485                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
486                                 g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
487                         }
488
489                         for (i=0;i<summary_set->len;i++) {
490                                 gchar *uid = g_ptr_array_index(summary_set, i);
491                                 if (g_hash_table_lookup(results, uid))
492                                         count++;
493                         }
494                         g_hash_table_destroy(results);
495                 }
496
497                 e_sexp_result_free(search->sexp, r);
498
499         } else {
500                 /* Sync the db, so that we search the db for changes */
501                 camel_folder_summary_save_to_db (search->folder->summary, ex);
502
503                 dd(printf ("sexp is : [%s]\n", expr));
504                 if (g_getenv("SQL_SEARCH_OLD"))
505                         sql_query = camel_sexp_to_sql (expr);
506                 else
507                         sql_query = camel_sexp_to_sql_sexp (expr);
508                 tmp1 = camel_db_sqlize_string(search->folder->full_name);
509                 tmp = g_strdup_printf ("SELECT COUNT (*) FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query?sql_query:"");
510                 camel_db_free_sqlized_string (tmp1);
511                 g_free (sql_query);
512                 dd(printf("Equivalent sql %s\n", tmp));
513
514                 cdb = (CamelDB *) (search->folder->parent_store->cdb_r);
515                 camel_db_count_message_info  (cdb, tmp, &count, ex);
516                 if (ex && camel_exception_is_set(ex)) {
517                         const gchar *exception = camel_exception_get_description (ex);
518                         if (strncmp(exception, "no such table", 13) == 0) {
519                                 d(g_warning ("Error during searching %s: %s\n", tmp, exception));
520                                 /* Suppress no such table */
521                                 camel_exception_clear (ex);
522                         }
523                 }
524                 g_free (tmp);
525         }
526
527 fail:
528         /* these might be allocated by match-threads */
529         if (p->threads)
530                 camel_folder_thread_messages_unref(p->threads);
531         if (p->threads_hash)
532                 g_hash_table_destroy(p->threads_hash);
533         if (search->summary_set)
534                 g_ptr_array_free(search->summary_set, TRUE);
535         if (search->summary)
536                 camel_folder_free_summary(search->folder, search->summary);
537
538         p->threads = NULL;
539         p->threads_hash = NULL;
540         search->folder = NULL;
541         search->summary = NULL;
542         search->summary_set = NULL;
543         search->current = NULL;
544         search->body_index = NULL;
545
546         return count;
547 }
548
549 static gboolean
550 do_search_in_memory (const gchar *expr)
551 {
552         /* if the expression contains any of these tokens, then perform a memory search, instead of the SQL one */
553         const gchar *in_memory_tokens[] = { "body-contains", "body-regex", "match-threads", "message-location", "header-soundex", "header-regex", "header-full-regex", "header-contains", NULL };
554         gint i;
555
556         if (!expr)
557                 return FALSE;
558
559         for (i = 0; in_memory_tokens [i]; i++) {
560                 if (strstr (expr, in_memory_tokens [i]))
561                         return TRUE;
562         }
563
564         return FALSE;
565 }
566
567 /**
568  * camel_folder_search_search:
569  * @search:
570  * @expr:
571  * @uids: to search against, NULL for all uid's.
572  * @ex:
573  *
574  * Run a search.  Search must have had Folder already set on it, and
575  * it must implement summaries.
576  *
577  * Returns:
578  **/
579 GPtrArray *
580 camel_folder_search_search (CamelFolderSearch *search,
581                             const gchar *expr,
582                             GPtrArray *uids,
583                             CamelException *ex)
584 {
585         ESExpResult *r;
586         GPtrArray *matches = NULL, *summary_set;
587         gint i;
588         CamelDB *cdb;
589         gchar *sql_query, *tmp, *tmp1;
590         GHashTable *results;
591
592         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
593
594         g_assert(search->folder);
595
596         p->ex = ex;
597
598         /* We route body-contains / thread based search and uid search through memory and not via db. */
599         if (uids || do_search_in_memory (expr)) {
600                 /* setup our search list only contains those we're interested in */
601                 search->summary = camel_folder_get_summary(search->folder);
602
603                 if (uids) {
604                         GHashTable *uids_hash = g_hash_table_new(g_str_hash, g_str_equal);
605
606                         summary_set = search->summary_set = g_ptr_array_new();
607                         for (i=0;i<uids->len;i++)
608                                 g_hash_table_insert(uids_hash, uids->pdata[i], uids->pdata[i]);
609                         for (i=0;i<search->summary->len;i++)
610                                 if (g_hash_table_lookup(uids_hash, search->summary->pdata[i]))
611                                         g_ptr_array_add(search->summary_set, search->summary->pdata[i]);
612                         g_hash_table_destroy(uids_hash);
613                 } else {
614                         summary_set = search->summary;
615                 }
616
617                 /* only re-parse if the search has changed */
618                 if (search->last_search == NULL
619                     || strcmp(search->last_search, expr)) {
620                         e_sexp_input_text(search->sexp, expr, strlen(expr));
621                         if (e_sexp_parse(search->sexp) == -1) {
622                                 camel_exception_setv (
623                                         ex, 1,
624                                         _("Cannot parse search expression: %s:\n%s"),
625                                         e_sexp_error(search->sexp), expr);
626                                 goto fail;
627                         }
628
629                         g_free(search->last_search);
630                         search->last_search = g_strdup(expr);
631                 }
632                 r = e_sexp_eval(search->sexp);
633                 if (r == NULL) {
634                         if (!camel_exception_is_set(ex))
635                                 camel_exception_setv (
636                                         ex, 1,
637                                         _("Error executing search expression: %s:\n%s"),
638                                         e_sexp_error(search->sexp), expr);
639                         goto fail;
640                 }
641
642                 matches = g_ptr_array_new();
643
644                 /* now create a folder summary to return?? */
645                 if (r->type == ESEXP_RES_ARRAY_PTR) {
646                         d(printf("got result\n"));
647
648                         /* reorder result in summary order */
649                         results = g_hash_table_new(g_str_hash, g_str_equal);
650                         for (i=0;i<r->value.ptrarray->len;i++) {
651                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
652                                 g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
653                         }
654
655                         for (i=0;i<summary_set->len;i++) {
656                                 gchar *uid = g_ptr_array_index(summary_set, i);
657                                 if (g_hash_table_lookup(results, uid))
658                                         g_ptr_array_add(matches, (gpointer) camel_pstring_strdup(uid));
659                         }
660                         g_hash_table_destroy(results);
661                 }
662
663                 e_sexp_result_free(search->sexp, r);
664
665         } else {
666                 /* Sync the db, so that we search the db for changes */
667                 camel_folder_summary_save_to_db (search->folder->summary, ex);
668
669                 dd(printf ("sexp is : [%s]\n", expr));
670                 if (g_getenv("SQL_SEARCH_OLD"))
671                         sql_query = camel_sexp_to_sql (expr);
672                 else
673                         sql_query = camel_sexp_to_sql_sexp (expr);
674                 tmp1 = camel_db_sqlize_string(search->folder->full_name);
675                 tmp = g_strdup_printf ("SELECT uid FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query?sql_query:"");
676                 camel_db_free_sqlized_string (tmp1);
677                 g_free (sql_query);
678                 dd(printf("Equivalent sql %s\n", tmp));
679
680                 matches = g_ptr_array_new();
681                 cdb = (CamelDB *) (search->folder->parent_store->cdb_r);
682                 camel_db_select (cdb, tmp, (CamelDBSelectCB) read_uid_callback, matches, ex);
683                 if (ex && camel_exception_is_set(ex)) {
684                         const gchar *exception = camel_exception_get_description (ex);
685                         if (strncmp(exception, "no such table", 13) == 0) {
686                                 d(g_warning ("Error during searching %s: %s\n", tmp, exception));
687                                 /* Suppress no such table */
688                                 camel_exception_clear (ex);
689                         }
690                 }
691                 g_free (tmp);
692
693         }
694
695 fail:
696         /* these might be allocated by match-threads */
697         if (p->threads)
698                 camel_folder_thread_messages_unref(p->threads);
699         if (p->threads_hash)
700                 g_hash_table_destroy(p->threads_hash);
701         if (search->summary_set)
702                 g_ptr_array_free(search->summary_set, TRUE);
703         if (search->summary)
704                 camel_folder_free_summary(search->folder, search->summary);
705
706         p->threads = NULL;
707         p->threads_hash = NULL;
708         search->folder = NULL;
709         search->summary = NULL;
710         search->summary_set = NULL;
711         search->current = NULL;
712         search->body_index = NULL;
713
714         return matches;
715 }
716
717 void camel_folder_search_free_result(CamelFolderSearch *search, GPtrArray *result)
718 {
719         g_ptr_array_foreach (result, (GFunc) camel_pstring_free, NULL);
720         g_ptr_array_free(result, TRUE);
721 }
722
723 /* dummy function, returns false always, or an empty match array */
724 static ESExpResult *
725 search_dummy(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
726 {
727         ESExpResult *r;
728
729         if (search->current == NULL) {
730                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
731                 r->value.boolean = FALSE;
732         } else {
733                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
734                 r->value.ptrarray = g_ptr_array_new();
735         }
736
737         return r;
738 }
739
740 /* impelemnt an 'array not', i.e. everything in the summary, not in the supplied array */
741 static ESExpResult *
742 search_not(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
743 {
744         ESExpResult *r;
745         gint i;
746
747         if (argc>0) {
748                 if (argv[0]->type == ESEXP_RES_ARRAY_PTR) {
749                         GPtrArray *v = argv[0]->value.ptrarray;
750                         const gchar *uid;
751
752                         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
753                         r->value.ptrarray = g_ptr_array_new();
754
755                         /* not against a single message?*/
756                         if (search->current) {
757                                 gint found = FALSE;
758
759                                 uid = camel_message_info_uid(search->current);
760                                 for (i=0;!found && i<v->len;i++) {
761                                         if (strcmp(uid, v->pdata[i]) == 0)
762                                                 found = TRUE;
763                                 }
764
765                                 if (!found)
766                                         g_ptr_array_add(r->value.ptrarray, (gchar *)uid);
767                         } else if (search->summary == NULL) {
768                                 g_warning("No summary set, 'not' against an array requires a summary");
769                         } else {
770                                 /* 'not' against the whole summary */
771                                 GHashTable *have = g_hash_table_new(g_str_hash, g_str_equal);
772                                 gchar **s;
773                                 gchar **m;
774
775                                 s = (gchar **)v->pdata;
776                                 for (i=0;i<v->len;i++)
777                                         g_hash_table_insert(have, s[i], s[i]);
778
779                                 v = search->summary_set?search->summary_set:search->summary;
780                                 m = (gchar **)v->pdata;
781                                 for (i=0;i<v->len;i++) {
782                                         gchar *uid = m[i];
783
784                                         if (g_hash_table_lookup(have, uid) == NULL)
785                                                 g_ptr_array_add(r->value.ptrarray, uid);
786                                 }
787                                 g_hash_table_destroy(have);
788                         }
789                 } else {
790                         gint res = TRUE;
791
792                         if (argv[0]->type == ESEXP_RES_BOOL)
793                                 res = !argv[0]->value.boolean;
794
795                         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
796                         r->value.boolean = res;
797                 }
798         } else {
799                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
800                 r->value.boolean = TRUE;
801         }
802
803         return r;
804 }
805
806 static ESExpResult *
807 search_match_all(struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
808 {
809         gint i;
810         ESExpResult *r, *r1;
811         gchar *error_msg;
812         GPtrArray *v;
813
814         if (argc>1) {
815                 g_warning("match-all only takes a single argument, other arguments ignored");
816         }
817
818         /* we are only matching a single message?  or already inside a match-all? */
819         if (search->current) {
820                 d(printf("matching against 1 message: %s\n", camel_message_info_subject(search->current)));
821
822                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
823                 r->value.boolean = FALSE;
824
825                 if (argc>0) {
826                         r1 = e_sexp_term_eval(f, argv[0]);
827                         if (r1->type == ESEXP_RES_BOOL) {
828                                 r->value.boolean = r1->value.boolean;
829                         } else {
830                                 g_warning("invalid syntax, matches require a single bool result");
831                                 error_msg = g_strdup_printf(_("(%s) requires a single bool result"), "match-all");
832                                 e_sexp_fatal_error(f, error_msg);
833                                 g_free(error_msg);
834                         }
835                         e_sexp_result_free(f, r1);
836                 } else {
837                         r->value.boolean = TRUE;
838                 }
839                 return r;
840         }
841
842         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
843         r->value.ptrarray = g_ptr_array_new();
844
845         if (search->summary == NULL) {
846                 /* TODO: make it work - e.g. use the folder and so forth for a slower search */
847                 g_warning("No summary supplied, match-all doesn't work with no summary");
848                 g_assert(0);
849                 return r;
850         }
851
852         v = search->summary_set?search->summary_set:search->summary;
853
854         if (v->len > g_hash_table_size (search->folder->summary->loaded_infos) && !CAMEL_IS_VEE_FOLDER (search->folder)) {
855                 camel_folder_summary_reload_from_db (search->folder->summary, search->priv->ex);
856         }
857
858         for (i=0;i<v->len;i++) {
859                 const gchar *uid;
860
861                 search->current = camel_folder_summary_uid (search->folder->summary, v->pdata[i]);
862                 if (!search->current)
863                         continue;
864                 uid = camel_message_info_uid(search->current);
865
866                 if (argc>0) {
867                         r1 = e_sexp_term_eval(f, argv[0]);
868                         if (r1->type == ESEXP_RES_BOOL) {
869                                 if (r1->value.boolean)
870                                         g_ptr_array_add(r->value.ptrarray, (gchar *)uid);
871                         } else {
872                                 g_warning("invalid syntax, matches require a single bool result");
873                                 error_msg = g_strdup_printf(_("(%s) requires a single bool result"), "match-all");
874                                 e_sexp_fatal_error(f, error_msg);
875                                 g_free(error_msg);
876                         }
877                         e_sexp_result_free(f, r1);
878                 } else {
879                         g_ptr_array_add(r->value.ptrarray, (gchar *)uid);
880                 }
881                 camel_message_info_free (search->current);
882         }
883         search->current = NULL;
884         return r;
885 }
886
887 static void
888 fill_thread_table(struct _CamelFolderThreadNode *root, GHashTable *id_hash)
889 {
890         while (root) {
891                 g_hash_table_insert(id_hash, (gchar *)camel_message_info_uid(root->message), root);
892                 if (root->child)
893                         fill_thread_table(root->child, id_hash);
894                 root = root->next;
895         }
896 }
897
898 static void
899 add_thread_results(struct _CamelFolderThreadNode *root, GHashTable *result_hash)
900 {
901         while (root) {
902                 g_hash_table_insert(result_hash, (gchar *)camel_message_info_uid(root->message), GINT_TO_POINTER (1));
903                 if (root->child)
904                         add_thread_results(root->child, result_hash);
905                 root = root->next;
906         }
907 }
908
909 static void
910 add_results(gchar *uid, gpointer dummy, GPtrArray *result)
911 {
912         g_ptr_array_add(result, uid);
913 }
914
915 static ESExpResult *
916 search_match_threads(struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
917 {
918         ESExpResult *r;
919         CamelFolderSearchPrivate *p = search->priv;
920         gint i, type;
921         GHashTable *results;
922         gchar *error_msg;
923
924         /* not supported in match-all */
925         if (search->current) {
926                 error_msg = g_strdup_printf(_("(%s) not allowed inside %s"), "match-threads", "match-all");
927                 e_sexp_fatal_error(f, error_msg);
928                 g_free(error_msg);
929         }
930
931         if (argc == 0) {
932                 error_msg = g_strdup_printf(_("(%s) requires a match type string"), "match-threads");
933                 e_sexp_fatal_error(f, error_msg);
934                 g_free(error_msg);
935         }
936
937         r = e_sexp_term_eval(f, argv[0]);
938         if (r->type != ESEXP_RES_STRING) {
939                 error_msg = g_strdup_printf(_("(%s) requires a match type string"), "match-threads");
940                 e_sexp_fatal_error(f, error_msg);
941                 g_free(error_msg);
942         }
943
944         type = 0;
945         if (!strcmp(r->value.string, "none"))
946                 type = 0;
947         else if (!strcmp(r->value.string, "all"))
948                 type = 1;
949         else if (!strcmp(r->value.string, "replies"))
950                 type = 2;
951         else if (!strcmp(r->value.string, "replies_parents"))
952                 type = 3;
953         else if (!strcmp(r->value.string, "single"))
954                 type = 4;
955         e_sexp_result_free(f, r);
956
957         /* behave as (begin does */
958         r = NULL;
959         for (i=1;i<argc;i++) {
960                 if (r)
961                         e_sexp_result_free(f, r);
962                 r = e_sexp_term_eval(f, argv[i]);
963         }
964
965         if (r == NULL || r->type != ESEXP_RES_ARRAY_PTR) {
966                 error_msg = g_strdup_printf(_("(%s) expects an array result"), "match-threads");
967                 e_sexp_fatal_error(f, error_msg);
968                 g_free(error_msg);
969         }
970
971         if (type == 0)
972                 return r;
973
974         if (search->folder == NULL) {
975                 error_msg = g_strdup_printf(_("(%s) requires the folder set"), "match-threads");
976                 e_sexp_fatal_error(f, error_msg);
977                 g_free(error_msg);
978         }
979
980         /* cache this, so we only have to re-calculate once per search at most */
981         if (p->threads == NULL) {
982                 p->threads = camel_folder_thread_messages_new(search->folder, NULL, TRUE);
983                 p->threads_hash = g_hash_table_new(g_str_hash, g_str_equal);
984
985                 fill_thread_table(p->threads->tree, p->threads_hash);
986         }
987
988         results = g_hash_table_new(g_str_hash, g_str_equal);
989         for (i=0;i<r->value.ptrarray->len;i++) {
990                 struct _CamelFolderThreadNode *node, *scan;
991
992                 if (type != 4)
993                         g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER(1));
994
995                 node = g_hash_table_lookup(p->threads_hash, (gchar *)g_ptr_array_index(r->value.ptrarray, i));
996                 if (node == NULL) /* this shouldn't happen but why cry over spilt milk */
997                         continue;
998
999                 /* select messages in thread according to search criteria */
1000                 if (type == 4) {
1001                         if (node->child == NULL && node->parent == NULL)
1002                                 g_hash_table_insert(results, (gchar *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
1003                 } else {
1004                         if (type == 3) {
1005                                 scan = node;
1006                                 while (scan && scan->parent) {
1007                                         scan = scan->parent;
1008                                         g_hash_table_insert(results, (gchar *)camel_message_info_uid(scan->message), GINT_TO_POINTER(1));
1009                                 }
1010                         } else if (type == 1) {
1011                                 while (node && node->parent)
1012                                         node = node->parent;
1013                         }
1014                         g_hash_table_insert(results, (gchar *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
1015                         if (node->child)
1016                                 add_thread_results(node->child, results);
1017                 }
1018         }
1019         e_sexp_result_free(f, r);
1020
1021         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1022         r->value.ptrarray = g_ptr_array_new();
1023
1024         g_hash_table_foreach(results, (GHFunc)add_results, r->value.ptrarray);
1025         g_hash_table_destroy(results);
1026
1027         return r;
1028 }
1029
1030 static CamelMimeMessage *
1031 get_current_message (CamelFolderSearch *search)
1032 {
1033         CamelException x = CAMEL_EXCEPTION_INITIALISER;
1034         CamelMimeMessage *res;
1035
1036         if (!search || !search->folder || !search->current)
1037                 return NULL;
1038
1039         res = camel_folder_get_message (search->folder, search->current->uid, &x);
1040
1041         if (!res)
1042                 camel_exception_clear (&x);
1043
1044         return res;
1045 }
1046
1047 static ESExpResult *
1048 check_header (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search, camel_search_match_t how)
1049 {
1050         ESExpResult *r;
1051         gint truth = FALSE;
1052
1053         r(printf("executing check-header %d\n", how));
1054
1055         /* are we inside a match-all? */
1056         if (search->current && argc>1
1057             && argv[0]->type == ESEXP_RES_STRING) {
1058                 gchar *headername;
1059                 const gchar *header = NULL, *charset = NULL;
1060                 gchar strbuf[32];
1061                 gint i, j;
1062                 camel_search_t type = CAMEL_SEARCH_TYPE_ASIS;
1063                 struct _camel_search_words *words;
1064                 CamelMimeMessage *message = NULL;
1065                 struct _camel_header_raw *raw_header;
1066
1067                 /* only a subset of headers are supported .. */
1068                 headername = argv[0]->value.string;
1069                 if (!g_ascii_strcasecmp(headername, "subject")) {
1070                         header = camel_message_info_subject(search->current);
1071                 } else if (!g_ascii_strcasecmp(headername, "date")) {
1072                         /* FIXME: not a very useful form of the date */
1073                         sprintf(strbuf, "%d", (gint)camel_message_info_date_sent(search->current));
1074                         header = strbuf;
1075                 } else if (!g_ascii_strcasecmp(headername, "from")) {
1076                         header = camel_message_info_from(search->current);
1077                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1078                 } else if (!g_ascii_strcasecmp(headername, "to")) {
1079                         header = camel_message_info_to(search->current);
1080                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1081                 } else if (!g_ascii_strcasecmp(headername, "cc")) {
1082                         header = camel_message_info_cc(search->current);
1083                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1084                 } else if (!g_ascii_strcasecmp(headername, "x-camel-mlist")) {
1085                         header = camel_message_info_mlist(search->current);
1086                         type = CAMEL_SEARCH_TYPE_MLIST;
1087                 } else {
1088                         message = get_current_message (search);
1089                         if (message) {
1090                                 CamelContentType *ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message));
1091
1092                                 if (ct) {
1093                                         charset = camel_content_type_param (ct, "charset");
1094                                         charset = camel_iconv_charset_name (charset);
1095                                 }
1096                         }
1097                 }
1098
1099                 if (header == NULL)
1100                         header = "";
1101
1102                 /* performs an OR of all words */
1103                 for (i=1;i<argc && !truth;i++) {
1104                         if (argv[i]->type == ESEXP_RES_STRING) {
1105                                 if (argv[i]->value.string[0] == 0) {
1106                                         truth = TRUE;
1107                                 } else if (how == CAMEL_SEARCH_MATCH_CONTAINS) {
1108                                         /* doesn't make sense to split words on anything but contains i.e. we can't have an ending match different words */
1109                                         words = camel_search_words_split((const guchar *) argv[i]->value.string);
1110                                         truth = TRUE;
1111                                         for (j=0;j<words->len && truth;j++) {
1112                                                 if (message) {
1113                                                         for (raw_header = ((CamelMimePart *)message)->headers; raw_header; raw_header = raw_header->next) {
1114                                                                 if (!g_ascii_strcasecmp (raw_header->name, headername)) {
1115                                                                         if (camel_search_header_match (raw_header->value, words->words[j]->word, how, type, charset))
1116                                                                                 break;;
1117                                                                 }
1118                                                         }
1119
1120                                                         truth = raw_header != NULL;
1121                                                 } else
1122                                                         truth = camel_search_header_match(header, words->words[j]->word, how, type, charset);
1123                                         }
1124                                         camel_search_words_free(words);
1125                                 } else {
1126                                         if (message) {
1127                                                 for (raw_header = ((CamelMimePart *)message)->headers; raw_header && !truth; raw_header = raw_header->next) {
1128                                                         if (!g_ascii_strcasecmp (raw_header->name, headername)) {
1129                                                                 truth = camel_search_header_match(raw_header->value, argv[i]->value.string, how, type, charset);
1130                                                         }
1131                                                 }
1132                                         } else
1133                                                 truth = camel_search_header_match(header, argv[i]->value.string, how, type, charset);
1134                                 }
1135                         }
1136                 }
1137
1138                 if (message)
1139                         camel_object_unref (message);
1140         }
1141         /* TODO: else, find all matches */
1142
1143         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1144         r->value.boolean = truth;
1145
1146         return r;
1147 }
1148
1149 /*
1150 static void
1151 l_printf(gchar *node)
1152 {
1153 printf("%s\t", node);
1154 }
1155 */
1156
1157 static ESExpResult *
1158 search_header_contains(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1159 {
1160         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS);
1161 }
1162
1163 static ESExpResult *
1164 search_header_matches(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1165 {
1166         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT);
1167 }
1168
1169 static ESExpResult *
1170 search_header_starts_with (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1171 {
1172         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS);
1173 }
1174
1175 static ESExpResult *
1176 search_header_ends_with (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1177 {
1178         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS);
1179 }
1180
1181 static ESExpResult *
1182 search_header_exists (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1183 {
1184         ESExpResult *r;
1185
1186         r(printf ("executing header-exists\n"));
1187
1188         if (search->current) {
1189                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1190                 if (argc == 1 && argv[0]->type == ESEXP_RES_STRING)
1191                         r->value.boolean = camel_medium_get_header(CAMEL_MEDIUM(search->current), argv[0]->value.string) != NULL;
1192
1193         } else {
1194                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1195                 r->value.ptrarray = g_ptr_array_new();
1196         }
1197
1198         return r;
1199 }
1200
1201 static ESExpResult *
1202 search_header_soundex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1203 {
1204         return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_SOUNDEX);
1205 }
1206
1207 static ESExpResult *
1208 search_header_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1209 {
1210         ESExpResult *r;
1211         CamelMimeMessage *msg;
1212
1213         msg = get_current_message (search);
1214
1215         if (msg) {
1216                 regex_t pattern;
1217                 const gchar *contents;
1218
1219                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1220
1221                 if (argc > 1 && argv[0]->type == ESEXP_RES_STRING
1222                     && (contents = camel_medium_get_header (CAMEL_MEDIUM (msg), argv[0]->value.string))
1223                     && camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE, argc-1, argv+1, search->priv->ex) == 0) {
1224                         r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
1225                         regfree (&pattern);
1226                 } else
1227                         r->value.boolean = FALSE;
1228
1229                 camel_object_unref (msg);
1230         } else {
1231                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1232                 r->value.ptrarray = g_ptr_array_new();
1233         }
1234
1235         return r;
1236 }
1237
1238 static gchar *
1239 get_full_header (CamelMimeMessage *message)
1240 {
1241         CamelMimePart *mp = CAMEL_MIME_PART (message);
1242         GString *str = g_string_new ("");
1243         struct _camel_header_raw *h;
1244
1245         for (h = mp->headers; h; h = h->next) {
1246                 if (h->value != NULL) {
1247                         g_string_append (str, h->name);
1248                         if (isspace (h->value[0]))
1249                                 g_string_append (str, ":");
1250                         else
1251                                 g_string_append (str, ": ");
1252                         g_string_append (str, h->value);
1253                         g_string_append_c (str, '\n');
1254                 }
1255         }
1256
1257         return g_string_free (str, FALSE);
1258 }
1259
1260 static ESExpResult *
1261 search_header_full_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1262 {
1263         ESExpResult *r;
1264         CamelMimeMessage *msg;
1265
1266         msg = get_current_message (search);
1267
1268         if (msg) {
1269                 regex_t pattern;
1270
1271                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1272
1273                 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->ex) == 0) {
1274                         gchar *contents;
1275
1276                         contents = get_full_header (msg);
1277                         r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
1278
1279                         g_free (contents);
1280                         regfree (&pattern);
1281                 } else
1282                         r->value.boolean = FALSE;
1283
1284                 camel_object_unref (msg);
1285         } else {
1286                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1287                 r->value.ptrarray = g_ptr_array_new();
1288         }
1289
1290         return r;
1291 }
1292
1293 /* this is just to OR results together */
1294 struct IterData {
1295         gint count;
1296         GPtrArray *uids;
1297 };
1298
1299 /* or, store all unique values */
1300 static void
1301 htor(gchar *key, gint value, struct IterData *iter_data)
1302 {
1303         g_ptr_array_add(iter_data->uids, key);
1304 }
1305
1306 /* and, only store duplicates */
1307 static void
1308 htand(gchar *key, gint value, struct IterData *iter_data)
1309 {
1310         if (value == iter_data->count)
1311                 g_ptr_array_add(iter_data->uids, key);
1312 }
1313
1314 static gint
1315 match_message_index (CamelIndex *idx,
1316                      const gchar *uid,
1317                      const gchar *match,
1318                      CamelException *ex)
1319 {
1320         CamelIndexCursor *wc, *nc;
1321         const gchar *word, *name;
1322         gint truth = FALSE;
1323
1324         wc = camel_index_words(idx);
1325         if (wc) {
1326                 while (!truth && (word = camel_index_cursor_next(wc))) {
1327                         if (camel_ustrstrcase(word,match) != NULL) {
1328                                 /* perf: could have the wc cursor return the name cursor */
1329                                 nc = camel_index_find(idx, word);
1330                                 if (nc) {
1331                                         while (!truth && (name = camel_index_cursor_next(nc)))
1332                                                 truth = strcmp(name, uid) == 0;
1333                                         camel_object_unref (nc);
1334                                 }
1335                         }
1336                 }
1337                 camel_object_unref (wc);
1338         }
1339
1340         return truth;
1341 }
1342
1343 /*
1344  "one two" "three" "four five"
1345
1346   one and two
1347 or
1348   three
1349 or
1350   four and five
1351 */
1352
1353 /* returns messages which contain all words listed in words */
1354 static GPtrArray *
1355 match_words_index (CamelFolderSearch *search,
1356                    struct _camel_search_words *words,
1357                    CamelException *ex)
1358 {
1359         GPtrArray *result = g_ptr_array_new();
1360         GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
1361         struct IterData lambdafoo;
1362         CamelIndexCursor *wc, *nc;
1363         const gchar *word, *name;
1364         gint i;
1365
1366         /* we can have a maximum of 32 words, as we use it as the AND mask */
1367
1368         wc = camel_index_words(search->body_index);
1369         if (wc) {
1370                 while ((word = camel_index_cursor_next(wc))) {
1371                         for (i=0;i<words->len;i++) {
1372                                 if (camel_ustrstrcase(word, words->words[i]->word) != NULL) {
1373                                         /* perf: could have the wc cursor return the name cursor */
1374                                         nc = camel_index_find(search->body_index, word);
1375                                         if (nc) {
1376                                                 while ((name = camel_index_cursor_next(nc))) {
1377                                                                 gint mask;
1378
1379                                                                 mask = (GPOINTER_TO_INT(g_hash_table_lookup(ht, name))) | (1<<i);
1380                                                                 g_hash_table_insert(ht, (gchar *) camel_pstring_peek(name), GINT_TO_POINTER(mask));
1381                                                 }
1382                                                 camel_object_unref (nc);
1383                                         }
1384                                 }
1385                         }
1386                 }
1387                 camel_object_unref (wc);
1388
1389                 lambdafoo.uids = result;
1390                 lambdafoo.count = (1<<words->len) - 1;
1391                 g_hash_table_foreach(ht, (GHFunc)htand, &lambdafoo);
1392                 g_hash_table_destroy(ht);
1393         }
1394
1395         return result;
1396 }
1397
1398 static gboolean
1399 match_words_1message (CamelDataWrapper *object, struct _camel_search_words *words, guint32 *mask)
1400 {
1401         CamelDataWrapper *containee;
1402         gint truth = FALSE;
1403         gint parts, i;
1404
1405         containee = camel_medium_get_content (CAMEL_MEDIUM (object));
1406
1407         if (containee == NULL)
1408                 return FALSE;
1409
1410         /* using the object types is more accurate than using the mime/types */
1411         if (CAMEL_IS_MULTIPART (containee)) {
1412                 parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
1413                 for (i = 0; i < parts && truth == FALSE; i++) {
1414                         CamelDataWrapper *part = (CamelDataWrapper *)camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
1415                         if (part)
1416                                 truth = match_words_1message(part, words, mask);
1417                 }
1418         } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
1419                 /* for messages we only look at its contents */
1420                 truth = match_words_1message((CamelDataWrapper *)containee, words, mask);
1421         } else if (camel_content_type_is(CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")) {
1422                 /* for all other text parts, we look inside, otherwise we dont care */
1423                 CamelStream *stream;
1424                 GByteArray *byte_array;
1425
1426                 byte_array = g_byte_array_new ();
1427                 stream = camel_stream_mem_new_with_byte_array (byte_array);
1428
1429                 /* FIXME: The match should be part of a stream op */
1430                 camel_data_wrapper_decode_to_stream (containee, stream);
1431                 camel_stream_write (stream, "", 1);
1432                 for (i=0;i<words->len;i++) {
1433                         /* FIXME: This is horridly slow, and should use a real search algorithm */
1434                         if (camel_ustrstrcase((const gchar *) byte_array->data, words->words[i]->word) != NULL) {
1435                                 *mask |= (1<<i);
1436                                 /* shortcut a match */
1437                                 if (*mask == (1<<(words->len))-1)
1438                                         return TRUE;
1439                         }
1440                 }
1441
1442                 camel_object_unref (stream);
1443         }
1444
1445         return truth;
1446 }
1447
1448 static gboolean
1449 match_words_message (CamelFolder *folder,
1450                      const gchar *uid,
1451                      struct _camel_search_words *words,
1452                      CamelException *ex)
1453 {
1454         guint32 mask;
1455         CamelMimeMessage *msg;
1456         CamelException x = CAMEL_EXCEPTION_INITIALISER;
1457         gint truth = FALSE;
1458
1459         msg = camel_folder_get_message(folder, uid, &x);
1460         if (msg) {
1461                 mask = 0;
1462                 truth = match_words_1message((CamelDataWrapper *)msg, words, &mask);
1463                 camel_object_unref (msg);
1464         } else
1465                 camel_exception_clear (&x);
1466
1467         return truth;
1468 }
1469
1470 static GPtrArray *
1471 match_words_messages (CamelFolderSearch *search,
1472                       struct _camel_search_words *words,
1473                       CamelException *ex)
1474 {
1475         gint i;
1476         GPtrArray *matches = g_ptr_array_new();
1477
1478         if (search->body_index) {
1479                 GPtrArray *indexed;
1480                 struct _camel_search_words *simple;
1481
1482                 simple = camel_search_words_simple(words);
1483                 indexed = match_words_index(search, simple, ex);
1484                 camel_search_words_free(simple);
1485
1486                 for (i=0;i<indexed->len;i++) {
1487                         const gchar *uid = g_ptr_array_index(indexed, i);
1488
1489                         if (match_words_message(search->folder, uid, words, ex))
1490                                 g_ptr_array_add(matches, (gchar *)uid);
1491                 }
1492
1493                 g_ptr_array_free(indexed, TRUE);
1494         } else {
1495                 GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1496
1497                 for (i=0;i<v->len;i++) {
1498                         gchar *uid  = g_ptr_array_index(v, i);
1499
1500                         if (match_words_message(search->folder, uid, words, ex))
1501                                 g_ptr_array_add(matches, (gchar *)uid);
1502                 }
1503         }
1504
1505         return matches;
1506 }
1507
1508 static ESExpResult *
1509 search_body_contains(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1510 {
1511         gint i, j;
1512         CamelException *ex = search->priv->ex;
1513         struct _camel_search_words *words;
1514         ESExpResult *r;
1515         struct IterData lambdafoo;
1516
1517         if (search->current) {
1518                 gint truth = FALSE;
1519
1520                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1521                         truth = TRUE;
1522                 } else {
1523                         for (i=0;i<argc && !truth;i++) {
1524                                 if (argv[i]->type == ESEXP_RES_STRING) {
1525                                         words = camel_search_words_split((const guchar *) argv[i]->value.string);
1526                                         truth = TRUE;
1527                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1528                                                 for (j=0;j<words->len && truth;j++)
1529                                                         truth = match_message_index(search->body_index, camel_message_info_uid(search->current), words->words[j]->word, ex);
1530                                         } else {
1531                                                 /* TODO: cache current message incase of multiple body search terms */
1532                                                 truth = match_words_message(search->folder, camel_message_info_uid(search->current), words, ex);
1533                                         }
1534                                         camel_search_words_free(words);
1535                                 }
1536                         }
1537                 }
1538                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1539                 r->value.boolean = truth;
1540         } else {
1541                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1542                 r->value.ptrarray = g_ptr_array_new();
1543
1544                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1545                         GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1546
1547                         for (i=0;i<v->len;i++) {
1548                                 gchar *uid = g_ptr_array_index(v, i);
1549
1550                                 g_ptr_array_add(r->value.ptrarray, uid);
1551                         }
1552                 } else {
1553                         GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
1554                         GPtrArray *matches;
1555
1556                         for (i=0;i<argc;i++) {
1557                                 if (argv[i]->type == ESEXP_RES_STRING) {
1558                                         words = camel_search_words_split((const guchar *) argv[i]->value.string);
1559                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1560                                                 matches = match_words_index(search, words, ex);
1561                                         } else {
1562                                                 matches = match_words_messages(search, words, ex);
1563                                         }
1564                                         for (j=0;j<matches->len;j++) {
1565                                                 g_hash_table_insert(ht, matches->pdata[j], matches->pdata[j]);
1566                                         }
1567                                         g_ptr_array_free(matches, TRUE);
1568                                         camel_search_words_free(words);
1569                                 }
1570                         }
1571                         lambdafoo.uids = r->value.ptrarray;
1572                         g_hash_table_foreach(ht, (GHFunc)htor, &lambdafoo);
1573                         g_hash_table_destroy(ht);
1574                 }
1575         }
1576
1577         return r;
1578 }
1579
1580 static ESExpResult *
1581 search_body_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1582 {
1583         ESExpResult *r;
1584         CamelMimeMessage *msg = get_current_message (search);
1585
1586         if (msg) {
1587                 regex_t pattern;
1588
1589                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1590
1591                 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->ex) == 0) {
1592                         r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) msg, &pattern);
1593                         regfree (&pattern);
1594                 } else
1595                         r->value.boolean = FALSE;
1596
1597                 camel_object_unref (msg);
1598         } else {
1599                 regex_t pattern;
1600
1601                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1602                 r->value.ptrarray = g_ptr_array_new ();
1603
1604                 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->ex) == 0) {
1605                         gint i;
1606                         GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1607                         CamelException x = CAMEL_EXCEPTION_INITIALISER;
1608                         CamelMimeMessage *message;
1609
1610                         for (i = 0; i < v->len; i++) {
1611                                 gchar *uid = g_ptr_array_index(v, i);
1612
1613                                 message = camel_folder_get_message (search->folder, uid, &x);
1614                                 if (message) {
1615                                         if (camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern)) {
1616                                                 g_ptr_array_add (r->value.ptrarray, uid);
1617                                         }
1618
1619                                         camel_object_unref (message);
1620                                 } else {
1621                                         camel_exception_clear (&x);
1622                                 }
1623                         }
1624
1625                         regfree (&pattern);
1626                 }
1627         }
1628
1629         return r;
1630 }
1631
1632 static ESExpResult *
1633 search_user_flag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1634 {
1635         ESExpResult *r;
1636         gint i;
1637
1638         r(printf("executing user-flag\n"));
1639
1640         /* are we inside a match-all? */
1641         if (search->current) {
1642                 gint truth = FALSE;
1643                 /* performs an OR of all words */
1644                 for (i=0;i<argc && !truth;i++) {
1645                         if (argv[i]->type == ESEXP_RES_STRING
1646                             && camel_message_info_user_flag(search->current, argv[i]->value.string)) {
1647                                 truth = TRUE;
1648                                 break;
1649                         }
1650                 }
1651                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1652                 r->value.boolean = truth;
1653         } else {
1654                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1655                 r->value.ptrarray = g_ptr_array_new();
1656         }
1657
1658         return r;
1659 }
1660
1661 static ESExpResult *
1662 search_system_flag (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1663 {
1664         ESExpResult *r;
1665
1666         r(printf ("executing system-flag\n"));
1667
1668         if (search->current) {
1669                 gboolean truth = FALSE;
1670
1671                 if (argc == 1)
1672                         truth = camel_system_flag_get (camel_message_info_flags(search->current), argv[0]->value.string);
1673
1674                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1675                 r->value.boolean = truth;
1676         } else {
1677                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1678                 r->value.ptrarray = g_ptr_array_new ();
1679         }
1680
1681         return r;
1682 }
1683
1684 static ESExpResult *
1685 search_user_tag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1686 {
1687         const gchar *value = NULL;
1688         ESExpResult *r;
1689
1690         r(printf("executing user-tag\n"));
1691
1692         if (search->current && argc == 1)
1693                 value = camel_message_info_user_tag(search->current, argv[0]->value.string);
1694
1695         r = e_sexp_result_new(f, ESEXP_RES_STRING);
1696         r->value.string = g_strdup (value ? value : "");
1697
1698         return r;
1699 }
1700
1701 static ESExpResult *
1702 search_get_sent_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1703 {
1704         ESExpResult *r;
1705
1706         r(printf("executing get-sent-date\n"));
1707
1708         /* are we inside a match-all? */
1709         if (s->current) {
1710                 r = e_sexp_result_new(f, ESEXP_RES_INT);
1711
1712                 r->value.number = camel_message_info_date_sent(s->current);
1713         } else {
1714                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1715                 r->value.ptrarray = g_ptr_array_new ();
1716         }
1717
1718         return r;
1719 }
1720
1721 static ESExpResult *
1722 search_get_received_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1723 {
1724         ESExpResult *r;
1725
1726         r(printf("executing get-received-date\n"));
1727
1728         /* are we inside a match-all? */
1729         if (s->current) {
1730                 r = e_sexp_result_new(f, ESEXP_RES_INT);
1731
1732                 r->value.number = camel_message_info_date_received(s->current);
1733         } else {
1734                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1735                 r->value.ptrarray = g_ptr_array_new ();
1736         }
1737
1738         return r;
1739 }
1740
1741 static ESExpResult *
1742 search_get_current_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1743 {
1744         ESExpResult *r;
1745
1746         r(printf("executing get-current-date\n"));
1747
1748         r = e_sexp_result_new(f, ESEXP_RES_INT);
1749         r->value.number = time (NULL);
1750         return r;
1751 }
1752
1753 static ESExpResult *
1754 search_get_size (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1755 {
1756         ESExpResult *r;
1757
1758         r(printf("executing get-size\n"));
1759
1760         /* are we inside a match-all? */
1761         if (s->current) {
1762                 r = e_sexp_result_new (f, ESEXP_RES_INT);
1763                 r->value.number = camel_message_info_size(s->current) / 1024;
1764         } else {
1765                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1766                 r->value.ptrarray = g_ptr_array_new ();
1767         }
1768
1769         return r;
1770 }
1771
1772 static ESExpResult *
1773 search_uid(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1774 {
1775         ESExpResult *r;
1776         gint i;
1777
1778         r(printf("executing uid\n"));
1779
1780         /* are we inside a match-all? */
1781         if (search->current) {
1782                 gint truth = FALSE;
1783                 const gchar *uid = camel_message_info_uid(search->current);
1784
1785                 /* performs an OR of all words */
1786                 for (i=0;i<argc && !truth;i++) {
1787                         if (argv[i]->type == ESEXP_RES_STRING
1788                             && !strcmp(uid, argv[i]->value.string)) {
1789                                 truth = TRUE;
1790                                 break;
1791                         }
1792                 }
1793                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1794                 r->value.boolean = truth;
1795         } else {
1796                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1797                 r->value.ptrarray = g_ptr_array_new();
1798                 for (i=0;i<argc;i++) {
1799                         if (argv[i]->type == ESEXP_RES_STRING)
1800                                 g_ptr_array_add(r->value.ptrarray, argv[i]->value.string);
1801                 }
1802         }
1803
1804         return r;
1805 }
1806
1807 static gint
1808 read_uid_callback (gpointer  ref, gint ncol, gchar ** cols, gchar **name)
1809 {
1810         GPtrArray *matches;
1811
1812         matches = (GPtrArray *) ref;
1813
1814         g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (cols [0]));
1815         return 0;
1816 }
1817
1818 static ESExpResult *
1819 search_message_location (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1820 {
1821         ESExpResult *r;
1822         gboolean same = FALSE;
1823
1824         if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
1825                 if (argv[0]->value.string && search->folder && search->folder->parent_store && camel_folder_get_full_name (search->folder)) {
1826                         CamelFolderInfo *fi = camel_store_get_folder_info (search->folder->parent_store, camel_folder_get_full_name (search->folder), 0, NULL);
1827                         if (fi) {
1828                                 same = g_str_equal (fi->uri ? fi->uri : "", argv[0]->value.string);
1829
1830                                 camel_store_free_folder_info (search->folder->parent_store, fi);
1831                         }
1832                 }
1833         }
1834
1835         if (search->current) {
1836                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1837                 r->value.boolean = same ? TRUE : FALSE;
1838         } else {
1839                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1840                 r->value.ptrarray = g_ptr_array_new ();
1841
1842                 if (same) {
1843                         /* all matches */
1844                         gint i;
1845                         GPtrArray *v = search->summary_set ? search->summary_set : search->summary;
1846
1847                         for (i = 0; i < v->len; i++) {
1848                                 gchar *uid = g_ptr_array_index (v, i);
1849
1850                                 g_ptr_array_add (r->value.ptrarray, uid);
1851                         }
1852                 }
1853         }
1854
1855         return r;
1856 }