From: Colin Leroy Date: Fri, 21 Sep 2012 10:19:43 +0000 (+0000) Subject: 2012-09-21 [colin] 3.8.1cvs68 X-Git-Tag: REL_3_9_0~56 X-Git-Url: http://git.claws-mail.org/?p=claws.git;a=commitdiff_plain;h=5bf542dc47d97400d96b67a0a8535d1622819056 2012-09-21 [colin] 3.8.1cvs68 * src/Makefile.am * src/advsearch.c ** ADDED ** * src/advsearch.h ** ADDED ** * src/folder.c * src/folder.h * src/folderview.c * src/imap.c * src/main.c * src/matcher.c * src/matcher.h * src/matchertypes.h ** ADDED ** * src/mh.c * src/mimeview.c * src/news.c * src/proctypes.h * src/summary_search.c * src/summaryview.c * src/summaryview.h * src/gtk/quicksearch.c * src/gtk/quicksearch.h Separated GUI and logic for search Use same logic for quicksearch and folder search Patch by Sean Buckheister --- diff --git a/ChangeLog b/ChangeLog index aa8da2cfa..2c9f15c21 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2012-09-21 [colin] 3.8.1cvs68 + + * src/Makefile.am + * src/advsearch.c ** ADDED ** + * src/advsearch.h ** ADDED ** + * src/folder.c + * src/folder.h + * src/folderview.c + * src/imap.c + * src/main.c + * src/matcher.c + * src/matcher.h + * src/matchertypes.h ** ADDED ** + * src/mh.c + * src/mimeview.c + * src/news.c + * src/proctypes.h + * src/summary_search.c + * src/summaryview.c + * src/summaryview.h + * src/gtk/quicksearch.c + * src/gtk/quicksearch.h + Separated GUI and logic for search + Use same logic for quicksearch and folder search + Patch by Sean Buckheister + 2012-09-19 [mones] 3.8.1cvs67 * src/mainwindow.c diff --git a/PATCHSETS b/PATCHSETS index 60368afd0..f1a301db9 100644 --- a/PATCHSETS +++ b/PATCHSETS @@ -4442,3 +4442,4 @@ ( cvs diff -u -r 1.28.2.50 -r 1.28.2.51 src/addrindex.c; cvs diff -u -r 1.14.2.64 -r 1.14.2.65 src/editaddress.c; cvs diff -u -r 1.8.2.46 -r 1.8.2.47 src/editldap.c; cvs diff -u -r 1.5.2.32 -r 1.5.2.33 src/exporthtml.c; cvs diff -u -r 1.18.2.36 -r 1.18.2.37 src/jpilot.c; cvs diff -u -r 1.49.2.150 -r 1.49.2.151 src/procmime.c; cvs diff -u -r 1.1.2.43 -r 1.1.2.44 src/plugins/bogofilter/bogofilter_gtk.c; cvs diff -u -r 1.1.2.38 -r 1.1.2.39 src/plugins/pgpcore/prefs_gpg.c; cvs diff -u -r 1.1.2.12 -r 1.1.2.13 src/plugins/smime/smime.c; cvs diff -u -r 1.23.2.60 -r 1.23.2.61 src/plugins/spamassassin/spamassassin_gtk.c; ) > 3.8.1cvs65.patchset ( cvs diff -u -r 1.60.2.151 -r 1.60.2.152 src/addressbook.c; cvs diff -u -r 1.3.2.12 -r 1.3.2.13 src/addrselect.c; ) > 3.8.1cvs66.patchset ( cvs diff -u -r 1.274.2.352 -r 1.274.2.353 src/mainwindow.c; ) > 3.8.1cvs67.patchset +( cvs diff -u -r 1.155.2.102 -r 1.155.2.103 src/Makefile.am; diff -u /dev/null src/advsearch.c; diff -u /dev/null src/advsearch.h; cvs diff -u -r 1.213.2.211 -r 1.213.2.212 src/folder.c; cvs diff -u -r 1.87.2.67 -r 1.87.2.68 src/folder.h; cvs diff -u -r 1.207.2.229 -r 1.207.2.230 src/folderview.c; cvs diff -u -r 1.179.2.262 -r 1.179.2.263 src/imap.c; cvs diff -u -r 1.115.2.257 -r 1.115.2.258 src/main.c; cvs diff -u -r 1.75.2.75 -r 1.75.2.76 src/matcher.c; cvs diff -u -r 1.39.2.21 -r 1.39.2.22 src/matcher.h; diff -u /dev/null src/matchertypes.h; cvs diff -u -r 1.79.2.76 -r 1.79.2.77 src/mh.c; cvs diff -u -r 1.83.2.195 -r 1.83.2.196 src/mimeview.c; cvs diff -u -r 1.101.2.72 -r 1.101.2.73 src/news.c; cvs diff -u -r 1.1.2.1 -r 1.1.2.2 src/proctypes.h; cvs diff -u -r 1.15.2.70 -r 1.15.2.71 src/summary_search.c; cvs diff -u -r 1.395.2.453 -r 1.395.2.454 src/summaryview.c; cvs diff -u -r 1.68.2.61 -r 1.68.2.62 src/summaryview.h; cvs diff -u -r 1.1.2.111 -r 1.1.2.112 src/gtk/quicksearch.c; cvs diff -u -r 1.1.2.24 -r 1.1.2.25 src/gtk/quicksearch.h; ) > 3.8.1cvs68.patchset diff --git a/configure.ac b/configure.ac index 2c7800b5b..dae7f0aa9 100644 --- a/configure.ac +++ b/configure.ac @@ -12,7 +12,7 @@ MINOR_VERSION=8 MICRO_VERSION=1 INTERFACE_AGE=0 BINARY_AGE=0 -EXTRA_VERSION=67 +EXTRA_VERSION=68 EXTRA_RELEASE= EXTRA_GTK2_VERSION= diff --git a/src/Makefile.am b/src/Makefile.am index 12030c05e..bb162beaa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -126,6 +126,7 @@ claws_mail_SOURCES = \ addrgather.c \ addrharvest.c \ addritem.c \ + advsearch.c \ alertpanel.c \ autofaces.c \ codeconv.c \ @@ -238,6 +239,7 @@ claws_mailinclude_HEADERS = \ addressitem.h \ addrgather.h \ addrharvest.h \ + advsearch.h \ alertpanel.h \ autofaces.h \ codeconv.h \ @@ -267,6 +269,7 @@ claws_mailinclude_HEADERS = \ mainwindow.h \ manual.h \ matcher.h \ + matchertypes.h \ matcher_parser.h \ matcher_parser_lex.h \ matcher_parser_parse.h \ diff --git a/src/advsearch.c b/src/advsearch.c new file mode 100644 index 000000000..306afa74c --- /dev/null +++ b/src/advsearch.c @@ -0,0 +1,496 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +# include "claws-features.h" +#endif + +#include "advsearch.h" + +#include +#include + +#include "matcher.h" +#include "matcher_parser.h" +#include "utils.h" + +struct _AdvancedSearch { + struct { + AdvancedSearchType type; + gchar *matchstring; + } request; + + MatcherList *predicate; + gboolean is_fast; + gboolean search_aborted; + + struct { + gboolean (*cb)(gpointer data, guint at, guint matched, guint total); + gpointer data; + } on_progress_cb; + struct { + void (*cb)(gpointer data); + gpointer data; + } on_error_cb; +}; + +void advsearch_set_on_progress_cb(AdvancedSearch *search, gboolean (*cb)(gpointer, guint, guint, guint), gpointer data) +{ + search->on_progress_cb.cb = cb; + search->on_progress_cb.data = data; +} + +void advsearch_set_on_error_cb(AdvancedSearch* search, void (*cb)(gpointer data), gpointer data) +{ + search->on_error_cb.cb = cb; + search->on_error_cb.data = data; +} + +static void prepare_matcher(AdvancedSearch *search); +static gboolean search_impl(MsgInfoList **messages, AdvancedSearch* search, + FolderItem* folderItem, gboolean recursive); + +// -------------------------- + +AdvancedSearch* advsearch_new() +{ + AdvancedSearch *result; + + result = g_new0(AdvancedSearch, 1); + + return result; +} + +void advsearch_free(AdvancedSearch *search) +{ + if (search->predicate != NULL) + matcherlist_free(search->predicate); + + g_free(search->request.matchstring); + g_free(search); +} + +void advsearch_set(AdvancedSearch *search, AdvancedSearchType type, const gchar *matchstring) +{ + cm_return_if_fail(search != NULL); + + search->request.type = type; + + g_free(search->request.matchstring); + search->request.matchstring = g_strdup(matchstring); + + prepare_matcher(search); +} + +gboolean advsearch_is_fast(AdvancedSearch *search) +{ + cm_return_val_if_fail(search != NULL, FALSE); + + return search->is_fast; +} + +gboolean advsearch_has_proper_predicate(AdvancedSearch *search) +{ + cm_return_val_if_fail(search != NULL, FALSE); + + return search->predicate != NULL; +} + +gboolean advsearch_search_msgs_in_folders(AdvancedSearch* search, MsgInfoList **messages, + FolderItem* folderItem, gboolean recursive) +{ + if (search == NULL || search->predicate == NULL) + return FALSE; + + search->search_aborted = FALSE; + return search_impl(messages, search, folderItem, recursive); +} + +void advsearch_abort(AdvancedSearch *search) +{ + search->search_aborted = TRUE; +} + +gchar *advsearch_expand_search_string(const gchar *search_string) +{ + int i = 0; + gchar term_char, save_char; + gchar *cmd_start, *cmd_end; + GString *matcherstr; + gchar *returnstr = NULL; + gchar *copy_str; + gboolean casesens, dontmatch, regex; + /* list of allowed pattern abbreviations */ + struct { + gchar *abbreviated; /* abbreviation */ + gchar *command; /* actual matcher command */ + gint numparams; /* number of params for cmd */ + gboolean qualifier; /* do we append stringmatch operations */ + gboolean quotes; /* do we need quotes */ + } + cmds[] = { + { "a", "all", 0, FALSE, FALSE }, + { "ag", "age_greater", 1, FALSE, FALSE }, + { "al", "age_lower", 1, FALSE, FALSE }, + { "b", "body_part", 1, TRUE, TRUE }, + { "B", "message", 1, TRUE, TRUE }, + { "c", "cc", 1, TRUE, TRUE }, + { "C", "to_or_cc", 1, TRUE, TRUE }, + { "D", "deleted", 0, FALSE, FALSE }, + { "e", "header \"Sender\"", 1, TRUE, TRUE }, + { "E", "execute", 1, FALSE, TRUE }, + { "f", "from", 1, TRUE, TRUE }, + { "F", "forwarded", 0, FALSE, FALSE }, + { "h", "headers_part", 1, TRUE, TRUE }, + { "ha", "has_attachments", 0, FALSE, FALSE }, + { "i", "header \"Message-ID\"", 1, TRUE, TRUE }, + { "I", "inreplyto", 1, TRUE, TRUE }, + { "k", "colorlabel", 1, FALSE, FALSE }, + { "L", "locked", 0, FALSE, FALSE }, + { "n", "newsgroups", 1, TRUE, TRUE }, + { "N", "new", 0, FALSE, FALSE }, + { "O", "~new", 0, FALSE, FALSE }, + { "r", "replied", 0, FALSE, FALSE }, + { "R", "~unread", 0, FALSE, FALSE }, + { "s", "subject", 1, TRUE, TRUE }, + { "se", "score_equal", 1, FALSE, FALSE }, + { "sg", "score_greater", 1, FALSE, FALSE }, + { "sl", "score_lower", 1, FALSE, FALSE }, + { "Se", "size_equal", 1, FALSE, FALSE }, + { "Sg", "size_greater", 1, FALSE, FALSE }, + { "Ss", "size_smaller", 1, FALSE, FALSE }, + { "t", "to", 1, TRUE, TRUE }, + { "tg", "tag", 1, TRUE, TRUE }, + { "T", "marked", 0, FALSE, FALSE }, + { "U", "unread", 0, FALSE, FALSE }, + { "x", "header \"References\"", 1, TRUE, TRUE }, + { "X", "test", 1, FALSE, FALSE }, + { "y", "header \"X-Label\"", 1, TRUE, TRUE }, + { "&", "&", 0, FALSE, FALSE }, + { "|", "|", 0, FALSE, FALSE }, + { "p", "partial", 0, FALSE, FALSE }, + { NULL, NULL, 0, FALSE, FALSE } + }; + + if (search_string == NULL) + return NULL; + + copy_str = g_strdup(search_string); + + matcherstr = g_string_sized_new(16); + cmd_start = copy_str; + while (cmd_start && *cmd_start) { + /* skip all white spaces */ + while (*cmd_start && isspace((guchar)*cmd_start)) + cmd_start++; + cmd_end = cmd_start; + + /* extract a command */ + while (*cmd_end && !isspace((guchar)*cmd_end)) + cmd_end++; + + /* save character */ + save_char = *cmd_end; + *cmd_end = '\0'; + + dontmatch = FALSE; + casesens = FALSE; + regex = FALSE; + + /* ~ and ! mean logical NOT */ + if (*cmd_start == '~' || *cmd_start == '!') + { + dontmatch = TRUE; + cmd_start++; + } + /* % means case sensitive match */ + if (*cmd_start == '%') + { + casesens = TRUE; + cmd_start++; + } + /* # means regex match */ + if (*cmd_start == '#') { + regex = TRUE; + cmd_start++; + } + + /* find matching abbreviation */ + for (i = 0; cmds[i].command; i++) { + if (!strcmp(cmd_start, cmds[i].abbreviated)) { + /* restore character */ + *cmd_end = save_char; + + /* copy command */ + if (matcherstr->len > 0) { + g_string_append(matcherstr, " "); + } + if (dontmatch) + g_string_append(matcherstr, "~"); + g_string_append(matcherstr, cmds[i].command); + g_string_append(matcherstr, " "); + + /* stop if no params required */ + if (cmds[i].numparams == 0) + break; + + /* extract a parameter, allow quotes */ + while (*cmd_end && isspace((guchar)*cmd_end)) + cmd_end++; + + cmd_start = cmd_end; + if (*cmd_start == '"') { + term_char = '"'; + cmd_end++; + } + else + term_char = ' '; + + /* extract actual parameter */ + while ((*cmd_end) && (*cmd_end != term_char)) + cmd_end++; + + if (*cmd_end == '"') + cmd_end++; + + save_char = *cmd_end; + *cmd_end = '\0'; + + if (cmds[i].qualifier) { + if (casesens) + g_string_append(matcherstr, regex ? "regexp " : "match "); + else + g_string_append(matcherstr, regex ? "regexpcase " : "matchcase "); + } + + /* do we need to add quotes ? */ + if (cmds[i].quotes && term_char != '"') + g_string_append(matcherstr, "\""); + + /* copy actual parameter */ + g_string_append(matcherstr, cmd_start); + + /* do we need to add quotes ? */ + if (cmds[i].quotes && term_char != '"') + g_string_append(matcherstr, "\""); + + /* restore original character */ + *cmd_end = save_char; + + break; + } + } + + if (*cmd_end) + cmd_end++; + cmd_start = cmd_end; + } + + g_free(copy_str); + + /* return search string if no match is found to allow + all available filtering expressions in advanced search */ + if (matcherstr->len > 0) returnstr = matcherstr->str; + else returnstr = g_strdup(search_string); + g_string_free(matcherstr, FALSE); + return returnstr; +} + +// -------------------------- + +static gchar *expand_tag_search_string(const gchar *search_string) +{ + gchar *newstr = NULL; + gchar **words = search_string ? g_strsplit(search_string, " ", -1):NULL; + gint i = 0; + while (words && words[i] && *words[i]) { + g_strstrip(words[i]); + if (!newstr) { + newstr = g_strdup_printf("tag matchcase \"%s\"", words[i]); + } else { + gint o_len = strlen(newstr); + gint s_len = 17; /* strlen("|tag matchcase \"\"") */ + gint n_len = s_len + strlen(words[i]); + newstr = g_realloc(newstr, o_len + n_len + 1); + strcpy(newstr + o_len, "|tag matchcase \""); + strcpy(newstr + o_len + (s_len - 1), words[i]); + strcpy(newstr + o_len + (n_len - 1), "\""); + } + i++; + } + g_strfreev(words); + return newstr; +} + +static void prepare_matcher_extended(AdvancedSearch *search) +{ + gchar *newstr = advsearch_expand_search_string(search->request.matchstring); + + if (newstr && newstr[0] != '\0') { + search->predicate = matcher_parser_get_cond(newstr, &search->is_fast); + g_free(newstr); + } +} + +static void prepare_matcher_tag(AdvancedSearch *search) +{ + char *newstr = expand_tag_search_string(search->request.matchstring); + search->predicate = matcher_parser_get_cond(newstr, &search->is_fast); + g_free(newstr); +} + +static void prepare_matcher_header(AdvancedSearch *search, gint match_header) +{ + MatcherProp *matcher; + + if (search->predicate == NULL) + search->predicate = g_new0(MatcherList, 1); + + matcher = matcherprop_new(match_header, NULL, MATCHTYPE_MATCHCASE, + search->request.matchstring, 0); + + search->predicate->matchers = g_slist_prepend(search->predicate->matchers, matcher); +} + +static void prepare_matcher_mixed(AdvancedSearch *search) +{ + prepare_matcher_tag(search); + + prepare_matcher_header(search, MATCHCRITERIA_SUBJECT); + prepare_matcher_header(search, MATCHCRITERIA_FROM); + prepare_matcher_header(search, MATCHCRITERIA_TO); + prepare_matcher_header(search, MATCHCRITERIA_TAG); +} + +static void prepare_matcher(AdvancedSearch *search) +{ + const gchar *search_string; + + cm_return_if_fail(search != NULL); + + if (search->predicate) { + matcherlist_free(search->predicate); + search->predicate = NULL; + } + + search_string = search->request.matchstring; + + if (search_string == NULL || search_string[0] == '\0') + return; + + switch (search->request.type) { + case ADVANCED_SEARCH_SUBJECT: + prepare_matcher_header(search, MATCHCRITERIA_SUBJECT); + break; + + case ADVANCED_SEARCH_FROM: + prepare_matcher_header(search, MATCHCRITERIA_FROM); + break; + + case ADVANCED_SEARCH_TO: + prepare_matcher_header(search, MATCHCRITERIA_TO); + break; + + case ADVANCED_SEARCH_TAG: + prepare_matcher_header(search, MATCHCRITERIA_TAG); + break; + + case ADVANCED_SEARCH_MIXED: + prepare_matcher_mixed(search); + break; + + case ADVANCED_SEARCH_EXTENDED: + prepare_matcher_extended(search); + break; + + default: + debug_print("unknown search type (%d)\n", search->request.type); + break; + } +} + +static gboolean search_progress_notify_cb(gpointer data, gboolean on_server, guint at, + guint matched, guint total) +{ + AdvancedSearch *search = (AdvancedSearch*) data; + + if (search->search_aborted) + return FALSE; + + if (on_server || search->on_progress_cb.cb == NULL) + return TRUE; + + return search->on_progress_cb.cb(search->on_progress_cb.data, at, matched, total); +} + +static gboolean search_filter_folder(MsgNumberList **msgnums, AdvancedSearch *search, + FolderItem *folderItem, gboolean onServer) +{ + gint matched; + + matched = folder_item_search_msgs(folderItem->folder, + folderItem, + msgnums, + &onServer, + search->predicate, + search_progress_notify_cb, + search); + + if (matched < 0) { + if (search->on_error_cb.cb != NULL) + search->on_error_cb.cb(search->on_error_cb.data); + return FALSE; + } + + if (folderItem->folder->klass->supports_server_search && !onServer) { + return search_filter_folder(msgnums, search, folderItem, onServer); + } else { + return TRUE; + } +} + +static gboolean search_impl(MsgInfoList **messages, AdvancedSearch* search, + FolderItem* folderItem, gboolean recursive) +{ + if (recursive) { + if (!search_impl(messages, search, folderItem, FALSE)) + return FALSE; + + if (folderItem->node->children != NULL && !search->search_aborted) { + GNode *node; + for (node = folderItem->node->children; node != NULL; node = node->next) { + FolderItem *cur = FOLDER_ITEM(node->data); + debug_print("in: %s\n", cur->path); + if (!search_impl(messages, search, cur, TRUE)) + return FALSE; + } + } + } else { + MsgNumberList *msgnums = NULL; + MsgNumberList *cur; + MsgInfoList *msgs = NULL; + + if (!search_filter_folder(&msgnums, search, folderItem, + folderItem->folder->klass->supports_server_search)) { + g_slist_free(msgnums); + return FALSE; + } + + for (cur = msgnums; cur != NULL; cur = cur->next) { + MsgInfo *msg = folder_item_get_msginfo(folderItem, GPOINTER_TO_UINT(cur->data)); + + msgs = g_slist_prepend(msgs, msg); + } + + while (msgs != NULL) { + MsgInfoList *front = msgs; + + msgs = msgs->next; + + front->next = *messages; + *messages = front; + } + + g_slist_free(msgnums); + } + + return TRUE; +} diff --git a/src/advsearch.h b/src/advsearch.h new file mode 100644 index 000000000..bb6803234 --- /dev/null +++ b/src/advsearch.h @@ -0,0 +1,60 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2012 the Claws Mail team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef ADVSEARCH_H +#define ADVSEARCH_H 1 + +#include "proctypes.h" +#include "folder.h" + +// temporary +#include "matcher.h" + +typedef enum +{ + ADVANCED_SEARCH_SUBJECT, + ADVANCED_SEARCH_FROM, + ADVANCED_SEARCH_TO, + ADVANCED_SEARCH_EXTENDED, + ADVANCED_SEARCH_MIXED, + ADVANCED_SEARCH_TAG, +} AdvancedSearchType; + + +typedef struct _AdvancedSearch AdvancedSearch; + +void advsearch_set_on_progress_cb(AdvancedSearch* search, + gboolean (*cb)(gpointer data, guint at, guint matched, guint total), gpointer data); +void advsearch_set_on_error_cb(AdvancedSearch* search, void (*cb)(gpointer data), gpointer data); + +AdvancedSearch *advsearch_new(); +void advsearch_free(AdvancedSearch *search); + +void advsearch_set(AdvancedSearch *search, AdvancedSearchType type, const gchar *matchstring); +gboolean advsearch_is_fast(AdvancedSearch *search); +gboolean advsearch_has_proper_predicate(AdvancedSearch *search); + +gboolean advsearch_search_msgs_in_folders(AdvancedSearch* search, MsgInfoList **messages, + FolderItem* folderItem, gboolean recursive); + +void advsearch_abort(AdvancedSearch *search); + +gchar *advsearch_expand_search_string(const gchar *search_string); + +#endif diff --git a/src/folder.c b/src/folder.c index c596f9f0b..e454d4962 100644 --- a/src/folder.c +++ b/src/folder.c @@ -4689,6 +4689,22 @@ gboolean folder_has_parent_of_type(FolderItem *item, return FALSE; } +gboolean folder_is_child_of(FolderItem *item, FolderItem *parent) +{ + if (item == NULL || parent == NULL) + return FALSE; + + while (item != NULL) { + if (parent == item) + return TRUE; + + item = folder_item_parent(item); + } + + return FALSE; +} + + gboolean folder_subscribe (const gchar *uri) { GList *cur; @@ -4715,3 +4731,74 @@ gboolean folder_get_sort_type (Folder *folder, folder->klass->get_sort_type(folder, sort_key, sort_type); return TRUE; } + +gint folder_item_search_msgs (Folder *folder, + FolderItem *container, + MsgNumberList **msgs, + gboolean *on_server, + MatcherList *predicate, + SearchProgressNotify progress_cb, + gpointer progress_data) +{ + if (folder->klass->search_msgs) { + return folder->klass->search_msgs(folder, container, + msgs, on_server, predicate, progress_cb, progress_data); + } else { + return folder_item_search_msgs_local(folder, container, + msgs, on_server, predicate, progress_cb, progress_data); + } +} + +gint folder_item_search_msgs_local (Folder *folder, + FolderItem *container, + MsgNumberList **msgs, + gboolean *on_server, + MatcherList *predicate, + SearchProgressNotify progress_cb, + gpointer progress_data) +{ + GSList *result = NULL; + GSList *cur = NULL; + gint matched_count = 0; + guint processed_count = 0; + gint msgcount; + GSList *nums = NULL; + + if (*msgs == NULL) { + gboolean old_valid = TRUE; + + msgcount = folder->klass->get_num_list(folder, container, &nums, &old_valid); + + if (msgcount < 0) + return -1; + } else { + nums = *msgs; + } + + for (cur = nums; cur != NULL; cur = cur->next) { + guint msgnum = GPOINTER_TO_UINT(cur->data); + MsgInfo *msg = folder_item_get_msginfo(container, msgnum); + + if (msg == NULL) { + g_slist_free(result); + return -1; + } + + if (matcherlist_match(predicate, msg)) { + result = g_slist_prepend(result, GUINT_TO_POINTER(msg->msgnum)); + matched_count++; + } + processed_count++; + + if (progress_cb != NULL + && !progress_cb(progress_data, FALSE, processed_count, + matched_count, msgcount)) + break; + } + + g_slist_free(nums); + *msgs = g_slist_reverse(result); + + return matched_count; +} + diff --git a/src/folder.h b/src/folder.h index e76a4648f..e8b5c5287 100644 --- a/src/folder.h +++ b/src/folder.h @@ -132,6 +132,7 @@ typedef void (*FolderItemFunc) (FolderItem *item, #include "proctypes.h" #include "xml.h" #include "prefs_account.h" +#include "matchertypes.h" struct _MsgCache; @@ -159,6 +160,23 @@ struct _Folder GHashTable *newsart; }; +/** + * Callback used to convey progress information of a specific search. + * + * \param data User-provided data + * \param on_server Whether or not the current progress information originated from the + * server + * \param at Number of the last message processed + * \param matched Number of messages with definitive matches found so far + * \param total Number of messages to be processed + * + * \note + * Even if the mailserver does not support progress reports, an instance of this type + * should be invoked when serverside search starts and ends, with \c at set to \c 0 and + * \c total, respectively. + */ +typedef gboolean (*SearchProgressNotify)(gpointer data, gboolean on_server, guint at, guint matched, guint total); + struct _FolderClass { /** @@ -175,6 +193,13 @@ struct _FolderClass * user. Can be upper and lowercase unlike the idstr. */ gchar *uistr; + /** + * A boolean to indicate whether or not the FolderClass supports search on the + * server. If \c TRUE, setting \c on_server in \c search_msgs offloads search to + * the server. + */ + gboolean supports_server_search; + /* virtual functions */ @@ -505,6 +530,58 @@ struct _FolderClass FolderItem *dest, MsgInfoList *msglist, GHashTable *relation); + + /** + * Search the given FolderItem for messages matching \c predicate. + * The search may be offloaded to the server if the \c folder + * supports server side search, as indicated by \c supports_server_search. + * + * \param folder The \c Folder of the container FolderItem + * \param container The \c FolderItem containing the messages to be searched + * \param msgs The \c MsgNumberList results will be saved to. + * If *msgs != NULL, the search will be restricted to + * messages whose numbers are contained therein. + * If \c on_server is considered \c FALSE, messages are guaranteed to + * be processed in the order they are listed in \c msgs. + * On error, \c msgs will not be changed. + * \param on_server Whether or not the search should be offloaded to the server. + * If \c on_server is not \c NULL and points to a \c TRUE value, + * search will be done on the server. If \c predicate contains + * one or more atoms the server does not support, the value + * pointed to by \c on_server will be set to \c FALSE upon return. + * In this case, \c msgs must still contain a valid superset of + * messages actually matched by \c predicate, or this method must + * return an error. + * \c on_server may only point to a \c TRUE value if + * \c supports_server_search is also \c TRUE. + * \c NULL and pointer to \c FALSE are considered equivalent and + * will start a client-only search. + * \param predicate The \c MatcherList to use in the search + * \param progress_cb Called for every message searched. + * When search is offloaded to the server, this function + * may or may not be called, depending on the implementation. + * The second argument of this function will be the number of + * messages already processed. + * Return \c FALSE from this function to end the search. + * May be \c NULL, no calls will be made in this case. + * \param progress_data First argument value for \c progress_cb + * \return Number of messages that matched \c predicate on success, a negative + * number otherwise. + * + * \note + * When search is stopped by returning \c FALSE from \c progress_cb, \c msgs will + * contain all messages found until the point of cancellation. The number of + * messages found will be returned as indicated above. + */ + gint (*search_msgs) (Folder *folder, + FolderItem *container, + MsgNumberList **msgs, + gboolean *on_server, + MatcherList *predicate, + SearchProgressNotify progress_cb, + gpointer progress_data); + + /** * Remove a message from a \c FolderItem. * @@ -861,6 +938,13 @@ gint folder_item_copy_msg (FolderItem *dest, MsgInfo *msginfo); gint folder_item_copy_msgs (FolderItem *dest, GSList *msglist); +gint folder_item_search_msgs (Folder *folder, + FolderItem *container, + MsgNumberList **msgs, + gboolean *on_server, + MatcherList *predicate, + SearchProgressNotify progress_cb, + gpointer progress_data); gint folder_item_remove_msg (FolderItem *item, gint num); gint folder_item_remove_msgs (FolderItem *item, @@ -887,6 +971,7 @@ void folder_item_update_freeze (void); void folder_item_update_thaw (void); void folder_item_set_batch (FolderItem *item, gboolean batch); gboolean folder_has_parent_of_type (FolderItem *item, SpecialFolderItemType type); +gboolean folder_is_child_of (FolderItem *item, FolderItem *possibleChild); void folder_synchronise (Folder *folder); gboolean folder_want_synchronise (Folder *folder); gboolean folder_subscribe (const gchar *uri); @@ -901,4 +986,14 @@ void folder_item_synchronise (FolderItem *item); void folder_item_discard_cache (FolderItem *item); void folder_item_commit_tags(FolderItem *item, MsgInfo *msginfo, GSList *tags_set, GSList *tags_unset); + + +gint folder_item_search_msgs_local (Folder *folder, + FolderItem *container, + MsgNumberList **msgs, + gboolean *on_server, + MatcherList *predicate, + SearchProgressNotify progress_cb, + gpointer progress_data); + #endif /* __FOLDER_H__ */ diff --git a/src/folderview.c b/src/folderview.c index a0a3536e6..83b01e3d4 100644 --- a/src/folderview.c +++ b/src/folderview.c @@ -1713,7 +1713,7 @@ static gboolean folderview_update_item_claws(gpointer source, gpointer data) if ((update_info->update_flags & F_ITEM_UPDATE_CONTENT) && update_info->item == folderview->summaryview->folder_item && update_info->item != NULL) - if (!quicksearch_is_active(folderview->summaryview->quicksearch)) + if (!quicksearch_has_sat_predicate(folderview->summaryview->quicksearch)) summary_show(folderview->summaryview, update_info->item); } diff --git a/src/gtk/quicksearch.c b/src/gtk/quicksearch.c index e8e57be2b..0756eb09b 100644 --- a/src/gtk/quicksearch.c +++ b/src/gtk/quicksearch.c @@ -46,12 +46,13 @@ #include "prefs_matcher.h" #include "claws.h" #include "statusbar.h" +#include "advsearch.h" +#include "alertpanel.h" struct _QuickSearchRequest { - QuickSearchType type; + AdvancedSearchType type; gchar *matchstring; - gboolean recursive; }; typedef struct _QuickSearchRequest QuickSearchRequest; @@ -69,17 +70,12 @@ struct _QuickSearch gboolean active; gchar *search_string; - MatcherList *matcher_list; - QuickSearchRequest *request; + QuickSearchRequest request; QuickSearchExecuteCallback callback; gpointer callback_data; gboolean running; gboolean has_focus; - gboolean matching; - gboolean deferred_free; - FolderItem *root_folder_item; - gboolean is_fast; gboolean in_typing; guint press_timeout_id; @@ -90,38 +86,67 @@ struct _QuickSearch GtkWidget *dynamic_menuitem; GtkWidget *autorun_menuitem; - gboolean gui; + AdvancedSearch *asearch; + gboolean want_reexec; }; +void quicksearch_set_on_progress_cb(QuickSearch* search, + gboolean (*cb)(gpointer data, guint at, guint matched, guint total), gpointer data) +{ + advsearch_set_on_progress_cb(search->asearch, cb, data); +} + static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run); static void quicksearch_set_matchstring(QuickSearch *quicksearch, const gchar *matchstring); static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active); -static void quicksearch_reset_folder_items(QuickSearch *quicksearch, FolderItem *folder_item); -static gchar *expand_search_string(const gchar *str); -static gchar *expand_tag_search_string(const gchar *str); -static gboolean quicksearch_from_gui(QuickSearch *quicksearch) +static void quicksearch_invoke_execute(QuickSearch *quicksearch, gboolean run_only_if_fast) { - return quicksearch->gui; + if (quicksearch->running) { + quicksearch->want_reexec = TRUE; + advsearch_abort(quicksearch->asearch); + return; + } + + do { + advsearch_set(quicksearch->asearch, quicksearch->request.type, + quicksearch->request.matchstring); + + if (run_only_if_fast && !advsearch_is_fast(quicksearch->asearch)) + return; + + quicksearch_set_active(quicksearch, advsearch_has_proper_predicate(quicksearch->asearch)); + + quicksearch->want_reexec = FALSE; + quicksearch_set_running(quicksearch, TRUE); + if (quicksearch->callback != NULL) + quicksearch->callback(quicksearch, quicksearch->callback_data); + quicksearch_set_running(quicksearch, FALSE); + } while (quicksearch->want_reexec); } -gboolean quicksearch_is_fast(QuickSearch *quicksearch) +gboolean quicksearch_run_on_folder(QuickSearch* quicksearch, FolderItem *folderItem, MsgInfoList **result) { - return quicksearch->is_fast; + if (quicksearch_has_sat_predicate(quicksearch)) { + gboolean searchres = advsearch_search_msgs_in_folders(quicksearch->asearch, result, folderItem, FALSE); + if (quicksearch->want_reexec) { + advsearch_set(quicksearch->asearch, quicksearch->request.type, ""); + } + return searchres; + } else + return FALSE; } -void quicksearch_set_recursive(QuickSearch *quicksearch, gboolean recursive) +gboolean quicksearch_is_fast(QuickSearch *quicksearch) { - quicksearch->request->recursive = recursive; + return advsearch_is_fast(quicksearch->asearch); } static void quicksearch_set_type(QuickSearch *quicksearch, gint type) { #if !GTK_CHECK_VERSION(3, 0, 0) gint index; - quicksearch->request->type = type; - if (quicksearch->gui == FALSE) - return; + quicksearch->request.type = type; index = menu_find_option_menu_index(GTK_CMOPTION_MENU(quicksearch->search_type_opt), GINT_TO_POINTER(type), NULL); @@ -143,7 +168,7 @@ static void quicksearch_set_popdown_strings(QuickSearch *quicksearch) combobox_unset_popdown_strings(GTK_COMBO_BOX(search_string_entry)); - if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) + if (prefs_common.summary_quicksearch_type == ADVANCED_SEARCH_EXTENDED) combobox_set_popdown_strings(GTK_COMBO_BOX(search_string_entry), quicksearch->extended_search_strings); else @@ -151,72 +176,6 @@ static void quicksearch_set_popdown_strings(QuickSearch *quicksearch) quicksearch->normal_search_strings); } -static void prepare_matcher(QuickSearch *quicksearch) -{ - /* param search_string is "matchstring" */ - const gchar *search_string; - QuickSearchType quicksearch_type; - - if (quicksearch == NULL) - return; - - /* When called from the GUI, reset type and matchstring */ - if (quicksearch_from_gui(quicksearch)) { - gchar *s = quicksearch_get_text(quicksearch); - quicksearch_set_matchstring(quicksearch, s); - g_free(s); - quicksearch->request->type = prefs_common.summary_quicksearch_type; - } - quicksearch_type = quicksearch->request->type; - search_string = quicksearch->request->matchstring; - - if (search_string == NULL || search_string[0] == '\0') { - quicksearch_set_active(quicksearch, FALSE); - } - - if (quicksearch->matcher_list != NULL) { - if (quicksearch->matching) { - quicksearch->deferred_free = TRUE; - return; - } - quicksearch->deferred_free = FALSE; - matcherlist_free(quicksearch->matcher_list); - quicksearch->matcher_list = NULL; - } - - if (search_string == NULL || search_string[0] == '\0') { - return; - } - if (quicksearch_type == QUICK_SEARCH_EXTENDED) { - char *newstr = NULL; - - newstr = expand_search_string(search_string); - if (newstr && newstr[0] != '\0') { - quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast); - g_free(newstr); - } else { - quicksearch->matcher_list = NULL; - quicksearch_set_active(quicksearch, FALSE); - return; - } - } else if (quicksearch_type == QUICK_SEARCH_TAG) { - char *newstr = expand_tag_search_string(search_string); - quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast); - g_free(newstr); - } else if (quicksearch_type == QUICK_SEARCH_MIXED) { - char *newstr = expand_tag_search_string(search_string); - quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast); - g_free(newstr); - g_free(quicksearch->search_string); - quicksearch->search_string = g_utf8_casefold(search_string, -1); - } else { - quicksearch->is_fast = TRUE; - g_free(quicksearch->search_string); - quicksearch->search_string = g_utf8_casefold(search_string, -1); - } - quicksearch_set_active(quicksearch, TRUE); -} - static void update_extended_buttons (QuickSearch *quicksearch) { GtkWidget *expr_btn = quicksearch->search_condition_expression; @@ -225,7 +184,7 @@ static void update_extended_buttons (QuickSearch *quicksearch) cm_return_if_fail(expr_btn != NULL); cm_return_if_fail(ext_btn != NULL); - if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) { + if (prefs_common.summary_quicksearch_type == ADVANCED_SEARCH_EXTENDED) { gtk_widget_show(expr_btn); gtk_widget_show(ext_btn); } else { @@ -258,13 +217,12 @@ static void searchbar_run(QuickSearch *quicksearch, gboolean run_only_if_fast) { gchar *search_string = quicksearch_get_text(quicksearch); quicksearch_set_matchstring(quicksearch, search_string); - prepare_matcher(quicksearch); /* add to history, for extended search add only correct matching rules */ if (!quicksearch->in_typing && search_string && strlen(search_string) != 0) { switch (prefs_common.summary_quicksearch_type) { - case QUICK_SEARCH_EXTENDED: - if (quicksearch->matcher_list) { + case ADVANCED_SEARCH_EXTENDED: + if (advsearch_has_proper_predicate(quicksearch->asearch)) { quicksearch->extended_search_strings = add_history(quicksearch->extended_search_strings, g_strdup(search_string)); @@ -287,21 +245,9 @@ static void searchbar_run(QuickSearch *quicksearch, gboolean run_only_if_fast) } - if (run_only_if_fast && !quicksearch->is_fast) { - g_free(search_string); - return; - } - if (quicksearch->matcher_list == NULL && - prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED && - search_string && strlen(search_string) != 0) { - g_free(search_string); - return; - } - quicksearch_set_running(quicksearch, TRUE); - if (quicksearch->callback != NULL) - quicksearch->callback(quicksearch, quicksearch->callback_data); - quicksearch_set_running(quicksearch, FALSE); g_free(search_string); + + quicksearch_invoke_execute(quicksearch, run_only_if_fast); } static int searchbar_changed_timeout(void *data) @@ -385,29 +331,18 @@ static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event, static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data) { QuickSearch *quicksearch = (QuickSearch *)data; - gchar *search_string = quicksearch_get_text(quicksearch); - quicksearch_set_matchstring(quicksearch, search_string); prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data( G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active( GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID)); + quicksearch->request.type = prefs_common.summary_quicksearch_type; /* Show extended search description button, only when Extended is selected */ update_extended_buttons(quicksearch); quicksearch_set_popdown_strings(quicksearch); - if (!search_string || *(search_string) == 0) { - g_free(search_string); - return TRUE; - } - - prepare_matcher(quicksearch); + quicksearch_invoke_execute(quicksearch, FALSE); - quicksearch_set_running(quicksearch, TRUE); - if (quicksearch->callback != NULL) - quicksearch->callback(quicksearch, quicksearch->callback_data); - quicksearch_set_running(quicksearch, FALSE); - g_free(search_string); return TRUE; } @@ -415,28 +350,14 @@ static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data) { QuickSearch *quicksearch = (QuickSearch *)data; gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); - gchar *search_string = quicksearch_get_text(quicksearch); - /* not needed to quicksearch_set_matchstring(search_string); - wait for prepare_matcher() */ prefs_common.summary_quicksearch_recurse = checked; - quicksearch_set_recursive(quicksearch, checked); /* reselect the search type */ quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type); - if (!search_string || *(search_string) == 0) { - g_free(search_string); - return TRUE; - } - - prepare_matcher(quicksearch); + quicksearch_invoke_execute(quicksearch, FALSE); - quicksearch_set_running(quicksearch, TRUE); - if (quicksearch->callback != NULL) - quicksearch->callback(quicksearch, quicksearch->callback_data); - quicksearch_set_running(quicksearch, FALSE); - g_free(search_string); return TRUE; } @@ -537,6 +458,7 @@ static gchar *search_descr_strings[] = { "|", N_("logical OR operator"), "! or ~", N_("logical NOT operator"), "%", N_("case sensitive search"), + "#", N_("match using regular expressions instead of substring search"), "", "" , " ", N_("all filtering expressions are allowed"), NULL, NULL @@ -645,41 +567,9 @@ static void quicksearch_set_button(GtkButton *button, const gchar *icon, const g gtk_widget_show_all(box); } -/* - * Builds a new QuickSearchRequest - */ -static QuickSearchRequest *quicksearchrequest_new(void) +static void quicksearch_error(gpointer data) { - QuickSearchRequest *request; - request = g_new0(QuickSearchRequest, 1); - return request; -} - -/* - * Builds a new QuickSearch object independent from the GUI - */ -QuickSearch *quicksearch_new_nogui(void) -{ - QuickSearch *quicksearch; - QuickSearchRequest *request; - - request = quicksearchrequest_new(); - quicksearch = g_new0(QuickSearch, 1); - quicksearch->request = request; - quicksearch->gui = FALSE; - - /* init. values initally found in quicksearch_new(). - There's no need to init. all pointers to NULL since we use g_new0 - */ - quicksearch->matcher_list = NULL; - quicksearch->active = FALSE; - quicksearch->running = FALSE; - quicksearch->in_typing = FALSE; - quicksearch->press_timeout_id = -1; - quicksearch->normal_search_strings = NULL; - quicksearch->extended_search_strings = NULL; - - return quicksearch; + alertpanel_error(_("Something went wrong during search. Please check you logs.")); } QuickSearch *quicksearch_new() @@ -699,8 +589,20 @@ QuickSearch *quicksearch_new() GtkWidget *menuitem; GtkWidget *vbox; - quicksearch = quicksearch_new_nogui(); - quicksearch->gui = TRUE; + quicksearch = g_new0(QuickSearch, 1); + + quicksearch->asearch = advsearch_new(); + advsearch_set_on_error_cb(quicksearch->asearch, quicksearch_error, NULL); + + /* init. values initally found in quicksearch_new(). + There's no need to init. all pointers to NULL since we use g_new0 + */ + quicksearch->active = FALSE; + quicksearch->running = FALSE; + quicksearch->in_typing = FALSE; + quicksearch->press_timeout_id = -1; + quicksearch->normal_search_strings = NULL; + quicksearch->extended_search_strings = NULL; /* quick search */ hbox_search = gtk_hbox_new(FALSE, 0); @@ -713,31 +615,31 @@ QuickSearch *quicksearch_new() search_type = gtk_menu_new(); MENUITEM_ADD (search_type, menuitem, - prefs_common_translated_header_name("Subject"), QUICK_SEARCH_SUBJECT); + prefs_common_translated_header_name("Subject"), ADVANCED_SEARCH_SUBJECT); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(searchtype_changed), quicksearch); MENUITEM_ADD (search_type, menuitem, - prefs_common_translated_header_name("From"), QUICK_SEARCH_FROM); + prefs_common_translated_header_name("From"), ADVANCED_SEARCH_FROM); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(searchtype_changed), quicksearch); MENUITEM_ADD (search_type, menuitem, - prefs_common_translated_header_name("To"), QUICK_SEARCH_TO); + prefs_common_translated_header_name("To"), ADVANCED_SEARCH_TO); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(searchtype_changed), quicksearch); MENUITEM_ADD (search_type, menuitem, - prefs_common_translated_header_name("Tag"), QUICK_SEARCH_TAG); + prefs_common_translated_header_name("Tag"), ADVANCED_SEARCH_TAG); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(searchtype_changed), quicksearch); MENUITEM_ADD (search_type, menuitem, - _("From/To/Subject/Tag"), QUICK_SEARCH_MIXED); + _("From/To/Subject/Tag"), ADVANCED_SEARCH_MIXED); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(searchtype_changed), quicksearch); - MENUITEM_ADD (search_type, menuitem, _("Extended"), QUICK_SEARCH_EXTENDED); + MENUITEM_ADD (search_type, menuitem, _("Extended"), ADVANCED_SEARCH_EXTENDED); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(searchtype_changed), quicksearch); @@ -749,7 +651,6 @@ QuickSearch *quicksearch_new() gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), prefs_common.summary_quicksearch_recurse); - quicksearch_set_recursive(quicksearch, prefs_common.summary_quicksearch_recurse); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(searchtype_recursive_changed), quicksearch); @@ -863,7 +764,6 @@ QuickSearch *quicksearch_new() quicksearch->search_string_entry = search_string_entry; quicksearch->search_condition_expression = search_condition_expression; quicksearch->search_description = search_description; - quicksearch->matcher_list = NULL; quicksearch->active = FALSE; quicksearch->running = FALSE; quicksearch->clear_search = clear_search; @@ -909,14 +809,11 @@ void quicksearch_show(QuickSearch *quicksearch) { MainWindow *mainwin = mainwindow_get_mainwindow(); GtkWidget *ctree = NULL; - prepare_matcher(quicksearch); gtk_widget_show(quicksearch->hbox_search); update_extended_buttons(quicksearch); gtk_widget_grab_focus( GTK_WIDGET(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))))); - GTK_EVENTS_FLUSH(); - if (!mainwin || !mainwin->summaryview) { return; } @@ -931,7 +828,7 @@ void quicksearch_show(QuickSearch *quicksearch) void quicksearch_hide(QuickSearch *quicksearch) { - if (quicksearch_is_active(quicksearch)) { + if (quicksearch_has_sat_predicate(quicksearch)) { quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, ""); quicksearch_set_active(quicksearch, FALSE); } @@ -947,12 +844,11 @@ void quicksearch_hide(QuickSearch *quicksearch) static void quicksearch_set_matchstring(QuickSearch *quicksearch, const gchar *matchstring) { - g_free(quicksearch->request->matchstring); - quicksearch->request->matchstring = g_strdup(matchstring); + g_free(quicksearch->request.matchstring); + quicksearch->request.matchstring = g_strdup(matchstring); } -void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type, - const gchar *matchstring) +void quicksearch_set(QuickSearch *quicksearch, AdvancedSearchType type, const gchar *matchstring) { quicksearch_set_type(quicksearch, type); @@ -960,12 +856,6 @@ void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type, quicksearch->in_typing = FALSE; quicksearch_set_matchstring(quicksearch, matchstring); - - if (!quicksearch_from_gui(quicksearch)) { - prepare_matcher(quicksearch); - /* no callback */ - return; - } g_signal_handlers_block_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))), G_CALLBACK(searchbar_changed_cb), quicksearch); @@ -976,19 +866,12 @@ void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type, prefs_common.summary_quicksearch_type = type; - prepare_matcher(quicksearch); - - quicksearch_set_running(quicksearch, TRUE); - if (quicksearch->callback != NULL) - quicksearch->callback(quicksearch, quicksearch->callback_data); - quicksearch_set_running(quicksearch, FALSE); + quicksearch_invoke_execute(quicksearch, FALSE); } -gboolean quicksearch_is_active(QuickSearch *quicksearch) +gboolean quicksearch_has_sat_predicate(QuickSearch *quicksearch) { - return quicksearch->active && - (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED - || quicksearch->matcher_list != NULL); + return quicksearch->active && advsearch_has_proper_predicate(quicksearch->asearch); } static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active) @@ -1007,8 +890,6 @@ static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active) quicksearch->active = active; - if (quicksearch->gui == FALSE) - return; #if !GTK_CHECK_VERSION(3, 0, 0) if (!colors_initialised) { @@ -1025,8 +906,8 @@ static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active) #endif if (active && - (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED - && quicksearch->matcher_list == NULL)) + (prefs_common.summary_quicksearch_type == ADVANCED_SEARCH_EXTENDED + && !advsearch_has_proper_predicate(quicksearch->asearch))) error = TRUE; if (active) { @@ -1058,7 +939,7 @@ static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active) } if (!active) { - quicksearch_reset_cur_folder_item(quicksearch); + advsearch_abort(quicksearch->asearch); } } @@ -1070,291 +951,6 @@ void quicksearch_set_execute_callback(QuickSearch *quicksearch, quicksearch->callback_data = data; } -gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo) -{ - gchar *searched_header = NULL; - gboolean result = FALSE; - gchar *to = NULL, *from = NULL, *subject = NULL; - QuickSearchType quicksearch_type; - - if (!quicksearch->active) - return TRUE; - - quicksearch_type = quicksearch->request->type; - - switch (quicksearch_type) { - case QUICK_SEARCH_SUBJECT: - if (msginfo->subject) - searched_header = g_utf8_casefold(msginfo->subject, -1); - else - return FALSE; - break; - case QUICK_SEARCH_FROM: - if (msginfo->from) - searched_header = g_utf8_casefold(msginfo->from, -1); - else - return FALSE; - break; - case QUICK_SEARCH_TO: - if (msginfo->to) - searched_header = g_utf8_casefold(msginfo->to, -1); - else - return FALSE; - break; - case QUICK_SEARCH_MIXED: - if (msginfo->to) - to = g_utf8_casefold(msginfo->to, -1); - if (msginfo->from) - from = g_utf8_casefold(msginfo->from, -1); - if (msginfo->subject) - subject = g_utf8_casefold(msginfo->subject, -1); - break; - case QUICK_SEARCH_EXTENDED: - break; - default: - debug_print("unknown search type (%d)\n", quicksearch_type); - break; - } - - quicksearch->matching = TRUE; - if (quicksearch_type != QUICK_SEARCH_EXTENDED && - quicksearch_type != QUICK_SEARCH_MIXED && - quicksearch_type != QUICK_SEARCH_TAG && - quicksearch->search_string && - searched_header && strstr(searched_header, quicksearch->search_string) != NULL) - result = TRUE; - else if (quicksearch_type == QUICK_SEARCH_MIXED && - quicksearch->search_string && ( - (to && strstr(to, quicksearch->search_string) != NULL) || - (from && strstr(from, quicksearch->search_string) != NULL) || - (subject && strstr(subject, quicksearch->search_string) != NULL) || - ((quicksearch->matcher_list != NULL) && - matcherlist_match(quicksearch->matcher_list, msginfo)) )) - result = TRUE; - else if ((quicksearch->matcher_list != NULL) && - matcherlist_match(quicksearch->matcher_list, msginfo)) - result = TRUE; - - quicksearch->matching = FALSE; - if (quicksearch_from_gui(quicksearch)==TRUE && quicksearch->deferred_free) { - /* Ref. http://lists.claws-mail.org/pipermail/users/2010-August/003063.html - See also 2.0.0cvs140 ChangeLog entry - and comment in search_msgs_in_folder() */ - prepare_matcher(quicksearch); - } - - g_free(to); - g_free(from); - g_free(subject); - g_free(searched_header); - - return result; -} - -/* allow Mutt-like patterns in quick search */ -static gchar *expand_search_string(const gchar *search_string) -{ - int i = 0; - gchar term_char, save_char; - gchar *cmd_start, *cmd_end; - GString *matcherstr; - gchar *returnstr = NULL; - gchar *copy_str; - gboolean casesens, dontmatch; - /* list of allowed pattern abbreviations */ - struct { - gchar *abbreviated; /* abbreviation */ - gchar *command; /* actual matcher command */ - gint numparams; /* number of params for cmd */ - gboolean qualifier; /* do we append regexpcase */ - gboolean quotes; /* do we need quotes */ - } - cmds[] = { - { "a", "all", 0, FALSE, FALSE }, - { "ag", "age_greater", 1, FALSE, FALSE }, - { "al", "age_lower", 1, FALSE, FALSE }, - { "b", "body_part", 1, TRUE, TRUE }, - { "B", "message", 1, TRUE, TRUE }, - { "c", "cc", 1, TRUE, TRUE }, - { "C", "to_or_cc", 1, TRUE, TRUE }, - { "D", "deleted", 0, FALSE, FALSE }, - { "e", "header \"Sender\"", 1, TRUE, TRUE }, - { "E", "execute", 1, FALSE, TRUE }, - { "f", "from", 1, TRUE, TRUE }, - { "F", "forwarded", 0, FALSE, FALSE }, - { "h", "headers_part", 1, TRUE, TRUE }, - { "ha", "has_attachments", 0, FALSE, FALSE }, - { "i", "header \"Message-ID\"", 1, TRUE, TRUE }, - { "I", "inreplyto", 1, TRUE, TRUE }, - { "k", "colorlabel", 1, FALSE, FALSE }, - { "L", "locked", 0, FALSE, FALSE }, - { "n", "newsgroups", 1, TRUE, TRUE }, - { "N", "new", 0, FALSE, FALSE }, - { "O", "~new", 0, FALSE, FALSE }, - { "r", "replied", 0, FALSE, FALSE }, - { "R", "~unread", 0, FALSE, FALSE }, - { "s", "subject", 1, TRUE, TRUE }, - { "se", "score_equal", 1, FALSE, FALSE }, - { "sg", "score_greater", 1, FALSE, FALSE }, - { "sl", "score_lower", 1, FALSE, FALSE }, - { "Se", "size_equal", 1, FALSE, FALSE }, - { "Sg", "size_greater", 1, FALSE, FALSE }, - { "Ss", "size_smaller", 1, FALSE, FALSE }, - { "t", "to", 1, TRUE, TRUE }, - { "tg", "tag", 1, TRUE, TRUE }, - { "T", "marked", 0, FALSE, FALSE }, - { "U", "unread", 0, FALSE, FALSE }, - { "x", "header \"References\"", 1, TRUE, TRUE }, - { "X", "test", 1, FALSE, FALSE }, - { "y", "header \"X-Label\"", 1, TRUE, TRUE }, - { "&", "&", 0, FALSE, FALSE }, - { "|", "|", 0, FALSE, FALSE }, - { "p", "partial", 0, FALSE, FALSE }, - { NULL, NULL, 0, FALSE, FALSE } - }; - - if (search_string == NULL) - return NULL; - - copy_str = g_strdup(search_string); - - matcherstr = g_string_sized_new(16); - cmd_start = copy_str; - while (cmd_start && *cmd_start) { - /* skip all white spaces */ - while (*cmd_start && isspace((guchar)*cmd_start)) - cmd_start++; - cmd_end = cmd_start; - - /* extract a command */ - while (*cmd_end && !isspace((guchar)*cmd_end)) - cmd_end++; - - /* save character */ - save_char = *cmd_end; - *cmd_end = '\0'; - - dontmatch = FALSE; - casesens = FALSE; - - /* ~ and ! mean logical NOT */ - if (*cmd_start == '~' || *cmd_start == '!') - { - dontmatch = TRUE; - cmd_start++; - } - /* % means case sensitive match */ - if (*cmd_start == '%') - { - casesens = TRUE; - cmd_start++; - } - - /* find matching abbreviation */ - for (i = 0; cmds[i].command; i++) { - if (!strcmp(cmd_start, cmds[i].abbreviated)) { - /* restore character */ - *cmd_end = save_char; - - /* copy command */ - if (matcherstr->len > 0) { - g_string_append(matcherstr, " "); - } - if (dontmatch) - g_string_append(matcherstr, "~"); - g_string_append(matcherstr, cmds[i].command); - g_string_append(matcherstr, " "); - - /* stop if no params required */ - if (cmds[i].numparams == 0) - break; - - /* extract a parameter, allow quotes */ - while (*cmd_end && isspace((guchar)*cmd_end)) - cmd_end++; - - cmd_start = cmd_end; - if (*cmd_start == '"') { - term_char = '"'; - cmd_end++; - } - else - term_char = ' '; - - /* extract actual parameter */ - while ((*cmd_end) && (*cmd_end != term_char)) - cmd_end++; - - if (*cmd_end == '"') - cmd_end++; - - save_char = *cmd_end; - *cmd_end = '\0'; - - if (cmds[i].qualifier) { - if (casesens) - g_string_append(matcherstr, "regexp "); - else - g_string_append(matcherstr, "regexpcase "); - } - - /* do we need to add quotes ? */ - if (cmds[i].quotes && term_char != '"') - g_string_append(matcherstr, "\""); - - /* copy actual parameter */ - g_string_append(matcherstr, cmd_start); - - /* do we need to add quotes ? */ - if (cmds[i].quotes && term_char != '"') - g_string_append(matcherstr, "\""); - - /* restore original character */ - *cmd_end = save_char; - - break; - } - } - - if (*cmd_end) - cmd_end++; - cmd_start = cmd_end; - } - - g_free(copy_str); - - /* return search string if no match is found to allow - all available filtering expressions in quicksearch */ - if (matcherstr->len > 0) returnstr = matcherstr->str; - else returnstr = g_strdup(search_string); - g_string_free(matcherstr, FALSE); - return returnstr; -} - -static gchar *expand_tag_search_string(const gchar *search_string) -{ - gchar *newstr = NULL; - gchar **words = search_string ? g_strsplit(search_string, " ", -1):NULL; - gint i = 0; - while (words && words[i] && *words[i]) { - g_strstrip(words[i]); - if (!newstr) { - newstr = g_strdup_printf("tag regexpcase \"%s\"", words[i]); - } else { - gint o_len = strlen(newstr); - gint s_len = 18; /* strlen("|tag regexpcase \"\"") */ - gint n_len = s_len + strlen(words[i]); - newstr = g_realloc(newstr,o_len+n_len+1); - strcpy(newstr+o_len, "|tag regexpcase \""); - strcpy(newstr+o_len+(s_len-1), words[i]); - strcpy(newstr+o_len+(n_len-1), "\""); - } - i++; - } - g_strfreev(words); - return newstr; -} - static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run) { quicksearch->running = run; @@ -1412,114 +1008,6 @@ void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType m } -static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch, - FolderItem *src) -{ - GSList *msglist = NULL; - GSList *cur; - gboolean result = FALSE; - gint num = 0, total = 0; - gint interval = quicksearch_is_fast(quicksearch) ? 5000:100; - - statusbar_print_all(_("Searching in %s... \n"), - src->path ? src->path : "(null)"); - - msglist = folder_item_get_msg_list(src); - total = src->total_msgs; - folder_item_update_freeze(); - for (cur = msglist; cur != NULL; cur = cur->next) { - MsgInfo *msg = (MsgInfo *)cur->data; - statusbar_progress_all(num++,total, interval); - if (quicksearch_match(quicksearch, msg)) { - result = TRUE; - break; - } - if (num % interval == 0) - GTK_EVENTS_FLUSH(); - if (!quicksearch_is_active(quicksearch)) - break; - } - folder_item_update_thaw(); - statusbar_progress_all(0,0,0); - statusbar_pop_all(); - - procmsg_msg_list_free(msglist); - return result; -} - -gboolean quicksearch_is_in_subfolder(QuickSearch *quicksearch, FolderItem *cur) -{ - if (quicksearch->root_folder_item == NULL) - return FALSE; - - while (cur) { - if (cur == quicksearch->root_folder_item) { - return TRUE; - } - cur = folder_item_parent(cur); - } - return FALSE; -} - -void quicksearch_search_subfolders(QuickSearch *quicksearch, - FolderView *folderview, - FolderItem *folder_item) -{ - FolderItem *cur = NULL; - GNode *node = folder_item->node->children; - - if (!prefs_common.summary_quicksearch_recurse - || quicksearch->in_typing == TRUE) - return; - - for (; node != NULL; node = node->next) { - cur = FOLDER_ITEM(node->data); - if (quicksearch_match_subfolder(quicksearch, cur)) { - folderview_update_search_icon(cur, TRUE); - } else { - folderview_update_search_icon(cur, FALSE); - } - if (cur->node->children) - quicksearch_search_subfolders(quicksearch, - folderview, - cur); - } - quicksearch->root_folder_item = folder_item; - if (!quicksearch_is_active(quicksearch)) - quicksearch_reset_cur_folder_item(quicksearch); -} - -static void quicksearch_reset_folder_items(QuickSearch *quicksearch, - FolderItem *folder_item) -{ - FolderItem *cur = NULL; - GNode *node = (folder_item && folder_item->node) ? - folder_item->node->children : NULL; - - for (; node != NULL; node = node->next) { - cur = FOLDER_ITEM(node->data); - folderview_update_search_icon(cur, FALSE); - if (cur->node->children) - quicksearch_reset_folder_items(quicksearch, - cur); - } -} - -void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch) -{ - if (quicksearch->root_folder_item) - quicksearch_reset_folder_items(quicksearch, - quicksearch->root_folder_item); - - quicksearch->root_folder_item = NULL; -} - -void quicksearch_folder_item_invalidate(QuickSearch *quicksearch, FolderItem *item) -{ - if (quicksearch->root_folder_item == item) - quicksearch->root_folder_item = NULL; -} - gboolean quicksearch_is_in_typing(QuickSearch *quicksearch) { return quicksearch->in_typing; @@ -1537,7 +1025,7 @@ void quicksearch_set_search_strings(QuickSearch *quicksearch) matcher_parser_disable_warnings(TRUE); do { - newstr = expand_search_string((gchar *) strings->data); + newstr = advsearch_expand_search_string((gchar *) strings->data); if (newstr && newstr[0] != '\0') { if (!strchr(newstr, ' ')) { quicksearch->normal_search_strings = @@ -1574,98 +1062,3 @@ void quicksearch_set_search_strings(QuickSearch *quicksearch) quicksearch_set_popdown_strings(quicksearch); } -/* - * Searches in the supplied folderItem the messages (MessageInfo) matching a - * QuickSearchType + search string (ex.: QUICK_SEARCH_FROM and "foo@bar.com"). - * - * Found messages are appended to the array 'messages' and their ref.counts - * are incremented by 1 --so they need to be released (procmsg_msginfo_free()) - * before the array 'messages' is freed. - */ -void search_msgs_in_folder(GSList **messages, QuickSearch* quicksearch, - FolderItem* folderItem) -{ - /* from quicksearch_match_subfolder */ - GSList *msglist = NULL; - GSList *cur; - - /* The list is built w/ MsgInfo items whose ref.counts are incremented, - but they are decremented when the list is freed by - procmsg_msg_list_free(): we'll ask for a new ref., below - */ - msglist = folder_item_get_msg_list(folderItem); - - for (cur = msglist; cur != NULL; cur = cur->next) { - MsgInfo *msg = (MsgInfo *)cur->data; - if (quicksearch_match(quicksearch, msg)) { - /*debug_print("found: %s from:%s\n",procmsg_get_message_file_path(msg),msg->from);*/ - *messages = g_slist_prepend(*messages, procmsg_msginfo_new_ref(msg)); - } - /* See 2.0.0cvs140 ChangeLog entry for details - see also comments in quicksearch_match() */ - if (quicksearch_from_gui(quicksearch)==TRUE - && !quicksearch_is_active(quicksearch)) - break; - } - procmsg_msg_list_free(msglist); -} - -/* - * Searches within the folderItem and its sub-folders (if recursive is TRUE) - * the messages matching the search request. - * - * NB: search within a Folder can be done this way: - * search_msg_in_folders(messages, quicksearch, searchType, - * FOLDER_ITEM(folder->node->data), TRUE); - */ -void search_msgs_in_folders(GSList **messages, QuickSearch* quicksearch, - FolderItem* folderItem) -{ - FolderItem *cur = NULL; - - search_msgs_in_folder(messages, quicksearch, folderItem); - if (quicksearch->request->recursive == FALSE) - return; - - GNode *node = folderItem->node->children; - for (; node != NULL; node = node->next) { - cur = FOLDER_ITEM(node->data); - debug_print("in: %s\n",cur->path); - if (cur->node->children) - search_msgs_in_folders(messages, quicksearch, cur); - else - search_msgs_in_folder(messages, quicksearch, cur); - } - *messages = g_slist_reverse(*messages); -} - - /* - * Returns the QuickSearchType associated to the supplied string. - */ -QuickSearchType quicksearch_type(const gchar* type) -{ - QuickSearchType searchType = QUICK_SEARCH_EXTENDED; - if (!type) - return searchType; - switch(toupper(*type)) { - case 'S': - searchType = QUICK_SEARCH_SUBJECT; - break; - case 'F': - searchType = QUICK_SEARCH_FROM; - break; - case 'T': - searchType = QUICK_SEARCH_TO; - break; - case 'E': - searchType = QUICK_SEARCH_EXTENDED; - break; - case 'M': - searchType = QUICK_SEARCH_MIXED; - break; - case 'G': - searchType = QUICK_SEARCH_TAG; - break; - } - return searchType; -} diff --git a/src/gtk/quicksearch.h b/src/gtk/quicksearch.h index 7e1de8cb2..c1a26bfac 100644 --- a/src/gtk/quicksearch.h +++ b/src/gtk/quicksearch.h @@ -21,49 +21,33 @@ #ifndef QUICKSEARCH_H #define QUICKSEARCH_H 1 -typedef enum -{ - QUICK_SEARCH_SUBJECT, - QUICK_SEARCH_FROM, - QUICK_SEARCH_TO, - QUICK_SEARCH_EXTENDED, - QUICK_SEARCH_MIXED, - QUICK_SEARCH_TAG -} QuickSearchType; - +#include "advsearch.h" typedef struct _QuickSearch QuickSearch; typedef void (*QuickSearchExecuteCallback) (QuickSearch *quicksearch, gpointer data); #include "procmsg.h" -void search_msgs_in_folders(GSList **messages, QuickSearch* quicksearch, - FolderItem* folderItem); -QuickSearchType quicksearch_type(const gchar *type); - QuickSearch *quicksearch_new(); -QuickSearch *quicksearch_new_nogui(); GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch); void quicksearch_show(QuickSearch *quicksearch); void quicksearch_hide(QuickSearch *quicksearch); -void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type, const gchar *matchstring); +void quicksearch_set(QuickSearch *quicksearch, AdvancedSearchType type, const gchar *matchstring); void quicksearch_set_recursive(QuickSearch *quicksearch, gboolean recursive); -gboolean quicksearch_is_active(QuickSearch *quicksearch); +gboolean quicksearch_has_sat_predicate(QuickSearch *quicksearch); void quicksearch_set_execute_callback(QuickSearch *quicksearch, QuickSearchExecuteCallback callback, gpointer data); -gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo); +void quicksearch_set_on_progress_cb(QuickSearch* search, + gboolean (*cb)(gpointer data, guint at, guint matched, guint total), gpointer data); + +gboolean quicksearch_run_on_folder(QuickSearch* quicksearch, FolderItem *folderItem, MsgInfoList **result); + gboolean quicksearch_is_running(QuickSearch *quicksearch); gboolean quicksearch_has_focus(QuickSearch *quicksearch); void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod); -void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch); -void quicksearch_search_subfolders(QuickSearch *quicksearch, - FolderView *folderview, - FolderItem *folder_item); -gboolean quicksearch_is_in_subfolder(QuickSearch *quicksearch, FolderItem *cur); gboolean quicksearch_is_fast(QuickSearch *quicksearch); gboolean quicksearch_is_in_typing(QuickSearch *quicksearch); void quicksearch_relayout(QuickSearch *quicksearch); void quicksearch_set_search_strings(QuickSearch *quicksearch); -void quicksearch_folder_item_invalidate(QuickSearch *quicksearch, FolderItem *item); #endif /* QUICKSEARCH_H */ diff --git a/src/imap.c b/src/imap.c index 923b51583..ddeceba5b 100644 --- a/src/imap.c +++ b/src/imap.c @@ -445,6 +445,7 @@ FolderClass *imap_get_class(void) imap_class.type = F_IMAP; imap_class.idstr = "imap"; imap_class.uistr = "IMAP4"; + imap_class.supports_server_search = FALSE; /* Folder functions */ imap_class.new_folder = imap_folder_new; @@ -476,6 +477,7 @@ FolderClass *imap_get_class(void) imap_class.add_msgs = imap_add_msgs; imap_class.copy_msg = imap_copy_msg; imap_class.copy_msgs = imap_copy_msgs; + imap_class.search_msgs = folder_item_search_msgs_local; imap_class.remove_msg = imap_remove_msg; imap_class.remove_msgs = imap_remove_msgs; imap_class.expunge = imap_expunge; diff --git a/src/main.c b/src/main.c index 3bc96640f..2e681e37c 100644 --- a/src/main.c +++ b/src/main.c @@ -121,6 +121,7 @@ #include "hooks.h" #include "menu.h" #include "quicksearch.h" +#include "advsearch.h" #ifdef HAVE_LIBETPAN #include "imap-thread.h" @@ -2550,8 +2551,6 @@ static void lock_socket_input_cb(gpointer data, MainWindow *mainwin = (MainWindow *)data; gint sock; gchar buf[BUFFSIZE]; - /* re-use the same quicksearch (& avoid matcher_list mem.leaks) */ - static QuickSearch *quicksearch = NULL; sock = fd_accept(source); fd_gets(sock, buf, sizeof(buf)); @@ -2674,37 +2673,44 @@ static void lock_socket_input_cb(gpointer data, } else if (!strncmp(buf, "search ", 7)) { FolderItem* folderItem = NULL; GSList *messages = NULL; - gchar *folder_name, *request; - QuickSearchType searchType = QUICK_SEARCH_EXTENDED; + gchar *folder_name = NULL; + gchar *request = NULL; + AdvancedSearch *search; gboolean recursive; - - if (quicksearch==NULL) - quicksearch = quicksearch_new_nogui(); + AdvancedSearchType searchType = ADVANCED_SEARCH_EXTENDED; + search = advsearch_new(); + folder_name = g_strdup(buf+7); strretchomp(folder_name); - if (fd_gets(sock, buf, sizeof(buf)) <= 0) { - g_free(folder_name); - folder_name=NULL; - } - searchType = quicksearch_type(buf); - if (fd_gets(sock, buf, sizeof(buf)) <= 0) { - g_free(folder_name); - folder_name=NULL; + if (fd_gets(sock, buf, sizeof(buf)) <= 0) + goto search_exit; + + switch (toupper(buf[0])) { + case 'S': searchType = ADVANCED_SEARCH_SUBJECT; break; + case 'F': searchType = ADVANCED_SEARCH_FROM; break; + case 'T': searchType = ADVANCED_SEARCH_TO; break; + case 'M': searchType = ADVANCED_SEARCH_MIXED; break; + case 'G': searchType = ADVANCED_SEARCH_TAG; break; + case 'E': searchType = ADVANCED_SEARCH_EXTENDED; break; } + + if (fd_gets(sock, buf, sizeof(buf)) <= 0) + goto search_exit; + request = g_strdup(buf); strretchomp(request); recursive = TRUE; if (fd_gets(sock, buf, sizeof(buf)) > 0) - if (buf[0]=='0') - recursive = FALSE; + recursive = buf[0] != '0'; - debug_print("search: %s %i %s %i\n",folder_name,searchType,request,recursive); + debug_print("search: %s %i %s %i\n", folder_name, searchType, request, recursive); if (folder_name) folderItem = folder_find_item_from_identifier(folder_name); + if (folder_name && folderItem == NULL) { debug_print("Unknown folder item : '%s', searching folder\n",folder_name); Folder* folder = folder_find_from_path(folder_name); @@ -2715,16 +2721,16 @@ static void lock_socket_input_cb(gpointer data, } else { debug_print("%s %s\n",folderItem->name, folderItem->path); } + if (folderItem != NULL) { - quicksearch_set(quicksearch, searchType, request); - quicksearch_set_recursive(quicksearch, recursive); - search_msgs_in_folders(&messages, quicksearch, folderItem); + advsearch_set(search, searchType, request); + advsearch_search_msgs_in_folders(search, &messages, folderItem, recursive); } else { g_print("Folder '%s' not found.\n'", folder_name); } GSList *cur; - for (cur=messages; cur != NULL; cur = cur->next) { + for (cur = messages; cur != NULL; cur = cur->next) { MsgInfo* msg = (MsgInfo *)cur->data; gchar *file = procmsg_get_message_file_path(msg); fd_write_all(sock, file, strlen(file)); @@ -2733,10 +2739,12 @@ static void lock_socket_input_cb(gpointer data, } fd_write_all(sock, ".\n", 2); - if (messages != NULL) - procmsg_msg_list_free(messages); +search_exit: g_free(folder_name); g_free(request); + advsearch_free(search); + if (messages != NULL) + procmsg_msg_list_free(messages); } else if (!strncmp(buf, "exit", 4)) { if (prefs_common.clean_on_exit && !prefs_common.ask_on_clean) { procmsg_empty_all_trash(); diff --git a/src/matcher.c b/src/matcher.c index 26e9735ef..386d43a20 100644 --- a/src/matcher.c +++ b/src/matcher.c @@ -48,6 +48,7 @@ #include "log.h" #include "tags.h" #include "folder_item_prefs.h" +#include "procmsg.h" /*! *\brief Keyword lookup element diff --git a/src/matcher.h b/src/matcher.h index cae91f8ad..d4257a3d0 100644 --- a/src/matcher.h +++ b/src/matcher.h @@ -23,7 +23,8 @@ #include #include #include -#include "procmsg.h" +#include "proctypes.h" +#include "matchertypes.h" /* constants generated by yacc */ #include "matcher_parser_lex.h" @@ -41,15 +42,11 @@ struct _MatcherProp { gboolean done; }; -typedef struct _MatcherProp MatcherProp; - struct _MatcherList { GSList *matchers; gboolean bool_and; }; -typedef struct _MatcherList MatcherList; - /* map MATCHCRITERIA_ to yacc's MATCHER_ */ #define MC_(name) \ diff --git a/src/matchertypes.h b/src/matchertypes.h new file mode 100644 index 000000000..2be1bdd2e --- /dev/null +++ b/src/matchertypes.h @@ -0,0 +1,29 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2012 the Claws Mail team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MATCHERTYPES_H +#define MATCHERTYPES_H + +struct _MatcherProp; +typedef struct _MatcherProp MatcherProp; + +struct _MatcherList; +typedef struct _MatcherList MatcherList; + +#endif diff --git a/src/mh.c b/src/mh.c index c4a692be0..bf2236de1 100644 --- a/src/mh.c +++ b/src/mh.c @@ -148,6 +148,7 @@ FolderClass *mh_get_class(void) mh_class.type = F_MH; mh_class.idstr = "mh"; mh_class.uistr = "MH"; + mh_class.supports_server_search = FALSE; /* Folder functions */ mh_class.new_folder = mh_folder_new; @@ -175,6 +176,7 @@ FolderClass *mh_get_class(void) mh_class.add_msgs = mh_add_msgs; mh_class.copy_msg = mh_copy_msg; mh_class.copy_msgs = mh_copy_msgs; + mh_class.search_msgs = folder_item_search_msgs_local; mh_class.remove_msg = mh_remove_msg; mh_class.remove_msgs = mh_remove_msgs; mh_class.remove_all_msg = mh_remove_all_msg; diff --git a/src/mimeview.c b/src/mimeview.c index 574407dd4..07d98951f 100644 --- a/src/mimeview.c +++ b/src/mimeview.c @@ -2787,7 +2787,7 @@ void mimeview_handle_cmd(MimeView *mimeview, const gchar *cmd, GdkEventButton *e GTK_TOGGLE_BUTTON(mimeview->messageview->mainwin->summaryview->toggle_search), TRUE); quicksearch_set(mimeview->messageview->mainwin->summaryview->quicksearch, - QUICK_SEARCH_EXTENDED, buf); + ADVANCED_SEARCH_EXTENDED, buf); g_free(buf); } } diff --git a/src/news.c b/src/news.c index fb4155f2c..541803d1f 100644 --- a/src/news.c +++ b/src/news.c @@ -158,6 +158,7 @@ FolderClass *news_get_class(void) news_class.type = F_NEWS; news_class.idstr = "news"; news_class.uistr = "News"; + news_class.supports_server_search = FALSE; /* Folder functions */ news_class.new_folder = news_folder_new; @@ -175,6 +176,7 @@ FolderClass *news_get_class(void) news_class.get_msginfos = news_get_msginfos; news_class.fetch_msg = news_fetch_msg; news_class.synchronise = news_synchronise; + news_class.search_msgs = folder_item_search_msgs_local; news_class.remove_msg = news_remove_msg; news_class.remove_cached_msg = news_remove_cached_msg; }; diff --git a/src/proctypes.h b/src/proctypes.h index 050e13c62..a95949bef 100644 --- a/src/proctypes.h +++ b/src/proctypes.h @@ -20,6 +20,8 @@ #ifndef PROCTYPES_H #define PROCTYPES_H +#include + struct _MsgInfo; typedef struct _MsgInfo MsgInfo; diff --git a/src/summary_search.c b/src/summary_search.c index 2d20aa533..a68fd1eea 100644 --- a/src/summary_search.c +++ b/src/summary_search.c @@ -79,7 +79,12 @@ static struct SummarySearchWindow { SummaryView *summaryview; - MatcherList *matcher_list; + MatcherList *matcher_list; + gboolean is_fast; + gboolean matcher_is_outdated; + gboolean search_in_progress; + GHashTable *matched_msgnums; + GHashTable *unverified_msgnums; gboolean is_searching; gboolean from_entry_has_focus; @@ -89,8 +94,14 @@ static struct SummarySearchWindow { gboolean adv_condition_entry_has_focus; } search_window; +static gchar* add_history_get(GtkWidget *from, GList **history); + static void summary_search_create (void); +static gboolean summary_search_verify_match (MsgInfo *msg); +static gboolean summary_search_prepare_matcher (); +static gboolean summary_search_prereduce_msg_list (); + static void summary_search_execute (gboolean backward, gboolean search_all); @@ -155,6 +166,28 @@ static gboolean key_pressed (GtkWidget *widget, } #endif +static gchar* add_history_get(GtkWidget *from, GList **history) +{ + gchar *result; + + result = gtk_combo_box_get_active_text(GTK_COMBO_BOX(from)); + if (!result) + result = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(from))), 0, -1); + + if (result && result[0] != '\0') { + /* add to history */ + + combobox_unset_popdown_strings(GTK_COMBO_BOX(from)); + *history = add_history(*history, result); + combobox_set_popdown_strings(GTK_COMBO_BOX(from), *history); + + return result; + } else { + g_free(result); + return NULL; + } +} + void summary_search(SummaryView *summaryview) { if (!search_window.window) { @@ -483,161 +516,215 @@ static void summary_search_create(void) search_window.close_btn = close_btn; search_window.stop_btn = stop_btn; search_window.matcher_list = NULL; + search_window.matcher_is_outdated = TRUE; + search_window.search_in_progress = FALSE; + search_window.matched_msgnums = NULL; + search_window.unverified_msgnums = NULL; search_window.is_searching = is_searching; #ifdef MAEMO maemo_window_full_screen_if_needed(GTK_WINDOW(search_window.window)); #endif } -static void summary_search_execute(gboolean backward, gboolean search_all) +static gboolean summary_search_verify_match(MsgInfo *msg) +{ + gpointer msgnum = GUINT_TO_POINTER(msg->msgnum); + + if (g_hash_table_lookup(search_window.matched_msgnums, msgnum) != NULL) + return TRUE; + + if (g_hash_table_lookup(search_window.unverified_msgnums, msgnum) != NULL) { + GSList *num = g_slist_prepend(NULL, msgnum); + gint match; + + match = folder_item_search_msgs(msg->folder->folder, + msg->folder, + &num, + NULL, + search_window.matcher_list, + NULL, + NULL); + + g_slist_free(num); + g_hash_table_remove(search_window.unverified_msgnums, msgnum); + + if (match > 0) { + g_hash_table_insert(search_window.matched_msgnums, msgnum, GINT_TO_POINTER(1)); + return TRUE; + } else { + return FALSE; + } + } + + return FALSE; +} + +static gboolean summary_search_prepare_matcher() { - SummaryView *summaryview = search_window.summaryview; - GtkCMCTree *ctree = GTK_CMCTREE(summaryview->ctree); - GtkCMCTreeNode *node; - MsgInfo *msginfo; gboolean adv_search; gboolean bool_and = FALSE; gboolean case_sens = FALSE; - gboolean all_searched = FALSE; - gboolean matched = FALSE; - gboolean body_matched = FALSE; + gchar *adv_condition = NULL; + gint match_type; gchar *from_str = NULL, *to_str = NULL, *subject_str = NULL; gchar *body_str = NULL; - gchar *adv_condition = NULL; - gboolean is_fast = TRUE; - gint interval = 1000; - gint i = 0; GSList *matchers = NULL; - if (summary_is_locked(summaryview)) { - return; - } - summary_lock(summaryview); - - adv_search = gtk_toggle_button_get_active - (GTK_TOGGLE_BUTTON(search_window.adv_search_checkbtn)); + if (!search_window.matcher_is_outdated) + return TRUE; if (search_window.matcher_list != NULL) { matcherlist_free(search_window.matcher_list); search_window.matcher_list = NULL; } + + adv_search = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(search_window.adv_search_checkbtn)); + if (adv_search) { - adv_condition = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.adv_condition_entry)); - if (!adv_condition) - adv_condition = gtk_editable_get_chars( - GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.adv_condition_entry))),0,-1); - if (adv_condition && adv_condition[0] != '\0') { - - /* add to history */ - combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.adv_condition_entry)); - prefs_common.summary_search_adv_condition_history = add_history( - prefs_common.summary_search_adv_condition_history, adv_condition); - combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.adv_condition_entry), - prefs_common.summary_search_adv_condition_history); - - search_window.matcher_list = matcher_parser_get_cond((gchar*)adv_condition, &is_fast); - if (!is_fast) - interval = 100; + adv_condition = add_history_get(search_window.adv_condition_entry, &prefs_common.summary_search_adv_condition_history); + if (adv_condition) { + search_window.matcher_list = matcher_parser_get_cond(adv_condition, &search_window.is_fast); /* TODO: check for condition parsing error and show an error dialog */ g_free(adv_condition); } else { /* TODO: warn if no search condition? (or make buttons enabled only when at least one search condition has been set */ - summary_unlock(summaryview); - return; + return FALSE; } } else { - bool_and = combobox_get_active_data( - GTK_COMBO_BOX(search_window.bool_optmenu)); - case_sens = gtk_toggle_button_get_active - (GTK_TOGGLE_BUTTON(search_window.case_checkbtn)); - - from_str = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.from_entry)); - to_str = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.to_entry)); - subject_str = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.subject_entry)); - body_str = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.body_entry)); - - if (!from_str) - from_str = gtk_editable_get_chars( - GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.from_entry))),0,-1); - if (!to_str) - to_str = gtk_editable_get_chars( - GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.to_entry))),0,-1); - if (!subject_str) - subject_str = gtk_editable_get_chars( - GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.subject_entry))),0,-1); - if (!body_str) - body_str = gtk_editable_get_chars( - GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.body_entry))),0,-1); - - if (!from_str || !to_str || !subject_str || !body_str) { + bool_and = combobox_get_active_data(GTK_COMBO_BOX(search_window.bool_optmenu)); + case_sens = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(search_window.case_checkbtn)); + + from_str = add_history_get(search_window.from_entry, &prefs_common.summary_search_from_history); + to_str = add_history_get(search_window.to_entry, &prefs_common.summary_search_to_history); + subject_str = add_history_get(search_window.subject_entry, &prefs_common.summary_search_subject_history); + body_str = add_history_get(search_window.body_entry, &prefs_common.summary_search_body_history); + + if (!from_str && !to_str && !subject_str && !body_str) { /* TODO: warn if no search criteria? (or make buttons enabled only when * at least one search criteria has been set */ - summary_unlock(summaryview); - return; - } - if ( (from_str[0] == '\0') && - (to_str[0] == '\0') && - (subject_str[0] == '\0') && - (body_str[0] == '\0')) { - /* TODO: warn if no search criteria? (or make buttons enabled only when - at least one search criteria has been set */ - summary_unlock(summaryview); - return; + return FALSE; } - /* add to history */ - if (from_str[0] != '\0') { - MatcherProp *prop = matcherprop_new(MATCHCRITERIA_FROM, - NULL, case_sens ? MATCHTYPE_MATCH:MATCHTYPE_MATCHCASE, - from_str, 0); + match_type = case_sens ? MATCHTYPE_MATCH : MATCHTYPE_MATCHCASE; + + if (from_str) { + MatcherProp *prop = matcherprop_new(MATCHCRITERIA_FROM, NULL, match_type, from_str, 0); matchers = g_slist_append(matchers, prop); - combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.from_entry)); - prefs_common.summary_search_from_history = add_history( - prefs_common.summary_search_from_history, from_str); - combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.from_entry), - prefs_common.summary_search_from_history); } - if (to_str[0] != '\0') { - MatcherProp *prop = matcherprop_new(MATCHCRITERIA_TO, - NULL, case_sens ? MATCHTYPE_MATCH:MATCHTYPE_MATCHCASE, - to_str, 0); + if (to_str) { + MatcherProp *prop = matcherprop_new(MATCHCRITERIA_TO, NULL, match_type, to_str, 0); matchers = g_slist_append(matchers, prop); - combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.to_entry)); - prefs_common.summary_search_to_history = add_history( - prefs_common.summary_search_to_history, to_str); - combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.to_entry), - prefs_common.summary_search_to_history); } - if (subject_str[0] != '\0') { - MatcherProp *prop = matcherprop_new(MATCHCRITERIA_SUBJECT, - NULL, case_sens ? MATCHTYPE_MATCH:MATCHTYPE_MATCHCASE, - subject_str, 0); + if (subject_str) { + MatcherProp *prop = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL, match_type, subject_str, 0); matchers = g_slist_append(matchers, prop); - combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.subject_entry)); - prefs_common.summary_search_subject_history = add_history( - prefs_common.summary_search_subject_history, subject_str); - combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.subject_entry), - prefs_common.summary_search_subject_history); } - if (body_str[0] != '\0') { - MatcherProp *prop = matcherprop_new(MATCHCRITERIA_BODY_PART, - NULL, case_sens ? MATCHTYPE_MATCH:MATCHTYPE_MATCHCASE, - body_str, 0); + if (body_str) { + MatcherProp *prop = matcherprop_new(MATCHCRITERIA_BODY_PART, NULL, match_type, body_str, 0); matchers = g_slist_append(matchers, prop); - combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.body_entry)); - prefs_common.summary_search_body_history = add_history( - prefs_common.summary_search_body_history, body_str); - combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.body_entry), - prefs_common.summary_search_body_history); } search_window.matcher_list = matcherlist_new(matchers, bool_and); + + g_free(from_str); + g_free(to_str); + g_free(subject_str); + g_free(body_str); + } + + search_window.matcher_is_outdated = FALSE; + + return TRUE; +} + +static gboolean summary_search_prereduce_msg_list() +{ + MsgInfo *msginfo; + FolderItem *folder; + gint matched_count; + MsgNumberList *msgnums = NULL; + MsgNumberList *cur; + SummaryView *summaryview = search_window.summaryview; + GtkCMCTree *ctree = GTK_CMCTREE(summaryview->ctree); + gboolean on_server; + + if (search_window.matcher_is_outdated && !summary_search_prepare_matcher()) { + return FALSE; + } + + msginfo = gtk_cmctree_node_get_row_data( + ctree, + GTK_CMCTREE_NODE(GTK_CMCLIST(ctree)->row_list)); + folder = msginfo->folder; + + on_server = folder->folder->klass->supports_server_search; + + if (on_server) { + matched_count = folder_item_search_msgs(folder->folder, + folder, + &msgnums, + &on_server, + search_window.matcher_list, + NULL, + NULL); + + if (matched_count < 0) { + alertpanel_error(_("Something went wrong during search. Please check you logs.")); + return FALSE; + } + } else { + gboolean old_valid = TRUE; + + folder->folder->klass->get_num_list(folder->folder, folder, &msgnums, &old_valid); + } + + if (search_window.unverified_msgnums != NULL) { + g_hash_table_unref(search_window.unverified_msgnums); + } + if (search_window.matched_msgnums != NULL) { + g_hash_table_unref(search_window.matched_msgnums); + } + + search_window.unverified_msgnums = g_hash_table_new(g_direct_hash, NULL); + search_window.matched_msgnums = g_hash_table_new(g_direct_hash, NULL); + for (cur = msgnums; cur != NULL; cur = cur->next) { + g_hash_table_insert(search_window.unverified_msgnums, cur->data, GINT_TO_POINTER(1)); + } + g_slist_free(msgnums); + + if (msginfo->folder->folder->klass->supports_server_search && on_server) { + GHashTable *tmp = search_window.matched_msgnums; + search_window.matched_msgnums = search_window.unverified_msgnums; + search_window.unverified_msgnums = tmp; + } + + return TRUE; +} + +static void summary_search_execute(gboolean backward, gboolean search_all) +{ + SummaryView *summaryview = search_window.summaryview; + GtkCMCTree *ctree = GTK_CMCTREE(summaryview->ctree); + GtkCMCTreeNode *node; + MsgInfo *msginfo; + gboolean all_searched = FALSE; + gboolean matched = FALSE; + gint i = 0; + + if (summary_is_locked(summaryview)) { + return; } + summary_lock(summaryview); search_window.is_searching = TRUE; main_window_cursor_wait(summaryview->mainwin); summary_show_stop_button(); + if (search_window.matcher_is_outdated && !summary_search_prereduce_msg_list()) { + goto exit; + } + if (search_all) { summary_freeze(summaryview); summary_unselect_all(summaryview); @@ -659,11 +746,9 @@ static void summary_search_execute(gboolean backward, gboolean search_all) } } else { if (backward) { - node = gtkut_ctree_node_prev - (ctree, summaryview->selected); + node = gtkut_ctree_node_prev(ctree, summaryview->selected); } else { - node = gtkut_ctree_node_next - (ctree, summaryview->selected); + node = gtkut_ctree_node_next(ctree, summaryview->selected); } } @@ -693,11 +778,9 @@ static void summary_search_execute(gboolean backward, gboolean search_all) GTK_STOCK_NO, "+" GTK_STOCK_YES, NULL); if (G_ALERTALTERNATE == val) { if (backward) { - node = GTK_CMCTREE_NODE - (GTK_CMCLIST(ctree)->row_list_end); + node = GTK_CMCTREE_NODE(GTK_CMCLIST(ctree)->row_list_end); } else { - node = GTK_CMCTREE_NODE - (GTK_CMCLIST(ctree)->row_list); + node = GTK_CMCTREE_NODE(GTK_CMCLIST(ctree)->row_list); } all_searched = TRUE; @@ -709,44 +792,33 @@ static void summary_search_execute(gboolean backward, gboolean search_all) } msginfo = gtk_cmctree_node_get_row_data(ctree, node); - body_matched = FALSE; - matched = matcherlist_match(search_window.matcher_list, msginfo); + matched = summary_search_verify_match(msginfo); if (matched) { if (search_all) { gtk_cmctree_select(ctree, node); } else { - if (messageview_is_visible - (summaryview->messageview)) { + if (messageview_is_visible(summaryview->messageview)) { summary_unlock(summaryview); - summary_select_node - (summaryview, node, TRUE, TRUE); + summary_select_node(summaryview, node, TRUE, TRUE); summary_lock(summaryview); - if (body_matched) { - messageview_search_string - (summaryview->messageview, - body_str, case_sens); - } } else { - summary_select_node - (summaryview, node, FALSE, TRUE); + summary_select_node(summaryview, node, FALSE, TRUE); } break; } } + if (i % (search_window.is_fast ? 1000 : 100) == 0) { + GTK_EVENTS_FLUSH(); + } + node = backward ? gtkut_ctree_node_prev(ctree, node) : gtkut_ctree_node_next(ctree, node); - if (i % interval == 0) - GTK_EVENTS_FLUSH(); } - g_free(from_str); - g_free(to_str); - g_free(subject_str); - g_free(body_str); - +exit: search_window.is_searching = FALSE; summary_hide_stop_button(); main_window_cursor_normal(summaryview->mainwin); @@ -771,6 +843,7 @@ static void summary_search_clear(GtkButton *button, gpointer data) if (search_window.is_searching) { search_window.is_searching = FALSE; } + search_window.matcher_is_outdated = TRUE; } static void summary_search_prev_clicked(GtkButton *button, gpointer data) @@ -800,6 +873,7 @@ static void adv_condition_btn_done(MatcherList * matchers) } str = matcherlist_to_string(matchers); + search_window.matcher_is_outdated = TRUE; if (str != NULL) { gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((search_window.adv_condition_entry)))), str); @@ -837,30 +911,35 @@ static void from_changed(void) { if (!search_window.from_entry_has_focus) gtk_widget_grab_focus(search_window.from_entry); + search_window.matcher_is_outdated = TRUE; } static void to_changed(void) { if (!search_window.to_entry_has_focus) gtk_widget_grab_focus(search_window.to_entry); + search_window.matcher_is_outdated = TRUE; } static void subject_changed(void) { if (!search_window.subject_entry_has_focus) gtk_widget_grab_focus(search_window.subject_entry); + search_window.matcher_is_outdated = TRUE; } static void body_changed(void) { if (!search_window.body_entry_has_focus) gtk_widget_grab_focus(search_window.body_entry); + search_window.matcher_is_outdated = TRUE; } static void adv_condition_changed(void) { if (!search_window.adv_condition_entry_has_focus) gtk_widget_grab_focus(search_window.adv_condition_entry); + search_window.matcher_is_outdated = TRUE; } static gboolean from_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event, diff --git a/src/summaryview.c b/src/summaryview.c index 799d709b5..22497d834 100644 --- a/src/summaryview.c +++ b/src/summaryview.c @@ -394,6 +394,7 @@ static gint summary_cmp_by_tags (GtkCMCList *clist, static void quicksearch_execute_cb (QuickSearch *quicksearch, gpointer data); + static void tog_searchbar_cb (GtkWidget *w, gpointer data); @@ -1068,16 +1069,101 @@ static void summary_switch_from_to(SummaryView *summaryview, FolderItem *item) summary_set_column_titles(summaryview); } -static gboolean summaryview_quicksearch_recurse(gpointer data) +static void summaryview_reset_recursive_folder_match(SummaryView *summaryview) { - SummaryView *summaryview = (SummaryView *)data; + GSList *cur; + + for (cur = summaryview->recursive_matched_folders; cur != NULL; cur = cur->next) { + folderview_update_search_icon(cur->data, FALSE); + } + + g_slist_free(summaryview->recursive_matched_folders); + summaryview->recursive_matched_folders = NULL; + summaryview->search_root_folder = NULL; +} + +static gboolean summaryview_quicksearch_recursive_progress(gpointer data, guint at, guint matched, guint total) +{ + QuickSearch *search = (QuickSearch*) data; + gint interval = quicksearch_is_fast(search) ? 5000 : 100; + + statusbar_progress_all(at, total, interval); + if (at % interval == 0) + GTK_EVENTS_FLUSH(); + + if (matched > 0) + return FALSE; + + return TRUE; +} + +static void summaryview_quicksearch_recurse_step(SummaryView *summaryview, FolderItem *item) +{ + MsgInfoList *msgs = NULL; + gboolean result = TRUE; + + statusbar_print_all(_("Searching in %s... \n"), + item->path ? item->path : "(null)"); + folder_item_update_freeze(); + + quicksearch_set_on_progress_cb(summaryview->quicksearch, summaryview_quicksearch_recursive_progress, summaryview->quicksearch); + if (!quicksearch_run_on_folder(summaryview->quicksearch, item, &msgs)) + result = FALSE; + + result = result && msgs != NULL; + + if (msgs != NULL) + procmsg_msg_list_free(msgs); + + folder_item_update_thaw(); + statusbar_progress_all(0, 0, 0); + statusbar_pop_all(); + + if (result) { + summaryview->recursive_matched_folders = g_slist_prepend( + summaryview->recursive_matched_folders, item); + + folderview_update_search_icon(item, TRUE); + } +} + +static void summaryview_quicksearch_search_subfolders(SummaryView *summaryview, FolderItem *folder_item) +{ + FolderItem *cur = NULL; + GNode *node = folder_item->node->children; + + if (!prefs_common.summary_quicksearch_recurse + || !quicksearch_has_sat_predicate(summaryview->quicksearch) + || quicksearch_is_in_typing(summaryview->quicksearch)) + return; + + for (; node != NULL; node = node->next) { + if (!quicksearch_has_sat_predicate(summaryview->quicksearch)) + return; + + cur = FOLDER_ITEM(node->data); + summaryview_quicksearch_recurse_step(summaryview, cur); + if (cur->node->children) + summaryview_quicksearch_search_subfolders(summaryview, cur); + } +} + +static void summaryview_quicksearch_recurse(SummaryView *summaryview) +{ + if (!prefs_common.summary_quicksearch_recurse + || !quicksearch_has_sat_predicate(summaryview->quicksearch) + || summaryview->folder_item == NULL) { + return; + } + main_window_cursor_wait(summaryview->mainwin); - quicksearch_reset_cur_folder_item(summaryview->quicksearch); - quicksearch_search_subfolders(summaryview->quicksearch, - summaryview->folderview, - summaryview->folder_item); + + summaryview_reset_recursive_folder_match(summaryview); + summaryview->search_root_folder = summaryview->folder_item; + + summaryview_quicksearch_search_subfolders(summaryview, summaryview->folder_item); + main_window_cursor_normal(summaryview->mainwin); - return FALSE; } static gboolean summary_check_consistency(FolderItem *item, GSList *mlist) @@ -1125,6 +1211,20 @@ static gboolean summary_check_consistency(FolderItem *item, GSList *mlist) return TRUE; } +static gboolean summaryview_quicksearch_root_progress(gpointer data, guint at, guint matched, guint total) +{ + SummaryView *summaryview = (SummaryView*) data; + + gint interval = quicksearch_is_fast(summaryview->quicksearch) ? 5000 : 100; + + statusbar_progress_all(at, total, interval); + + if (at % interval == 0) + GTK_EVENTS_FLUSH(); + + return TRUE; +} + gboolean summary_show(SummaryView *summaryview, FolderItem *item) { GtkCMCTree *ctree = GTK_CMCTREE(summaryview->ctree); @@ -1169,8 +1269,8 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item) } if (!prefs_common.summary_quicksearch_sticky && (!prefs_common.summary_quicksearch_recurse - || !quicksearch_is_active(summaryview->quicksearch) - || (item && !quicksearch_is_in_subfolder(summaryview->quicksearch, item))) + || !quicksearch_has_sat_predicate(summaryview->quicksearch) + || (item && !folder_is_child_of(item, summaryview->search_root_folder))) && !quicksearch_is_running(summaryview->quicksearch) && !is_refresh) { quicksearch_set(summaryview->quicksearch, prefs_common.summary_quicksearch_type, ""); @@ -1207,7 +1307,7 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item) END_TIMING(); return FALSE; } - if (changed || !quicksearch_is_active(summaryview->quicksearch)) + if (changed || !quicksearch_has_sat_predicate(summaryview->quicksearch)) folder_update_op_count(); } @@ -1231,16 +1331,7 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item) summary_thaw(summaryview); summary_unlock(summaryview); inc_unlock(); - if (item && quicksearch_is_running(summaryview->quicksearch)) { - main_window_cursor_wait(summaryview->mainwin); - quicksearch_reset_cur_folder_item(summaryview->quicksearch); - if (quicksearch_is_active(summaryview->quicksearch)) - quicksearch_search_subfolders(summaryview->quicksearch, - summaryview->folderview, - summaryview->folder_item); - main_window_cursor_normal(summaryview->mainwin); - } - END_TIMING(); + END_TIMING(); return TRUE; } g_free(buf); @@ -1266,10 +1357,44 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item) mlist = folder_item_get_msg_list(item); } + if (quicksearch_has_sat_predicate(summaryview->quicksearch)) { + procmsg_msg_list_free(mlist); + mlist = NULL; + + START_TIMING("quicksearch"); + + statusbar_print_all(_("Searching in %s... \n"), + summaryview->folder_item->path ? + summaryview->folder_item->path : "(null)"); + + folder_item_update_freeze(); + + quicksearch_set_on_progress_cb(summaryview->quicksearch, summaryview_quicksearch_root_progress, summaryview); + quicksearch_run_on_folder(summaryview->quicksearch, summaryview->folder_item, &mlist); + + folder_item_update_thaw(); + statusbar_progress_all(0, 0, 0); + statusbar_pop_all(); + + if (!quicksearch_has_sat_predicate(summaryview->quicksearch)) { + debug_print("search cancelled!\n"); + printf("search cancelled!\n"); + summary_thaw(summaryview); + STATUSBAR_POP(summaryview->mainwin); + main_window_cursor_normal(summaryview->mainwin); + summary_unlock(summaryview); + inc_unlock(); + summary_show(summaryview, summaryview->folder_item); + END_TIMING(); + return FALSE; + } + END_TIMING(); + } + if ((summaryview->folder_item->hide_read_msgs || summaryview->folder_item->hide_del_msgs || summaryview->folder_item->hide_read_threads) && - quicksearch_is_active(summaryview->quicksearch) == FALSE) { + quicksearch_has_sat_predicate(summaryview->quicksearch) == FALSE) { GSList *not_killed; summary_set_hide_read_msgs_menu(summaryview, summaryview->folder_item->hide_read_msgs); @@ -1312,52 +1437,6 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item) summary_set_hide_read_threads_menu(summaryview, FALSE); } - if (quicksearch_is_active(summaryview->quicksearch)) { - GSList *not_killed; - gint interval = quicksearch_is_fast(summaryview->quicksearch) ? 5000:100; - START_TIMING("quicksearch"); - gint num = 0, total = summaryview->folder_item->total_msgs; - statusbar_print_all(_("Searching in %s... \n"), - summaryview->folder_item->path ? - summaryview->folder_item->path : "(null)"); - not_killed = NULL; - folder_item_update_freeze(); - for (cur = mlist ; cur != NULL && cur->data != NULL ; cur = g_slist_next(cur)) { - MsgInfo * msginfo = (MsgInfo *) cur->data; - - statusbar_progress_all(num++,total, interval); - - if (!msginfo->hidden && quicksearch_match(summaryview->quicksearch, msginfo)) - not_killed = g_slist_prepend(not_killed, msginfo); - else - procmsg_msginfo_free(msginfo); - if (num % interval == 0) - GTK_EVENTS_FLUSH(); - if (!quicksearch_is_active(summaryview->quicksearch)) { - break; - } - } - folder_item_update_thaw(); - statusbar_progress_all(0,0,0); - statusbar_pop_all(); - - hidden_removed = TRUE; - if (!quicksearch_is_active(summaryview->quicksearch)) { - debug_print("search cancelled!\n"); - summary_thaw(summaryview); - STATUSBAR_POP(summaryview->mainwin); - main_window_cursor_normal(summaryview->mainwin); - summary_unlock(summaryview); - inc_unlock(); - summary_show(summaryview, summaryview->folder_item); - END_TIMING(); - return FALSE; - } - g_slist_free(mlist); - mlist = not_killed; - END_TIMING(); - } - if (!hidden_removed) { START_TIMING("removing hidden"); not_killed = NULL; @@ -1382,13 +1461,6 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item) g_slist_free(mlist); - if (quicksearch_is_active(summaryview->quicksearch) && - quicksearch_is_running(summaryview->quicksearch)) { - /* only scan subfolders when quicksearch changed, - * not when search is the same and folder changed */ - g_timeout_add(100, summaryview_quicksearch_recurse, summaryview); - } - if (is_refresh) { if (!quicksearch_is_in_typing(summaryview->quicksearch)) { summaryview->displayed = @@ -2492,7 +2564,7 @@ static void summary_status_show(SummaryView *summaryview) if (summaryview->folder_item->hide_read_msgs || summaryview->folder_item->hide_del_msgs || summaryview->folder_item->hide_read_threads - || quicksearch_is_active(summaryview->quicksearch)) { + || quicksearch_has_sat_predicate(summaryview->quicksearch)) { rowlist = GTK_CMCLIST(summaryview->ctree)->row_list; for (cur = rowlist; cur != NULL && cur->data != NULL; cur = cur->next) { msginfo = gtk_cmctree_node_get_row_data @@ -6738,7 +6810,11 @@ static void quicksearch_execute_cb(QuickSearch *quicksearch, gpointer data) { SummaryView *summaryview = data; - summary_show(summaryview, summaryview->folder_item); + summaryview_reset_recursive_folder_match(summaryview); + if (summary_show(summaryview, summaryview->folder_item)) + summaryview_quicksearch_recurse(summaryview); + else + summaryview_reset_recursive_folder_match(summaryview); } static void tog_searchbar_cb(GtkWidget *w, gpointer data) @@ -8014,8 +8090,6 @@ static gboolean summary_update_folder_hook(gpointer source, gpointer data) hookdata = source; if (hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) { summary_update_unread(summaryview, hookdata->item); - quicksearch_folder_item_invalidate(summaryview->quicksearch, - hookdata->item); } else summary_update_unread(summaryview, NULL); @@ -8052,7 +8126,7 @@ static void summary_find_answers (SummaryView *summaryview, MsgInfo *msg) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(summaryview->toggle_search), TRUE); - quicksearch_set(summaryview->quicksearch, QUICK_SEARCH_EXTENDED, buf); + quicksearch_set(summaryview->quicksearch, ADVANCED_SEARCH_EXTENDED, buf); g_free(buf); node = gtk_cmctree_node_nth(GTK_CMCTREE(summaryview->ctree), 0); diff --git a/src/summaryview.h b/src/summaryview.h index 147a04a41..d99a510bd 100644 --- a/src/summaryview.h +++ b/src/summaryview.h @@ -166,6 +166,10 @@ private: gint folder_update_callback_id; GtkTargetList *target_list; /* DnD */ + + // folders with matches for recursive quicksearch queries + GSList *recursive_matched_folders; + FolderItem *search_root_folder; #if !GTK_CHECK_VERSION(2,12,0) GtkTooltips *tooltips;