2007-05-30 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-list.h>
12 #include <tny-iterator.h>
13 #include <tny-simple-list.h>
14
15 #include "modest-text-utils.h"
16
17 #include "modest-search.h"
18
19
20 static GList*
21 add_header (GList *list, TnyHeader *header, TnyFolder *folder)
22 {
23         gchar *furl = tny_folder_get_url_string (folder);
24         const gchar *uid = tny_header_get_uid (header);
25         gchar *str  = g_strdup_printf ("%s/%s", furl, uid);
26         g_free (furl);
27         return g_list_prepend (list, str);
28 }
29
30 static gboolean
31 read_chunk (TnyStream *stream, char *buffer, gsize count, gsize *nread)
32 {
33         gsize _nread;
34         gssize res;
35
36         _nread = 0;
37         while (_nread < count) {
38                 res = tny_stream_read (stream,
39                                        buffer + _nread, 
40                                        count - _nread);
41                 if (res == -1) {
42                         *nread = _nread;
43                         return FALSE;
44                 }
45
46                 if (res == 0)
47                         break;
48
49                 _nread += res;
50         }
51
52         *nread = _nread;
53         return TRUE;
54
55
56 }
57
58 #ifdef MODEST_HAVE_OGS
59 static gboolean
60 search_mime_part_ogs (TnyMimePart *part, ModestSearch *search)
61 {
62         TnyStream *stream;
63         char       buffer[4096];
64         gsize      len;
65         gsize      nread;
66         gboolean   is_html = FALSE;
67         gboolean   found;
68         gboolean   res;
69
70
71         if (! tny_mime_part_content_type_is (part, "text/ *") ||
72             ! (is_html = tny_mime_part_content_type_is (part, "text/html"))) {
73                 return FALSE;
74         }
75
76         found = FALSE;
77         len = sizeof (buffer);
78         stream = tny_mime_part_get_stream (part);
79
80         while ((res = read_chunk (stream, buffer, len, &nread))) {
81
82                 if (is_html) {
83
84                         found = ogs_text_searcher_search_html (search->text_searcher,
85                                                                buffer,
86                                                                nread,
87                                                                nread < len);
88                 } else {
89                         found = ogs_text_searcher_search_text (search->text_searcher,
90                                                                buffer,
91                                                                nread);
92                 }
93
94                 if (found) {
95                         break;
96                 }
97
98         }
99
100         if (!found) {
101                 found = ogs_text_searcher_search_done (search->text_searcher);
102         }
103
104         ogs_text_searcher_reset (search->text_searcher);
105
106         return found;
107 }
108 #endif
109
110 static gboolean
111 search_mime_part_strcmp (TnyMimePart *part, ModestSearch *search)
112 {
113         TnyStream *stream;
114         char       buffer[8193];
115         char      *chunk[2];
116         gsize      len;
117         gssize     nread;
118         gboolean   found;
119         gboolean   res;
120
121         if (! tny_mime_part_content_type_is (part, "text/ *")) {
122                 return FALSE;
123         }
124
125         found = FALSE;
126         len = (sizeof (buffer) - 1) / 2;
127
128         if (strlen (search->body) > len) {
129                 g_warning ("Search term bigger then chunk."
130                            "We might not find everything");     
131         }
132
133         stream = tny_mime_part_get_stream (part);
134
135         memset (buffer, 0, sizeof (buffer));
136         chunk[0] = buffer;
137         chunk[1] = buffer + len;
138
139         res = read_chunk (stream, chunk[0], len, &nread);
140
141         if (res == FALSE) {
142                 goto done;
143         }
144
145         found = !modest_text_utils_utf8_strcmp (search->body,
146                                                 buffer,
147                                                 TRUE);
148         if (found) {
149                 goto done;
150         }
151
152         /* This works like this:
153          * buffer: [ooooooooooo|xxxxxxxxxxxx|\0] 
154          *          ^chunk[0]  ^chunk[1]
155          * we have prefilled chunk[0] now we always read into chunk[1]
156          * and then move the content of chunk[1] to chunk[0].
157          * The idea is to prevent not finding search terms that are
158          * spread across 2 reads:        
159          * buffer: [ooooooooTES|Txxxxxxxxxxx|\0] 
160          * We should catch that because we always search the whole
161          * buffer not only the chunks.
162          *
163          * Of course that breaks for search terms > sizeof (chunk)
164          * but sizeof (chunk) should be big enough I guess (see
165          * the g_warning in this function)
166          * */   
167         while ((res = read_chunk (stream, chunk[1], len, &nread))) {
168                 buffer[len + nread] = '\0';
169
170                 found = !modest_text_utils_utf8_strcmp (search->body,
171                                                         buffer,
172                                                         TRUE);
173
174                 if (found) {
175                         break;
176                 }
177
178                 /* also move the \0 */
179                 g_memmove (chunk[0], chunk[1], len + 1);
180         }
181
182 done:
183         g_object_unref (stream);
184         return found;
185 }
186
187 static gboolean
188 search_string (const char      *what,
189                const char      *where,
190                ModestSearch    *search)
191 {
192         gboolean found;
193 #ifdef MODEST_HAVE_OGS
194         if (search->flags & MODEST_SEARCH_USE_OGS) {
195                 found = ogs_text_searcher_search_text (search->text_searcher,
196                                                        where,
197                                                        strlen (where));
198
199                 ogs_text_searcher_reset (search->text_searcher);
200         } else {
201 #endif
202                 found = !modest_text_utils_utf8_strcmp (what, where, TRUE);
203 #ifdef MODEST_HAVE_OGS
204         }
205 #endif
206         return found;
207 }
208
209
210 /**
211  * modest_search:
212  * @folder: a #TnyFolder instance
213  * @search: a #ModestSearch query
214  *
215  * This operation will search @folder for headers that match the query @search.
216  * It will return a doubly linked list with URIs that point to the message.
217  **/
218 GList *
219 modest_search (TnyFolder *folder, ModestSearch *search)
220 {
221         GList *retval = NULL;
222         TnyIterator *iter;
223         TnyList *list;
224         gboolean (*part_search_func) (TnyMimePart *part, ModestSearch *search);
225
226         part_search_func = search_mime_part_strcmp;
227
228 #ifdef MODEST_HAVE_OGS
229         if (search->flags & MODEST_SEARCH_USE_OGS) {
230         
231                 if (search->text_searcher == NULL && search->query != NULL) {
232                         OgsTextSearcher *text_searcher; 
233
234                         text_searcher = ogs_text_searcher_new (FALSE);
235                         ogs_text_searcher_parse_query (text_searcher, search->query);
236                         search->text_searcher = text_searcher;
237                 }
238
239                 part_search_func = search_mime_part_ogs;
240         }
241 #endif
242
243         list = tny_simple_list_new ();
244         tny_folder_get_headers (folder, list, FALSE, NULL);
245
246         iter = tny_list_create_iterator (list);
247
248         while (!tny_iterator_is_done (iter)) {
249                 TnyHeader *cur = (TnyHeader *) tny_iterator_get_current (iter);
250                 time_t t = tny_header_get_date_sent (cur);
251                 gboolean found = FALSE;
252
253                 if (search->flags & MODEST_SEARCH_BEFORE)
254                         if (!(t <= search->before))
255                                 goto go_next;
256
257                 if (search->flags & MODEST_SEARCH_AFTER)
258                         if (!(t >= search->after))
259                                 goto go_next;
260
261                 if (search->flags & MODEST_SEARCH_SIZE)
262                         if (tny_header_get_message_size  (cur) < search->minsize)
263                                 goto go_next;
264
265                 if (search->flags & MODEST_SEARCH_SUBJECT) {
266                         const char *str = tny_header_get_subject (cur);
267
268                         if ((found = search_string (search->subject, str, search))) {
269                             retval = add_header (retval, cur, folder);
270                         }
271                 }
272                 
273                 if (!found && search->flags & MODEST_SEARCH_SENDER) {
274                         const char *str = tny_header_get_from (cur);
275
276                         if ((found = search_string (search->from, str, search))) {
277                                 retval = add_header (retval, cur, folder);
278                         }
279                 }
280                 
281                 if (!found && search->flags & MODEST_SEARCH_RECIPIENT) {
282                         const char *str = tny_header_get_to (cur);
283
284                         if ((found = search_string (search->recipient, str, search))) {
285                                 retval = add_header (retval, cur, folder);
286                         }
287                 }
288                 
289                 if (!found && search->flags & MODEST_SEARCH_BODY) {
290                         TnyHeaderFlags flags;
291                         GError      *err = NULL;
292                         TnyMsg      *msg = NULL;
293                         TnyIterator *piter;
294                         TnyList     *parts;
295
296                         flags = tny_header_get_flags (cur);
297
298                         if (!(flags & TNY_HEADER_FLAG_CACHED)) {
299                                 goto go_next;
300                         }
301
302                         msg = tny_folder_get_msg (folder, cur, &err);
303
304                         if (err != NULL || msg == NULL) {
305                                 g_warning ("Could not get message\n");
306                                 g_error_free (err);
307
308                                 if (msg) {
309                                         g_object_unref (msg);
310                                 }
311                         }       
312
313                         parts = tny_simple_list_new ();
314                         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
315
316                         piter = tny_list_create_iterator (parts);
317                         while (!found && !tny_iterator_is_done (piter)) {
318                                 TnyMimePart *pcur = (TnyMimePart *) tny_iterator_get_current (piter);
319
320                                 if ((found = part_search_func (pcur, search))) {
321                                         retval = add_header (retval, cur, folder);                              
322                                 }
323
324                                 g_object_unref (pcur);
325                                 tny_iterator_next (piter);
326                         }
327
328                         g_object_unref (piter);
329                         g_object_unref (parts);
330                         g_object_unref (msg);
331
332                 }
333 go_next:
334                 g_object_unref (cur);
335                 tny_iterator_next (iter);
336         }
337
338         g_object_unref (iter);
339         g_object_unref (list);
340         return retval;
341 }
342