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