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