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