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