2007-06-03 Christian Kellner <ckellner@openismus.com>
[modest] / src / modest-search.c
1 #define _GNU_SOURCE
2
3 #ifdef HAVE_CONFIG_H
4 #include <config.h>
5 #endif
6
7 #include <string.h>
8
9 #include <tny-shared.h>
10 #include <tny-folder.h>
11 #include <tny-folder-store.h>
12 #include <tny-list.h>
13 #include <tny-iterator.h>
14 #include <tny-simple-list.h>
15
16 #include <libmodest-dbus-client/libmodest-dbus-client.h>
17
18 #include "modest-text-utils.h"
19 #include "modest-account-mgr.h"
20 #include "modest-tny-account-store.h"
21 #include "modest-tny-account.h"
22 #include "modest-search.h"
23 #include "modest-runtime.h"
24
25 static char *
26 g_strdup_or_null (const char *str)
27 {
28         char *string = NULL;
29
30         if  (str != NULL) {
31                 string = g_strdup (str);
32         }
33
34         return string;
35 }
36
37 static GList*
38 add_hit (GList *list, TnyHeader *header, TnyFolder *folder)
39 {
40         ModestSearchHit *hit;
41         TnyHeaderFlags   flags;
42         char            *furl;
43         char            *msg_url;
44         const char      *uid;
45         const char      *subject;
46         const char      *sender;
47
48         hit = g_slice_new0 (ModestSearchHit);
49
50         furl = tny_folder_get_url_string (folder);
51         uid = tny_header_get_uid (header);
52         msg_url = g_strdup_printf ("%s/%s", furl, uid);
53         subject = tny_header_get_subject (header);
54         sender = tny_header_get_from (header);
55
56         flags = tny_header_get_flags (header);
57
58         hit->msgid = msg_url;
59         hit->subject = g_strdup_or_null (subject);
60         hit->sender = g_strdup_or_null (sender);
61         hit->folder = furl;
62         hit->msize = tny_header_get_message_size (header);
63         hit->has_attachment = flags & TNY_HEADER_FLAG_ATTACHMENTS;
64         hit->is_unread = ! (flags & TNY_HEADER_FLAG_SEEN);
65         hit->timestamp = tny_header_get_date_received (header);
66
67         return g_list_prepend (list, hit);
68 }
69
70 static gboolean
71 read_chunk (TnyStream *stream, char *buffer, gsize count, gsize *nread)
72 {
73         gsize _nread;
74         gssize res;
75
76         _nread = 0;
77         while (_nread < count) {
78                 res = tny_stream_read (stream,
79                                        buffer + _nread, 
80                                        count - _nread);
81                 if (res == -1) {
82                         *nread = _nread;
83                         return FALSE;
84                 }
85
86                 if (res == 0)
87                         break;
88
89                 _nread += res;
90         }
91
92         *nread = _nread;
93         return TRUE;
94
95
96 }
97
98 #ifdef MODEST_HAVE_OGS
99 static gboolean
100 search_mime_part_ogs (TnyMimePart *part, ModestSearch *search)
101 {
102         TnyStream *stream;
103         char       buffer[4096];
104         gsize      len;
105         gsize      nread;
106         gboolean   is_html = FALSE;
107         gboolean   found;
108         gboolean   res;
109
110
111         if (! tny_mime_part_content_type_is (part, "text/ *") ||
112             ! (is_html = tny_mime_part_content_type_is (part, "text/html"))) {
113                 return FALSE;
114         }
115
116         found = FALSE;
117         len = sizeof (buffer);
118         stream = tny_mime_part_get_stream (part);
119
120         while ((res = read_chunk (stream, buffer, len, &nread))) {
121
122                 if (is_html) {
123
124                         found = ogs_text_searcher_search_html (search->text_searcher,
125                                                                buffer,
126                                                                nread,
127                                                                nread < len);
128                 } else {
129                         found = ogs_text_searcher_search_text (search->text_searcher,
130                                                                buffer,
131                                                                nread);
132                 }
133
134                 if (found) {
135                         break;
136                 }
137
138         }
139
140         if (!found) {
141                 found = ogs_text_searcher_search_done (search->text_searcher);
142         }
143
144         ogs_text_searcher_reset (search->text_searcher);
145
146         return found;
147 }
148 #endif
149
150 static gboolean
151 search_mime_part_strcmp (TnyMimePart *part, ModestSearch *search)
152 {
153         TnyStream *stream;
154         char       buffer[8193];
155         char      *chunk[2];
156         gsize      len;
157         gssize     nread;
158         gboolean   found;
159         gboolean   res;
160
161         if (! tny_mime_part_content_type_is (part, "text/ *")) {
162                 return FALSE;
163         }
164
165         found = FALSE;
166         len = (sizeof (buffer) - 1) / 2;
167
168         if (strlen (search->body) > len) {
169                 g_warning ("Search term bigger then chunk."
170                            "We might not find everything");     
171         }
172
173         stream = tny_mime_part_get_stream (part);
174
175         memset (buffer, 0, sizeof (buffer));
176         chunk[0] = buffer;
177         chunk[1] = buffer + len;
178
179         res = read_chunk (stream, chunk[0], len, &nread);
180
181         if (res == FALSE) {
182                 goto done;
183         }
184
185         found = !modest_text_utils_utf8_strcmp (search->body,
186                                                 buffer,
187                                                 TRUE);
188         if (found) {
189                 goto done;
190         }
191
192         /* This works like this:
193          * buffer: [ooooooooooo|xxxxxxxxxxxx|\0] 
194          *          ^chunk[0]  ^chunk[1]
195          * we have prefilled chunk[0] now we always read into chunk[1]
196          * and then move the content of chunk[1] to chunk[0].
197          * The idea is to prevent not finding search terms that are
198          * spread across 2 reads:        
199          * buffer: [ooooooooTES|Txxxxxxxxxxx|\0] 
200          * We should catch that because we always search the whole
201          * buffer not only the chunks.
202          *
203          * Of course that breaks for search terms > sizeof (chunk)
204          * but sizeof (chunk) should be big enough I guess (see
205          * the g_warning in this function)
206          * */   
207         while ((res = read_chunk (stream, chunk[1], len, &nread))) {
208                 buffer[len + nread] = '\0';
209
210                 found = !modest_text_utils_utf8_strcmp (search->body,
211                                                         buffer,
212                                                         TRUE);
213
214                 if (found) {
215                         break;
216                 }
217
218                 /* also move the \0 */
219                 g_memmove (chunk[0], chunk[1], len + 1);
220         }
221
222 done:
223         g_object_unref (stream);
224         return found;
225 }
226
227 static gboolean
228 search_string (const char      *what,
229                const char      *where,
230                ModestSearch    *search)
231 {
232         gboolean found;
233 #ifdef MODEST_HAVE_OGS
234         if (search->flags & MODEST_SEARCH_USE_OGS) {
235                 found = ogs_text_searcher_search_text (search->text_searcher,
236                                                        where,
237                                                        strlen (where));
238
239                 ogs_text_searcher_reset (search->text_searcher);
240         } else {
241 #endif
242                 if (what == NULL || where == NULL) {
243                         return FALSE;
244                 }
245
246                 found = !modest_text_utils_utf8_strcmp (what, where, TRUE);
247 #ifdef MODEST_HAVE_OGS
248         }
249 #endif
250         return found;
251 }
252
253
254
255 /**
256  * modest_search:
257  * @folder: a #TnyFolder instance
258  * @search: a #ModestSearch query
259  *
260  * This operation will search @folder for headers that match the query @search.
261  * It will return a doubly linked list with URIs that point to the message.
262  **/
263 GList *
264 modest_search_folder (TnyFolder *folder, ModestSearch *search)
265 {
266         GList *retval = NULL;
267         TnyIterator *iter;
268         TnyList *list;
269         gboolean (*part_search_func) (TnyMimePart *part, ModestSearch *search);
270
271         part_search_func = search_mime_part_strcmp;
272
273 #ifdef MODEST_HAVE_OGS
274         if (search->flags & MODEST_SEARCH_USE_OGS) {
275         
276                 if (search->text_searcher == NULL && search->query != NULL) {
277                         OgsTextSearcher *text_searcher; 
278
279                         text_searcher = ogs_text_searcher_new (FALSE);
280                         ogs_text_searcher_parse_query (text_searcher, search->query);
281                         search->text_searcher = text_searcher;
282                 }
283
284                 part_search_func = search_mime_part_ogs;
285         }
286 #endif
287
288         list = tny_simple_list_new ();
289         tny_folder_get_headers (folder, list, FALSE, NULL);
290
291         iter = tny_list_create_iterator (list);
292
293         while (!tny_iterator_is_done (iter)) {
294                 TnyHeader *cur = (TnyHeader *) tny_iterator_get_current (iter);
295                 time_t t = tny_header_get_date_sent (cur);
296                 gboolean found = FALSE;
297                 
298                 if (search->flags & MODEST_SEARCH_BEFORE)
299                         if (!(t <= search->before))
300                                 goto go_next;
301
302                 if (search->flags & MODEST_SEARCH_AFTER)
303                         if (!(t >= search->after))
304                                 goto go_next;
305
306                 if (search->flags & MODEST_SEARCH_SIZE)
307                         if (tny_header_get_message_size (cur) < search->minsize)
308                                 goto go_next;
309
310                 if (search->flags & MODEST_SEARCH_SUBJECT) {
311                         const char *str = tny_header_get_subject (cur);
312
313                         if ((found = search_string (search->subject, str, search))) {
314                             retval = add_hit (retval, cur, folder);
315                         }
316                 }
317                 
318                 if (!found && search->flags & MODEST_SEARCH_SENDER) {
319                         const char *str = tny_header_get_from (cur);
320
321                         if ((found = search_string (search->from, str, search))) {
322                                 retval = add_hit (retval, cur, folder);
323                         }
324                 }
325                 
326                 if (!found && search->flags & MODEST_SEARCH_RECIPIENT) {
327                         const char *str = tny_header_get_to (cur);
328
329                         if ((found = search_string (search->recipient, str, search))) {
330                                 retval = add_hit (retval, cur, folder);
331                         }
332                 }
333         
334                 if (!found && search->flags & MODEST_SEARCH_BODY) {
335                         TnyHeaderFlags flags;
336                         GError      *err = NULL;
337                         TnyMsg      *msg = NULL;
338                         TnyIterator *piter;
339                         TnyList     *parts;
340
341                         flags = tny_header_get_flags (cur);
342
343                         if (!(flags & TNY_HEADER_FLAG_CACHED)) {
344                                 goto go_next;
345                         }
346
347                         msg = tny_folder_get_msg (folder, cur, &err);
348
349                         if (err != NULL || msg == NULL) {
350                                 g_warning ("Could not get message\n");
351                                 g_error_free (err);
352
353                                 if (msg) {
354                                         g_object_unref (msg);
355                                 }
356                         }       
357
358                         parts = tny_simple_list_new ();
359                         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
360
361                         piter = tny_list_create_iterator (parts);
362                         while (!found && !tny_iterator_is_done (piter)) {
363                                 TnyMimePart *pcur = (TnyMimePart *) tny_iterator_get_current (piter);
364
365                                 if ((found = part_search_func (pcur, search))) {
366                                         retval = add_hit (retval, cur, folder);                         
367                                 }
368
369                                 g_object_unref (pcur);
370                                 tny_iterator_next (piter);
371                         }
372
373                         g_object_unref (piter);
374                         g_object_unref (parts);
375                         g_object_unref (msg);
376
377                 }
378
379 go_next:
380                 g_object_unref (cur);
381                 tny_iterator_next (iter);
382         }
383
384         g_object_unref (iter);
385         g_object_unref (list);
386         return retval;
387 }
388
389 GList *
390 modest_search_account (TnyAccount *account, ModestSearch *search)
391 {
392         TnyFolderStore      *store;
393         TnyIterator         *iter;
394         TnyList             *folders;
395         GList               *hits;
396         GError              *error;
397
398         error = NULL;
399         hits = NULL;
400
401         store = TNY_FOLDER_STORE (account);
402
403         folders = tny_simple_list_new ();
404         tny_folder_store_get_folders (store, folders, NULL, &error);
405         
406         if (error != NULL) {
407                 g_object_unref (folders);
408                 return NULL;
409         }
410
411         iter = tny_list_create_iterator (folders);
412         while (!tny_iterator_is_done (iter)) {
413                 TnyFolder *folder;
414                 GList     *res;
415
416                 folder = TNY_FOLDER (tny_iterator_get_current (iter));
417                 
418                 res = modest_search_folder (folder, search);
419
420                 if (res != NULL) {
421                         if (hits == NULL) {
422                                 hits = res;
423                         } else {
424                                 hits = g_list_concat (hits, res);
425                         }
426                 }
427
428                 g_object_unref (folder);
429                 tny_iterator_next (iter);
430         }
431
432         g_object_unref (iter);
433         g_object_unref (folders);
434
435         return hits;
436 }
437
438 GList *
439 modest_search_all_accounts (ModestSearch *search)
440 {
441         ModestTnyAccountStore *astore;
442         TnyList               *accounts;
443         TnyIterator           *iter;
444         GList                 *hits;
445
446         hits = NULL;
447         astore = modest_runtime_get_account_store ();
448
449         accounts = tny_simple_list_new ();
450         tny_account_store_get_accounts (TNY_ACCOUNT_STORE (astore),
451                                         accounts,
452                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
453
454         iter = tny_list_create_iterator (accounts);
455         while (!tny_iterator_is_done (iter)) {
456                 TnyAccount *account;
457                 GList      *res;
458
459                 account = TNY_ACCOUNT (tny_iterator_get_current (iter));
460
461                 g_debug ("Searching account %s",
462                          tny_account_get_name (account));
463                 res = modest_search_account (account, search);
464                 
465                 if (res != NULL) {
466
467                         if (hits == NULL) {
468                                 hits = res;
469                         } else {
470                                 hits = g_list_concat (hits, res);
471                         }
472                 }
473
474                 g_object_unref (account);
475                 tny_iterator_next (iter);
476         }
477
478         g_object_unref (accounts);
479
480         return hits;
481 }
482
483