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