X-Git-Url: http://git.maemo.org/git/?p=modest;a=blobdiff_plain;f=src%2Fmodest-search.c;h=6146a8bbdd62579c9e01e3e6d94df4079a733f1a;hp=31aa485022a2e13568207f280237f7131af2d977;hb=94bf2a181e8b2644f037246b2fc3a70c865c22e2;hpb=65682ca70e6d3983f705ec94d7acc87418806f0c diff --git a/src/modest-search.c b/src/modest-search.c index 31aa485..6146a8b 100644 --- a/src/modest-search.c +++ b/src/modest-search.c @@ -43,15 +43,35 @@ #include #include #include - -#include +#include +#include #include "modest-text-utils.h" #include "modest-account-mgr.h" #include "modest-tny-account-store.h" #include "modest-tny-account.h" +#include "modest-tny-mime-part.h" +#include "modest-tny-folder.h" #include "modest-search.h" #include "modest-runtime.h" +#include "modest-platform.h" + +typedef struct +{ + guint pending_calls; + GList *msg_hits; + ModestSearch *search; + ModestSearchCallback callback; + gpointer user_data; + TnyList *all_folders; +} SearchHelper; + +static SearchHelper *create_helper (ModestSearchCallback callback, + ModestSearch *search, + gpointer user_data); + +static void _search_folder (TnyFolder *folder, + SearchHelper *helper); static gchar * g_strdup_or_null (const gchar *str) @@ -65,22 +85,21 @@ g_strdup_or_null (const gchar *str) return string; } - static GList* add_hit (GList *list, TnyHeader *header, TnyFolder *folder) { - ModestSearchHit *hit; + ModestSearchResultHit *hit; TnyHeaderFlags flags; char *furl; char *msg_url; - const char *uid; - const char *subject; - const char *sender; + char *uid; + char *subject; + char *sender; - hit = g_slice_new0 (ModestSearchHit); + hit = g_slice_new0 (ModestSearchResultHit); furl = tny_folder_get_url_string (folder); - printf ("DEBUG: %s: folder URL=%s\n", __FUNCTION__, furl); + g_debug ("%s: folder URL=%s\n", __FUNCTION__, furl); if (!furl) { g_warning ("%s: tny_folder_get_url_string(): returned NULL for folder. Folder name=%s\n", __FUNCTION__, tny_folder_get_name (folder)); } @@ -89,51 +108,58 @@ add_hit (GList *list, TnyHeader *header, TnyFolder *folder) * and/or find out what UID form is used when finding, in camel_data_cache_get(). * so we can find what we get. Philip is working on this. */ - uid = tny_header_get_uid (header); + uid = tny_header_dup_uid (header); if (!furl) { - g_warning ("%s: tny_header_get_uid(): returned NULL for message with subject=%s\n", __FUNCTION__, tny_header_get_subject (header)); + gchar *subject = tny_header_dup_subject (header); + g_warning ("%s: tny_header_get_uid(): returned NULL for message with subject=%s\n", __FUNCTION__, subject); + g_free (subject); } msg_url = g_strdup_printf ("%s/%s", furl, uid); g_free (furl); + g_free (uid); - subject = tny_header_get_subject (header); - sender = tny_header_get_from (header); + subject = tny_header_dup_subject (header); + sender = tny_header_dup_from (header); flags = tny_header_get_flags (header); hit->msgid = msg_url; - hit->subject = g_strdup_or_null (subject); - hit->sender = g_strdup_or_null (sender); + hit->subject = subject; + hit->sender = sender; hit->folder = g_strdup_or_null (tny_folder_get_name (folder)); hit->msize = tny_header_get_message_size (header); hit->has_attachment = flags & TNY_HEADER_FLAG_ATTACHMENTS; hit->is_unread = ! (flags & TNY_HEADER_FLAG_SEEN); - hit->timestamp = tny_header_get_date_received (header); + hit->timestamp = MIN (tny_header_get_date_received (header), tny_header_get_date_sent (header)); return g_list_prepend (list, hit); } +/** Call this until it returns FALSE or nread is set to 0. + * + * @result: FALSE is something failed. */ static gboolean read_chunk (TnyStream *stream, char *buffer, gsize count, gsize *nread) { - gsize _nread; - gssize res; + gsize _nread = 0; + gssize res = 0; - _nread = 0; while (_nread < count) { res = tny_stream_read (stream, buffer + _nread, count - _nread); - if (res == -1) { + if (res == -1) { /* error */ *nread = _nread; return FALSE; } - if (res == 0) - break; - _nread += res; + + if (res == 0) { /* no more bytes read. */ + *nread = _nread; + return TRUE; + } } *nread = _nread; @@ -141,34 +167,29 @@ read_chunk (TnyStream *stream, char *buffer, gsize count, gsize *nread) } #ifdef MODEST_HAVE_OGS +/* + * This function assumes that the mime part is of type "text / *" + */ static gboolean search_mime_part_ogs (TnyMimePart *part, ModestSearch *search) { - TnyStream *stream; + TnyStream *stream = NULL; char buffer[4096]; - gsize len; - gsize nread; - gboolean is_html = FALSE; - gboolean found; - gboolean res; - - - if (! tny_mime_part_content_type_is (part, "text/*") || - ! (is_html = tny_mime_part_content_type_is (part, "text/html"))) { - g_debug ("%s: No text or html MIME part found.\n", __FUNCTION__); - return FALSE; - } + const gsize len = sizeof (buffer); + gsize nread = 0; + gboolean is_text_html = FALSE; + gboolean found = FALSE; + gboolean res = FALSE; + + is_text_html = tny_mime_part_content_type_is (part, "text/html"); - found = FALSE; - len = sizeof (buffer); stream = tny_mime_part_get_stream (part); - while ((res = read_chunk (stream, buffer, len, &nread))) { - + res = read_chunk (stream, buffer, len, &nread); + while (res && (nread > 0)) { /* search->text_searcher was instantiated in modest_search_folder(). */ - if (is_html) { - + if (is_text_html) { found = ogs_text_searcher_search_html (search->text_searcher, buffer, nread, @@ -179,34 +200,34 @@ search_mime_part_ogs (TnyMimePart *part, ModestSearch *search) nread); } + /* HACK: this helps UI refreshes because the search + operations could be heavy */ + while (gtk_events_pending ()) + gtk_main_iteration (); + if (found) { break; } - + + nread = 0; + res = read_chunk (stream, buffer, len, &nread); } + g_object_unref (stream); if (!found) { found = ogs_text_searcher_search_done (search->text_searcher); } ogs_text_searcher_reset (search->text_searcher); - - /* debug stuff: - if (!found) { - buffer[len -1] = 0; - printf ("DEBUG: %s: query %s was not found in message text: %s\n", - __FUNCTION__, search->query, buffer); - - } else { - printf ("DEBUG: %s: found.\n", __FUNCTION__); - } - */ return found; } #else +/* + * This function assumes that the mime part is of type "text / *" + */ static gboolean search_mime_part_strcmp (TnyMimePart *part, ModestSearch *search) { @@ -218,11 +239,6 @@ search_mime_part_strcmp (TnyMimePart *part, ModestSearch *search) gboolean found; gboolean res; - if (! tny_mime_part_content_type_is (part, "text/*")) { - g_debug ("%s: No text MIME part found.\n", __FUNCTION__); - return FALSE; - } - found = FALSE; len = (sizeof (buffer) - 1) / 2; @@ -272,7 +288,12 @@ search_mime_part_strcmp (TnyMimePart *part, ModestSearch *search) buffer, TRUE); - if (found) { + /* HACK: this helps UI refreshes because the search + operations could be heavy */ + while (gtk_events_pending ()) + gtk_main_iteration (); + + if ((found)||(nread == 0)) { break; } @@ -291,7 +312,7 @@ search_string (const char *what, const char *where, ModestSearch *search) { - gboolean found; + gboolean found = FALSE; #ifdef MODEST_HAVE_OGS if (search->flags & MODEST_SEARCH_USE_OGS) { found = ogs_text_searcher_search_text (search->text_searcher, @@ -309,99 +330,140 @@ search_string (const char *what, #ifdef MODEST_HAVE_OGS } #endif + + /* HACK: this helps UI refreshes because the search + operations could be heavy */ + while (gtk_events_pending ()) + gtk_main_iteration (); + return found; } - -/** - * modest_search: - * @folder: a #TnyFolder instance - * @search: a #ModestSearch query - * - * This operation will search @folder for headers that match the query @search. - * It will return a doubly linked list with URIs that point to the message. - **/ -GList * -modest_search_folder (TnyFolder *folder, ModestSearch *search) +static gboolean +search_mime_part_and_child_parts (TnyMimePart *part, ModestSearch *search) { - GList *retval = NULL; - TnyIterator *iter = NULL; - TnyList *list = NULL; - -#ifdef MODEST_HAVE_OGS - if (search->flags & MODEST_SEARCH_USE_OGS) { + gboolean found = FALSE; + + /* Do not search into attachments */ + if (modest_tny_mime_part_is_attachment_for_modest (part) && !TNY_IS_MSG (part)) + return FALSE; + + #ifdef MODEST_HAVE_OGS + found = search_mime_part_ogs (part, search); + #else + found = search_mime_part_strcmp (part, search); + #endif + + if (found) { + return found; + } - if (search->text_searcher == NULL && search->query != NULL) { - OgsTextSearcher *text_searcher; + /* Check the child part too, recursively: */ + TnyList *child_parts = tny_simple_list_new (); + tny_mime_part_get_parts (TNY_MIME_PART (part), child_parts); - text_searcher = ogs_text_searcher_new (FALSE); - ogs_text_searcher_parse_query (text_searcher, search->query); - search->text_searcher = text_searcher; + TnyIterator *piter = tny_list_create_iterator (child_parts); + while (!found && !tny_iterator_is_done (piter)) { + TnyMimePart *pcur = (TnyMimePart *) tny_iterator_get_current (piter); + if (pcur) { + found = search_mime_part_and_child_parts (pcur, search); + + g_object_unref (pcur); } + + tny_iterator_next (piter); } -#endif - list = tny_simple_list_new (); - GError *error = NULL; - tny_folder_get_headers (folder, list, FALSE /* don't refresh */, &error); - if (error) { - g_warning ("%s: tny_folder_get_headers() failed with error=%s.\n", - __FUNCTION__, error->message); - g_error_free (error); - error = NULL; + g_object_unref (piter); + g_object_unref (child_parts); + + return found; +} + +static void +search_next_folder (SearchHelper *helper) +{ + TnyIterator *iter = tny_list_create_iterator (helper->all_folders); + TnyFolder *first = TNY_FOLDER (tny_iterator_get_current (iter)); + + _search_folder (first, helper); + + g_object_unref (first); + g_object_unref (iter); +} + +static void +modest_search_folder_get_headers_cb (TnyFolder *folder, + gboolean cancelled, + TnyList *headers, + GError *err, + gpointer user_data) +{ + TnyIterator *iter = NULL; + SearchHelper *helper; + + helper = (SearchHelper *) user_data; + + if (err || cancelled) { + goto end; } - iter = tny_list_create_iterator (list); + iter = tny_list_create_iterator (headers); while (!tny_iterator_is_done (iter)) { + TnyHeader *cur = (TnyHeader *) tny_iterator_get_current (iter); - time_t t = tny_header_get_date_sent (cur); + const time_t t = tny_header_get_date_sent (cur); gboolean found = FALSE; - if (search->flags & MODEST_SEARCH_BEFORE) - if (!(t <= search->before)) + /* Ignore deleted (not yet expunged) emails: */ + if (tny_header_get_flags(cur) & TNY_HEADER_FLAG_DELETED) + goto go_next; + + if (helper->search->flags & MODEST_SEARCH_BEFORE) + if (!(t <= helper->search->end_date)) goto go_next; - if (search->flags & MODEST_SEARCH_AFTER) - if (!(t >= search->after)) + if (helper->search->flags & MODEST_SEARCH_AFTER) + if (!(t >= helper->search->start_date)) goto go_next; - if (search->flags & MODEST_SEARCH_SIZE) - if (tny_header_get_message_size (cur) < search->minsize) + if (helper->search->flags & MODEST_SEARCH_SIZE) + if (tny_header_get_message_size (cur) < helper->search->minsize) goto go_next; - if (search->flags & MODEST_SEARCH_SUBJECT) { - const char *str = tny_header_get_subject (cur); + if (helper->search->flags & MODEST_SEARCH_SUBJECT) { + char *str = tny_header_dup_subject (cur); - if ((found = search_string (search->subject, str, search))) { - retval = add_hit (retval, cur, folder); + if ((found = search_string (helper->search->subject, str, helper->search))) { + helper->msg_hits = add_hit (helper->msg_hits, cur, folder); } + g_free (str); } - if (!found && search->flags & MODEST_SEARCH_SENDER) { - char *str = g_strdup (tny_header_get_from (cur)); + if (!found && helper->search->flags & MODEST_SEARCH_SENDER) { + char *str = tny_header_dup_from (cur); - if ((found = search_string (search->from, (const gchar *) str, search))) { - retval = add_hit (retval, cur, folder); + if ((found = search_string (helper->search->from, (const gchar *) str, helper->search))) { + helper->msg_hits = add_hit (helper->msg_hits, cur, folder); } g_free (str); } - if (!found && search->flags & MODEST_SEARCH_RECIPIENT) { - const char *str = tny_header_get_to (cur); + if (!found && helper->search->flags & MODEST_SEARCH_RECIPIENT) { + char *str = tny_header_dup_to (cur); - if ((found = search_string (search->recipient, str, search))) { - retval = add_hit (retval, cur, folder); + if ((found = search_string (helper->search->recipient, str, helper->search))) { + helper->msg_hits = add_hit (helper->msg_hits, cur, folder); } + g_free (str); } - if (!found && search->flags & MODEST_SEARCH_BODY) { + if (!found && helper->search->flags & MODEST_SEARCH_BODY) { TnyHeaderFlags flags; GError *err = NULL; TnyMsg *msg = NULL; - TnyIterator *piter; - TnyList *parts; flags = tny_header_get_flags (cur); @@ -418,110 +480,209 @@ modest_search_folder (TnyFolder *folder, ModestSearch *search) if (msg) { g_object_unref (msg); } - } - - parts = tny_simple_list_new (); - tny_mime_part_get_parts (TNY_MIME_PART (msg), parts); + } else { + gchar *str; + str = tny_header_dup_subject (cur); + g_debug ("Searching in %s\n", str); + g_free (str); - if (tny_list_get_length(parts) == 0) { - gchar *url_string = tny_msg_get_url_string (msg); - g_debug ("DEBUG: %s: tny_mime_part_get_parts(msg) returned an empty list for message url=%s", - __FUNCTION__, url_string); - g_free (url_string); - } - - piter = tny_list_create_iterator (parts); - while (!found && !tny_iterator_is_done (piter)) { - TnyMimePart *pcur = (TnyMimePart *) tny_iterator_get_current (piter); - - #ifdef MODEST_HAVE_OGS - found = search_mime_part_ogs (pcur, search); - #else - found = search_mime_part_strcmp (pcur, search); - #endif - + found = search_mime_part_and_child_parts (TNY_MIME_PART (msg), + helper->search); if (found) { - retval = add_hit (retval, cur, folder); + helper->msg_hits = add_hit (helper->msg_hits, cur, folder); } - - g_object_unref (pcur); - tny_iterator_next (piter); } - - g_object_unref (piter); - g_object_unref (parts); - g_object_unref (msg); - + + if (msg) + g_object_unref (msg); } - -go_next: + go_next: g_object_unref (cur); tny_iterator_next (iter); } + /* Frees */ g_object_unref (iter); - g_object_unref (list); - return retval; + end: + if (headers) + g_object_unref (headers); + + /* Check search finished */ + tny_list_remove (helper->all_folders, G_OBJECT (folder)); + if (tny_list_get_length (helper->all_folders) == 0) { + /* callback */ + helper->callback (helper->msg_hits, helper->user_data); + + /* free helper */ + g_object_unref (helper->all_folders); + g_list_free (helper->msg_hits); + g_slice_free (SearchHelper, helper); + } else { + search_next_folder (helper); + } } -GList * -modest_search_account (TnyAccount *account, ModestSearch *search) +static void +_search_folder (TnyFolder *folder, + SearchHelper *helper) { - TnyFolderStore *store; - TnyIterator *iter; - TnyList *folders; - GList *hits; - GError *error; + TnyList *list = NULL; - error = NULL; - hits = NULL; + g_debug ("%s: searching folder %s.", __FUNCTION__, tny_folder_get_name (folder)); + + /* Check that we should be searching this folder. */ + /* Note that we don't try to search sub-folders. + * Maybe we should, but that should be specified. */ + if (helper->search->folder && strlen (helper->search->folder)) { + if (!strcmp (helper->search->folder, "outbox")) { + if (modest_tny_folder_guess_folder_type (folder) != TNY_FOLDER_TYPE_OUTBOX) { + modest_search_folder_get_headers_cb (folder, TRUE, NULL, NULL, helper); + return; + } + } else if (strcmp (tny_folder_get_id (folder), helper->search->folder) != 0) { + modest_search_folder_get_headers_cb (folder, TRUE, NULL, NULL, helper); + return; + } + } + +#ifdef MODEST_HAVE_OGS + if (helper->search->flags & MODEST_SEARCH_USE_OGS) { + + if (helper->search->text_searcher == NULL && helper->search->query != NULL) { + OgsTextSearcher *text_searcher; - store = TNY_FOLDER_STORE (account); + text_searcher = ogs_text_searcher_new (FALSE); + ogs_text_searcher_parse_query (text_searcher, helper->search->query); + helper->search->text_searcher = text_searcher; + } + } +#endif + list = tny_simple_list_new (); + /* Get the headers */ + tny_folder_get_headers_async (folder, list, FALSE, + modest_search_folder_get_headers_cb, + NULL, helper); +} - folders = tny_simple_list_new (); - tny_folder_store_get_folders (store, folders, NULL, &error); - - if (error != NULL) { - g_object_unref (folders); - return NULL; +void +modest_search_folder (TnyFolder *folder, + ModestSearch *search, + ModestSearchCallback callback, + gpointer user_data) +{ + SearchHelper *helper; + + /* Create the helper */ + helper = create_helper (callback, search, user_data); + + /* Search */ + _search_folder (folder, helper); +} + +static void +modest_search_account_get_folders_cb (TnyFolderStore *self, + gboolean cancelled, + TnyList *folders, + GError *err, + gpointer user_data) +{ + TnyIterator *iter; + SearchHelper *helper; + + helper = (SearchHelper *) user_data; + + if (err || cancelled) { + goto end; } + /* IMPORTANT: We need to get the headers of the folders one by + one, because otherwise the get_headers_async calls are + often canceled. That's why we firstly retrieve all folders, + and then we search inside them one by one. sergio */ iter = tny_list_create_iterator (folders); while (!tny_iterator_is_done (iter)) { - TnyFolder *folder; - GList *res; - - folder = TNY_FOLDER (tny_iterator_get_current (iter)); - /* g_debug ("DEBUG: %s: searching folder %s.", - __FUNCTION__, tny_folder_get_name (folder)); */ - - res = modest_search_folder (folder, search); - - if (res != NULL) { - if (hits == NULL) { - hits = res; - } else { - hits = g_list_concat (hits, res); - } + TnyFolder *folder = NULL; + + /* Search into folder */ + folder = TNY_FOLDER (tny_iterator_get_current (iter)); + tny_list_append (helper->all_folders, G_OBJECT (folder)); + + /* Search into children. Could be a merge folder */ + if (TNY_IS_FOLDER_STORE (folder)) { + TnyList *children = tny_simple_list_new (); + helper->pending_calls++; + tny_folder_store_get_folders_async (TNY_FOLDER_STORE (folder), children, NULL, + modest_search_account_get_folders_cb, + NULL, helper); } g_object_unref (folder); tny_iterator_next (iter); } - g_object_unref (iter); - g_object_unref (folders); + end: + /* Remove the "account" reference */ + helper->pending_calls--; + + if (folders) + g_object_unref (folders); + + /* If there are not more folders, begin to search from the first one */ + if (helper->pending_calls == 0) { + TnyIterator *iter = tny_list_create_iterator (helper->all_folders); + TnyFolder *first = TNY_FOLDER (tny_iterator_get_current (iter)); + + _search_folder (first, helper); + + g_object_unref (first); + g_object_unref (iter); + } +} + +static void +_search_account (TnyAccount *account, + SearchHelper *helper) +{ + TnyList *folders = tny_simple_list_new (); + + g_debug ("%s: Searching account %s", __FUNCTION__, tny_account_get_name (account)); + + /* Add a "reference" to the folder total. This allows the code + not to finalize the helper if an account is fully refreshed + before we get the folders of the others */ + helper->pending_calls++; + + /* Get folders */ + tny_folder_store_get_folders_async (TNY_FOLDER_STORE (account), folders, NULL, + modest_search_account_get_folders_cb, + NULL, helper); +} + +void +modest_search_account (TnyAccount *account, + ModestSearch *search, + ModestSearchCallback callback, + gpointer user_data) +{ + SearchHelper *helper; + + /* Create the helper */ + helper = create_helper (callback, search, user_data); - return hits; + /* Search */ + _search_account (account, helper); } -GList * -modest_search_all_accounts (ModestSearch *search) +void +modest_search_all_accounts (ModestSearch *search, + ModestSearchCallback callback, + gpointer user_data) { ModestTnyAccountStore *astore; - TnyList *accounts; - TnyIterator *iter; - GList *hits; + TnyList *accounts; + TnyIterator *iter; + GList *hits; + SearchHelper *helper; hits = NULL; astore = modest_runtime_get_account_store (); @@ -531,34 +692,60 @@ modest_search_all_accounts (ModestSearch *search) accounts, TNY_ACCOUNT_STORE_STORE_ACCOUNTS); + /* Create the helper */ + helper = create_helper (callback, search, user_data); + + /* Search through all accounts */ iter = tny_list_create_iterator (accounts); while (!tny_iterator_is_done (iter)) { - TnyAccount *account; - GList *res; + TnyAccount *account = NULL; account = TNY_ACCOUNT (tny_iterator_get_current (iter)); - - /* g_debug ("DEBUG: %s: Searching account %s", - __FUNCTION__, tny_account_get_name (account)); */ - res = modest_search_account (account, search); - - if (res != NULL) { - - if (hits == NULL) { - hits = res; - } else { - hits = g_list_concat (hits, res); - } - } - + _search_account (account, helper); g_object_unref (account); + tny_iterator_next (iter); } - - g_object_unref (accounts); g_object_unref (iter); + g_object_unref (accounts); +} + +static SearchHelper * +create_helper (ModestSearchCallback callback, + ModestSearch *search, + gpointer user_data) +{ + SearchHelper *helper; + + helper = g_slice_new0 (SearchHelper); + helper->pending_calls = 0; + helper->search = search; + helper->callback = callback; + helper->user_data = user_data; + helper->msg_hits = NULL; + helper->all_folders = tny_simple_list_new (); - return hits; + return helper; } +void +modest_search_free (ModestSearch *search) +{ + if (search->folder) + g_free (search->folder); + if (search->subject) + g_free (search->subject); + if (search->from) + g_free (search->from); + if (search->recipient) + g_free (search->recipient); + if (search->body) + g_free (search->body); +#ifdef MODEST_HAVE_OGS + if (search->query) + g_free (search->query); + if (search->text_searcher) + ogs_text_searcher_free (search->text_searcher); +#endif +}