change the CamelFolderSummary interfaces to allow partial summary queries
[platform/upstream/evolution-data-server.git] / camel / providers / mbox / camel-mbox-search.c
1 /* 
2  * Copyright 2000 HelixCode (http://www.helixcode.com).
3  *
4  * Author : 
5  *  Michael Zucchi <notzed@helixcode.com>
6
7  * This program is free software; you can redistribute it and/or 
8  * modify it under the terms of the GNU General Public License as 
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20  * USA
21  */
22
23 #include <glib.h>
24 #include <stdio.h>
25 #include <time.h>
26 #include <string.h>
27
28
29 #include <camel/gmime-utils.h>
30 #include <camel/camel-log.h>
31 #include "camel/camel-mime-message.h"
32 #include "camel/camel-mime-part.h"
33 #include "camel/camel-stream.h"
34 #include "camel/camel-stream-fs.h"
35 #include "camel/camel.h"
36 #include "camel-mbox-folder.h"
37 #include "camel-mbox-summary.h"
38
39 #include "camel-mbox-search.h"
40 #define HAVE_FILTER
41 #ifdef HAVE_FILTER
42 #include "e-sexp.h"
43
44 #define HAVE_IBEX
45 #ifdef HAVE_IBEX
46 #include "ibex.h"
47 #endif
48
49 #define p(x)                    /* parse debug */
50 #define r(x)                    /* run debug */
51 #define d(x)                    /* general debug */
52
53
54 /*
55
56   Matching operators:
57
58   list = (body-contains string+)
59   bool = (body-contains string+)
60         Returns a list of all messages containing any of the strings in the message.
61         If within a match-all, then returns true for the current message.
62
63   list = (match-all bool-expr)
64         Returns a list of all messages for which the bool expression is true.
65         The bool-expr is evaluated for each message in turn.
66         It is more efficient not to perform body-content comparisons inside a
67         match-all operator.
68
69   int = (date-sent)
70         Returns a time_t of the date-sent of the message.
71
72   bool = (header-contains string string+)
73         Returns true if the current message (inside a match-all operator)
74         has a header 'string1', which contains any of the following strings.
75 */
76
77
78 struct _searchcontext {
79         int id;                 /* id of this search */
80         int cancelled;          /* search cancelled? */
81
82         CamelFolder *folder;
83
84 #ifdef HAVE_IBEX
85         ibex *index;            /* index of content for this folder */
86 #endif
87
88         CamelFolderSummary *summary;
89         const GArray *message_info;
90
91         CamelMessageInfo *message_current;      /* when performing a (match  operation */
92 };
93
94 struct _glib_sux_donkeys {
95         int count;
96         GPtrArray *uids;
97 };
98 /* or, store all unique values */
99 static void
100 g_lib_sux_htor(char *key, int value, struct _glib_sux_donkeys *fuckup)
101 {
102         g_ptr_array_add(fuckup->uids, key);
103 }
104
105 static ESExpResult *
106 func_body_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
107 {
108         ESExpResult *r;
109         int i, j;
110         struct _searchcontext *ctx = data;
111
112         if (ctx->message_current) {
113                 int truth = FALSE;
114
115                 r = e_sexp_result_new(ESEXP_RES_BOOL);
116                 if (ctx->index) {
117                         for (i=0;i<argc && !truth;i++) {
118                                 if (argv[i]->type == ESEXP_RES_STRING) {
119                                         truth = ibex_find_name(ctx->index, ctx->message_current->uid, argv[i]->value.string);
120                                 } else {
121                                         g_warning("Invalid type passed to body-contains match function");
122                                 }
123                         }
124                 } else {
125                         g_warning("Cannot perform indexed query with no index");
126                 }
127                 r->value.bool = truth;
128         } else {
129                 r = e_sexp_result_new(ESEXP_RES_ARRAY_PTR);
130
131                 if (ctx->index) {
132                         if (argc==1) {
133                                 /* common case */
134                                 r->value.ptrarray = ibex_find(ctx->index, argv[0]->value.string);
135                         } else {
136                                 GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
137                                 GPtrArray *pa;
138                                 struct _glib_sux_donkeys lambdafoo;
139
140                                 /* this sux, perform an or operation on the result(s) of each word */
141                                 for (i=0;i<argc;i++) {
142                                         if (argv[i]->type == ESEXP_RES_STRING) {
143                                                 pa = ibex_find(ctx->index, argv[i]->value.string);
144                                                 for (j=0;j<pa->len;j++) {
145                                                         g_hash_table_insert(ht, g_ptr_array_index(pa, j), (void *)1);
146                                                 }
147                                                 g_ptr_array_free(pa, FALSE);
148                                         }
149                                 }
150                                 lambdafoo.uids = g_ptr_array_new();
151                                 g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htor, &lambdafoo);
152                                 r->value.ptrarray = lambdafoo.uids;
153                         }
154                 } else {
155                         r->value.ptrarray = g_ptr_array_new();
156                 }
157         }
158
159         return r;
160 }
161
162 static ESExpResult *
163 func_date_sent(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
164 {
165         ESExpResult *r;
166         struct _searchcontext *ctx = data;
167
168         r = e_sexp_result_new(ESEXP_RES_INT);
169
170         if (ctx->message_current) {
171                 g_warning("FIXME: implement date parsing ...");
172                 /* r->value.number = get_date(ctx->message_current); */
173         } else {
174                 r->value.number = time(0);
175         }
176         return r;
177 }
178
179
180 static ESExpResult *
181 func_match_all(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data)
182 {
183         int i;
184         ESExpResult *r, *r1;
185         struct _searchcontext *ctx = data;
186
187         if (argc>1) {
188                 g_warning("match-all only takes a single argument, other arguments ignored");
189         }
190         r = e_sexp_result_new(ESEXP_RES_ARRAY_PTR);
191         r->value.ptrarray = g_ptr_array_new();
192
193         for (i=0;i<ctx->message_info->len;i++) {
194                 if (argc>0) {
195                         ctx->message_current = &g_array_index(ctx->message_info, CamelMessageInfo, i);
196                         r1 = e_sexp_term_eval(f, argv[0]);
197                         if (r1->type == ESEXP_RES_BOOL) {
198                                 if (r1->value.bool)
199                                         g_ptr_array_add(r->value.ptrarray, ctx->message_current->uid);
200                         } else {
201                                 g_warning("invalid syntax, matches require a single bool result");
202                         }
203                         e_sexp_result_free(r1);
204                 } else {
205                         g_ptr_array_add(r->value.ptrarray, ctx->message_current->uid);
206                 }
207         }
208         ctx->message_current = NULL;
209
210         return r;
211 }
212
213 static ESExpResult *
214 func_header_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
215 {
216         ESExpResult *r;
217         struct _searchcontext *ctx = data;
218         int truth = FALSE;
219
220         r(printf("executing header-contains\n"));
221
222         /* are we inside a match-all? */
223         if (ctx->message_current && argc>1
224             && argv[0]->type == ESEXP_RES_STRING) {
225                 char *headername, *header;
226                 int i;
227
228                 /* only a subset of headers are supported .. */
229                 headername = argv[0]->value.string;
230                 if (!strcasecmp(headername, "subject")) {
231                         header = ctx->message_current->subject;
232                 } else if (!strcasecmp(headername, "date")) {
233                         header = ctx->message_current->sent_date;
234                 } else if (!strcasecmp(headername, "from")) {
235                         header = ctx->message_current->sender;
236                 } else {
237                         g_warning("Performing query on unknown header: %s", headername);
238                         header = NULL;
239                 }
240
241                 if (header) {
242                         for (i=1;i<argc && !truth;i++) {
243                                 if (argv[i]->type == ESEXP_RES_STRING
244                                     && strstr(header, argv[i]->value.string)) {
245                                         printf("%s got a match with %s of %s\n", ctx->message_current->uid, header, argv[i]->value.string);
246                                         truth = TRUE;
247                                         break;
248                                 }
249                         }
250                 }
251         }
252         r = e_sexp_result_new(ESEXP_RES_BOOL);
253         r->value.bool = truth;
254
255         return r;
256 }
257
258
259 /* 'builtin' functions */
260 static struct {
261         char *name;
262         ESExpFunc *func;
263         int type;               /* set to 1 if a function can perform shortcut evaluation, or
264                                    doesn't execute everything, 0 otherwise */
265 } symbols[] = {
266         { "body-contains", func_body_contains, 0 },
267         { "date-sent", func_date_sent, 0 },
268         { "match-all", (ESExpFunc *)func_match_all, 1 },
269         { "header-contains", func_header_contains, 0 },
270 };
271
272 int camel_mbox_folder_search_by_expression(CamelFolder *folder, const char *expression,
273                                            CamelSearchFunc *func, void *data, CamelException *ex)
274 {
275         int i;
276         struct _searchcontext *ctx;
277         GList *matches = NULL;
278         ESExp *f;
279         ESExpResult *r;
280
281         /* setup our expression evaluator */
282         f = e_sexp_new();
283
284         ctx = g_malloc0(sizeof(*ctx));
285
286         ctx->id = ((CamelMboxFolder *)folder)->search_id++;
287
288         /* setup out context */
289         ctx->folder = folder;
290         ctx->summary = camel_folder_get_summary(folder, ex);
291         
292         if (ctx->summary == NULL || camel_exception_get_id (ex)) {
293                 printf ("Cannot get summary\n"
294                         "Full description : %s\n", camel_exception_get_description (ex));
295                 g_free(ctx);
296                 gtk_object_unref((GtkObject *)f);
297                 return -1;
298         }
299
300         gtk_object_ref((GtkObject *)ctx->summary);
301
302         /* FIXME: the index should be global to the folder */
303         ctx->message_info = CAMEL_MBOX_SUMMARY(ctx->summary)->message_info;
304         ctx->message_current = NULL;
305         ctx->index = ibex_open(CAMEL_MBOX_FOLDER(folder)->index_file_path, FALSE);
306         if (!ctx->index) {
307                 perror("Cannot open index file (ignored)");
308         }
309
310         ((CamelMboxFolder *)folder)->searches = g_list_append(((CamelMboxFolder *)folder)->searches, ctx);
311
312         for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) {
313                 if (symbols[i].type == 1) {
314                         e_sexp_add_ifunction(f, 0, symbols[i].name, (ESExpIFunc *)symbols[i].func, ctx);
315                 } else {
316                         e_sexp_add_function(f, 0, symbols[i].name, symbols[i].func, ctx);
317                 }
318         }
319
320         e_sexp_input_text(f, expression, strlen(expression));
321         e_sexp_parse(f);
322         r = e_sexp_eval(f);
323
324         /* now create a folder summary to return?? */
325         if (r
326             && r->type == ESEXP_RES_ARRAY_PTR) {
327                 d(printf("got result ...\n"));
328                 for (i=0;i<r->value.ptrarray->len;i++) {
329                         d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
330                         matches = g_list_prepend(matches, g_strdup(g_ptr_array_index(r->value.ptrarray, i)));
331                 }
332                 if (!ctx->cancelled) {
333                         func(folder, ctx->id, TRUE, matches, data);
334                 }
335                 g_list_free(matches);
336                 e_sexp_result_free(r);
337         } else {
338                 printf("no result!\n");
339         }
340
341         if (ctx->index)
342                 ibex_close(ctx->index);
343
344         gtk_object_unref((GtkObject *)ctx->summary);
345         gtk_object_unref((GtkObject *)f);
346         i = ctx->id;
347
348         ((CamelMboxFolder *)folder)->searches = g_list_remove(((CamelMboxFolder *)folder)->searches, ctx);
349
350         g_free(ctx);
351
352         return i;
353 }
354
355 static struct _searchcontext *
356 find_context(CamelMboxFolder *f, int id)
357 {
358         struct _searchcontext *ctx;
359         GList *l;
360
361         l = f->searches;
362         while (l) {
363                 ctx = l->data;
364                 if (ctx->id == id) {
365                         return ctx;
366                 }
367                 l = g_list_next(l);
368         }
369
370         return NULL;
371 }
372
373 gboolean camel_mbox_folder_search_complete(CamelFolder *folder, int searchid, int wait, CamelException *ex)
374 {
375         struct _searchcontext *ctx;
376
377         ctx = find_context((CamelMboxFolder *)folder, searchid);
378
379         if (ctx)
380                 return ctx->cancelled;
381
382         /* if its been removed, its complete ... */
383         return TRUE;
384 }
385
386 void camel_mbox_folder_search_cancel(CamelFolder *folder, int searchid, CamelException *ex)
387 {
388         struct _searchcontext *ctx;
389
390         ctx = find_context((CamelMboxFolder *)folder, searchid);
391         if (ctx) {
392                 ctx->cancelled = TRUE;
393                 return;
394         }
395
396         /* FIXME: set exception, return */
397 }
398
399 #else /* HAVE_FILTER */
400
401 int camel_mbox_folder_search_by_expression(CamelFolder *folder, const char *expression,
402                                            CamelSearchFunc *func, void *data, CamelException *ex)
403 {
404         return -1;
405 }
406
407 gboolean camel_mbox_folder_search_complete(CamelFolder *folder, int searchid, int wait, CamelException *ex)
408 {
409         return TRUE;
410 }
411
412 void camel_mbox_folder_search_cancel(CamelFolder *folder, int searchid, CamelException *ex)
413 {
414         /* empty */
415 }
416
417 #endif /*! HAVE_FILTER */