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