Update search folder on new mail; fix #7132

Also disables the search folder when it is hidden, so we aren't doing
background I/O.
This commit is contained in:
Charles Lindsay 2013-07-10 16:06:08 -07:00
parent d07f342af3
commit aebfadea70
8 changed files with 169 additions and 53 deletions

View file

@ -1675,12 +1675,23 @@ public class GearyController {
}
private void do_search(string search_text) {
Geary.SearchFolder? folder = null;
try {
folder = (Geary.SearchFolder) current_account.get_special_folder(
Geary.SpecialFolderType.SEARCH);
} catch (Error e) {
debug("Could not get search folder: %s", e.message);
return;
}
if (search_text == "") {
if (previous_non_search_folder != null && current_folder is Geary.SearchFolder)
main_window.folder_list.select_folder(previous_non_search_folder);
main_window.folder_list.remove_search();
search_text_changed("");
folder.clear();
return;
}
@ -1690,16 +1701,7 @@ public class GearyController {
cancel_search(); // Stop any search in progress.
Geary.SearchFolder? folder;
try {
folder = (Geary.SearchFolder) current_account.get_special_folder(
Geary.SpecialFolderType.SEARCH);
folder.set_search_keywords(search_text, cancellable_search);
} catch (Error e) {
debug("Could not get search folder: %s", e.message);
return;
}
folder.set_search_query(search_text, cancellable_search);
main_window.folder_list.set_search(folder);
search_text_changed(main_window.main_toolbar.search_text);

View file

@ -286,8 +286,9 @@ public class ConversationViewer : Gtk.Box {
}
}
private void on_search_text_changed() {
highlight_search_terms.begin();
private void on_search_text_changed(string? query) {
if (query != null)
highlight_search_terms.begin();
}
private async void highlight_search_terms() {
@ -304,7 +305,7 @@ public class ConversationViewer : Gtk.Box {
try {
// Request a list of search terms.
Gee.Collection<string>? search_keywords = yield search_folder.get_search_keywords_async(
Gee.Collection<string>? search_keywords = yield search_folder.get_search_matches_async(
ids, cancellable_fetch);
// Highlight the search terms.
@ -1713,7 +1714,7 @@ public class ConversationViewer : Gtk.Box {
web_view.unmark_text_matches();
if (search_folder != null) {
search_folder.search_keywords_changed.disconnect(on_search_text_changed);
search_folder.search_query_changed.disconnect(on_search_text_changed);
search_folder = null;
}
@ -1751,7 +1752,7 @@ public class ConversationViewer : Gtk.Box {
private uint on_enter_search_folder(uint state, uint event, void *user, Object? object) {
search_folder = current_folder as Geary.SearchFolder;
assert(search_folder != null);
search_folder.search_keywords_changed.connect(on_search_text_changed);
search_folder.search_query_changed.connect(on_search_text_changed);
return SearchState.SEARCH_FOLDER;
}

View file

@ -97,16 +97,16 @@ public abstract class Geary.AbstractAccount : BaseObject, Geary.Account {
public abstract async Geary.Email local_fetch_email_async(Geary.EmailIdentifier email_id,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
public abstract async Geary.EmailIdentifier? folder_email_id_to_search(
public abstract async Geary.EmailIdentifier? folder_email_id_to_search_async(
Geary.FolderPath folder_path, Geary.EmailIdentifier id,
Geary.FolderPath? return_folder_path, Cancellable? cancellable = null) throws Error;
public abstract async Gee.Collection<Geary.EmailIdentifier>? local_search_async(string keywords,
public abstract async Gee.Collection<Geary.EmailIdentifier>? local_search_async(string query,
Geary.Email.Field requested_fields, bool partial_ok, Geary.FolderPath? email_id_folder_path,
int limit = 100, int offset = 0, Gee.Collection<Geary.FolderPath?>? folder_blacklist = null,
Gee.Collection<Geary.EmailIdentifier>? search_ids = null, Cancellable? cancellable = null) throws Error;
public abstract async Gee.Collection<string>? get_search_keywords_async(
public abstract async Gee.Collection<string>? get_search_matches_async(
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error;
public virtual string to_string() {

View file

@ -248,12 +248,12 @@ public interface Geary.Account : BaseObject {
* be used in local_fetch_email_async(). Return null if the email id isn't
* in the local database.
*/
public abstract async Geary.EmailIdentifier? folder_email_id_to_search(
public abstract async Geary.EmailIdentifier? folder_email_id_to_search_async(
Geary.FolderPath folder_path, Geary.EmailIdentifier id,
Geary.FolderPath? return_folder_path, Cancellable? cancellable = null) throws Error;
/**
* Performs a search with the given keyword string. Optionally, a list of folders not to search
* Performs a search with the given query string. Optionally, a list of folders not to search
* can be passed as well as a list of email identifiers to restrict the search to only those messages.
* Returns a list of email objects with the requested fields. If partial_ok is false, mail
* will only be returned if it includes all requested fields. The
@ -263,16 +263,16 @@ public interface Geary.Account : BaseObject {
* you can walk the table. limit can be negative to mean "no limit" but
* offset must not be negative.
*/
public abstract async Gee.Collection<Geary.Email>? local_search_async(string keywords,
public abstract async Gee.Collection<Geary.Email>? local_search_async(string query,
Geary.Email.Field requested_fields, bool partial_ok, Geary.FolderPath? email_id_folder_path,
int limit = 100, int offset = 0, Gee.Collection<Geary.FolderPath?>? folder_blacklist = null,
Gee.Collection<Geary.EmailIdentifier>? search_ids = null, Cancellable? cancellable = null) throws Error;
/**
* Given a list of mail IDs, returns a list of keywords that match for the current
* search keywords.
* Given a list of mail IDs, returns a list of words that match for the
* last run local_search_async() query.
*/
public abstract async Gee.Collection<string>? get_search_keywords_async(
public abstract async Gee.Collection<string>? get_search_matches_async(
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error;
/**

View file

@ -55,19 +55,21 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
Geary.SpecialFolderType.TRASH,
// Orphan emails (without a folder) are also excluded; see ctor.
};
private string? search_query = null;
private Gee.TreeSet<Geary.Email> search_results;
private Geary.Nonblocking.Mutex result_mutex = new Geary.Nonblocking.Mutex();
/**
* Fired when the search keywords have changed. This signal is fired *after* the search
* Fired when the search query has changed. This signal is fired *after* the search
* has completed.
*/
public signal void search_keywords_changed(string keywords);
public signal void search_query_changed(string? query);
public SearchFolder(Account account) {
_account = account;
account.folders_available_unavailable.connect(on_folders_available_unavailable);
account.email_locally_complete.connect(on_email_locally_complete);
clear_search_results();
@ -78,6 +80,7 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
~SearchFolder() {
account.folders_available_unavailable.disconnect(on_folders_available_unavailable);;
account.email_locally_complete.disconnect(on_email_locally_complete);
}
private void on_folders_available_unavailable(Gee.Collection<Geary.Folder>? available,
@ -91,22 +94,102 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
}
}
private async Gee.ArrayList<Geary.EmailIdentifier> folder_ids_to_search_async(Geary.Folder folder,
Gee.Collection<Geary.EmailIdentifier> folder_ids, Cancellable? cancellable) throws Error {
Gee.ArrayList<Geary.EmailIdentifier> local_ids = new Gee.ArrayList<Geary.EmailIdentifier>();
foreach (Geary.EmailIdentifier folder_id in folder_ids) {
// TODO: parallelize.
Geary.EmailIdentifier? local_id = yield account.folder_email_id_to_search_async(
folder.path, folder_id, path, cancellable);
if (local_id != null)
local_ids.add(local_id);
}
return local_ids;
}
private async void append_new_email_async(string query, Geary.Folder folder,
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable) throws Error {
Gee.ArrayList<Geary.EmailIdentifier> local_ids = yield folder_ids_to_search_async(
folder, ids, cancellable);
int result_mutex_token = yield result_mutex.claim_async();
Error? error = null;
try {
Gee.Collection<Geary.Email>? results = yield account.local_search_async(
query, Geary.Email.Field.PROPERTIES, false, path, MAX_RESULT_EMAILS, 0,
exclude_folders, local_ids, cancellable);
if (results != null) {
Gee.HashMap<Geary.EmailIdentifier, Geary.Email> to_add
= new Gee.HashMap<Geary.EmailIdentifier, Geary.Email>();
foreach(Geary.Email email in results)
if (!search_results.contains(email))
to_add.set(email.id, email);
if (to_add.size > 0) {
search_results.add_all(to_add.values);
_properties.set_total(search_results.size);
notify_email_appended(to_add.keys);
notify_email_count_changed(search_results.size, CountChangeReason.APPENDED);
}
}
} catch(Error e) {
error = e;
}
result_mutex.release(ref result_mutex_token);
if (error != null)
throw error;
}
private void on_append_new_email_complete(Object? source, AsyncResult result) {
try {
append_new_email_async.end(result);
} catch(Error e) {
debug("Error appending new email to search results: %s", e.message);
}
}
private void on_email_locally_complete(Geary.Folder folder,
Gee.Collection<Geary.EmailIdentifier> ids) {
if (search_query != null)
append_new_email_async.begin(search_query, folder, ids, null, on_append_new_email_complete);
}
/**
* Clears the search query and results.
*/
public void clear() {
Gee.TreeSet<Geary.Email> local_results = search_results;
clear_search_results();
notify_email_removed(email_collection_to_ids(local_results));
notify_email_count_changed(0, Geary.Folder.CountChangeReason.REMOVED);
if (search_query != null) {
search_query = null;
search_query_changed(null);
}
}
/**
* Sets the keyword string for this search.
*/
public void set_search_keywords(string keywords, Cancellable? cancellable = null) {
set_search_keywords_async.begin(keywords, cancellable, on_set_search_keywords_complete);
public void set_search_query(string query, Cancellable? cancellable = null) {
set_search_query_async.begin(query, cancellable, on_set_search_query_complete);
}
private void on_set_search_keywords_complete(Object? source, AsyncResult result) {
private void on_set_search_query_complete(Object? source, AsyncResult result) {
try {
set_search_keywords_async.end(result);
set_search_query_async.end(result);
} catch(Error e) {
debug("Search error: %s", e.message);
}
}
private async void set_search_keywords_async(string keywords, Cancellable? cancellable = null) throws Error {
private async void set_search_query_async(string query, Cancellable? cancellable = null) throws Error {
int result_mutex_token = yield result_mutex.claim_async();
Error? error = null;
try {
@ -115,7 +198,7 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
// list_email_async() etc., but this leads to some more
// complications when redoing the search.
Gee.Collection<Geary.Email>? _new_results = yield account.local_search_async(
keywords, Geary.Email.Field.PROPERTIES, false, path, MAX_RESULT_EMAILS, 0,
query, Geary.Email.Field.PROPERTIES, false, path, MAX_RESULT_EMAILS, 0,
exclude_folders, null, cancellable);
if (_new_results == null) {
@ -125,6 +208,12 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
Gee.TreeSet<Geary.Email> local_results = search_results;
// Clear existing results.
clear_search_results();
// Note that we probably shouldn't be firing these signals
// from inside our mutex lock. We do it here, below, and
// in append_new_email_async(). Keep an eye on it, and if
// there's ever a case where it might cause problems, it
// shouldn't be too hard to move the firings outside.
notify_email_removed(email_collection_to_ids(local_results));
notify_email_count_changed(0, Geary.Folder.CountChangeReason.REMOVED);
}
@ -132,7 +221,7 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
// Move new search results into a hashset, using email ID for equality.
Gee.HashSet<Geary.Email> new_results = new Gee.HashSet<Geary.Email>(email_id_hash, email_id_equal);
new_results.add_all(_new_results);
// Match the new results up with the existing results.
Gee.HashSet<Geary.Email> to_add = new Gee.HashSet<Geary.Email>(email_id_hash, email_id_equal);
Gee.HashSet<Geary.Email> to_remove = new Gee.HashSet<Geary.Email>(email_id_hash, email_id_equal);
@ -170,7 +259,8 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
result_mutex.release(ref result_mutex_token);
search_keywords_changed(keywords);
search_query = query;
search_query_changed(query);
if (error != null)
throw error;
@ -280,12 +370,14 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
}
/**
* Given a list of mail IDs, returns a list of keywords that match for the current
* search keywords.
* Given a list of mail IDs, returns a list of words that match for the current
* search query.
*/
public async Gee.Collection<string>? get_search_keywords_async(
public async Gee.Collection<string>? get_search_matches_async(
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error {
return yield account.get_search_keywords_async(ids, cancellable);
if (search_query == null)
return null;
return yield account.get_search_matches_async(ids, cancellable);
}
private void exclude_folder(Geary.Folder folder) {

View file

@ -476,7 +476,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
foreach (Geary.EmailIdentifier id in relevant_ids) {
// TODO: parallelize this.
try {
Geary.EmailIdentifier? search_id = yield folder.account.folder_email_id_to_search(
Geary.EmailIdentifier? search_id = yield folder.account.folder_email_id_to_search_async(
folder.path, id, null, cancellable);
if (search_id != null) {
Geary.Email email = yield folder.account.local_fetch_email_async(

View file

@ -625,14 +625,32 @@ private class Geary.ImapDB.Account : BaseObject {
}
}
private string? get_search_ids_sql(Gee.Collection<Geary.EmailIdentifier>? search_ids) throws Error {
if (search_ids == null)
return null;
Gee.ArrayList<int64?> ids = new Gee.ArrayList<int64?>();
foreach (Geary.EmailIdentifier id in search_ids) {
if (!(id is Geary.ImapDB.EmailIdentifier)) {
throw new EngineError.BAD_PARAMETERS(
"search_ids must contain only Geary.ImapDB.EmailIdentifiers");
}
ids.add(id.ordering);
}
StringBuilder sql = new StringBuilder();
sql_append_ids(sql, ids);
return sql.str;
}
public async Gee.Collection<Geary.Email>? search_async(string prepared_query,
Geary.Email.Field requested_fields, bool partial_ok, Geary.FolderPath? email_id_folder_path,
int limit = 100, int offset = 0, Gee.Collection<Geary.FolderPath?>? folder_blacklist = null,
Gee.Collection<Geary.EmailIdentifier>? search_ids = null, Cancellable? cancellable = null) throws Error {
Gee.Collection<Geary.Email> search_results = new Gee.HashSet<Geary.Email>();
// TODO: support search_ids
string? search_ids_sql = get_search_ids_sql(search_ids);
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
string blacklisted_ids_sql = do_get_blacklisted_message_ids_sql(
folder_blacklist, cx, cancellable);
@ -645,6 +663,8 @@ private class Geary.ImapDB.Account : BaseObject {
""";
if (blacklisted_ids_sql != "")
sql += " AND id NOT IN (%s)".printf(blacklisted_ids_sql);
if (!Geary.String.is_empty(search_ids_sql))
sql += " AND id IN (%s)".printf(search_ids_sql);
sql += " ORDER BY internaldate_time_t DESC";
if (limit > 0)
sql += " LIMIT ? OFFSET ?";
@ -678,9 +698,9 @@ private class Geary.ImapDB.Account : BaseObject {
return (search_results.size == 0 ? null : search_results);
}
public async Gee.Collection<string>? get_search_keywords_async(string prepared_query,
public async Gee.Collection<string>? get_search_matches_async(string prepared_query,
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error {
Gee.Set<string> search_keywords = new Gee.HashSet<string>();
Gee.Set<string> search_matches = new Gee.HashSet<string>();
// Create a question mark for each ID.
string id_string = "";
@ -718,7 +738,7 @@ private class Geary.ImapDB.Account : BaseObject {
// the results into our return set.
foreach(SearchOffset offset in all_offsets) {
string text = result.string_at(offset.column + 1);
search_keywords.add(text[offset.byte_offset : offset.byte_offset + offset.size].down());
search_matches.add(text[offset.byte_offset : offset.byte_offset + offset.size].down());
}
result.next(cancellable);
@ -727,7 +747,7 @@ private class Geary.ImapDB.Account : BaseObject {
return Db.TransactionOutcome.DONE;
}, cancellable);
return (search_keywords.size == 0 ? null : search_keywords);
return (search_matches.size == 0 ? null : search_matches);
}
public async Geary.Email fetch_email_async(Geary.EmailIdentifier email_id,
@ -760,7 +780,7 @@ private class Geary.ImapDB.Account : BaseObject {
return email;
}
public async Geary.EmailIdentifier? folder_email_id_to_search(
public async Geary.EmailIdentifier? folder_email_id_to_search_async(
Geary.FolderPath folder_path, Geary.EmailIdentifier id,
Geary.FolderPath? return_folder_path, Cancellable? cancellable) throws Error {
int64? row_id = null;

View file

@ -510,29 +510,30 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
return yield local.fetch_email_async(email_id, required_fields, cancellable);
}
public override async Geary.EmailIdentifier? folder_email_id_to_search(
public override async Geary.EmailIdentifier? folder_email_id_to_search_async(
Geary.FolderPath folder_path, Geary.EmailIdentifier id,
Geary.FolderPath? return_folder_path, Cancellable? cancellable = null) throws Error {
return yield local.folder_email_id_to_search(folder_path, id, return_folder_path, cancellable);
return yield local.folder_email_id_to_search_async(
folder_path, id, return_folder_path, cancellable);
}
public override async Gee.Collection<Geary.Email>? local_search_async(string keywords,
public override async Gee.Collection<Geary.Email>? local_search_async(string query,
Geary.Email.Field requested_fields, bool partial_ok, Geary.FolderPath? email_id_folder_path,
int limit = 100, int offset = 0, Gee.Collection<Geary.FolderPath?>? folder_blacklist = null,
Gee.Collection<Geary.EmailIdentifier>? search_ids = null, Cancellable? cancellable = null) throws Error {
if (offset < 0)
throw new EngineError.BAD_PARAMETERS("Offset must not be negative");
previous_prepared_search_query = local.prepare_search_query(keywords);
previous_prepared_search_query = local.prepare_search_query(query);
return yield local.search_async(local.prepare_search_query(keywords),
return yield local.search_async(previous_prepared_search_query,
requested_fields, partial_ok, email_id_folder_path, limit, offset,
folder_blacklist, search_ids, cancellable);
}
public override async Gee.Collection<string>? get_search_keywords_async(
public override async Gee.Collection<string>? get_search_matches_async(
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error {
return yield local.get_search_keywords_async(previous_prepared_search_query, ids, cancellable);
return yield local.get_search_matches_async(previous_prepared_search_query, ids, cancellable);
}
private void on_login_failed(Geary.Credentials? credentials) {