From 316f19c69af274440f244bc8f7458c9bb8220994 Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Fri, 21 Jun 2013 17:19:39 -0700 Subject: [PATCH] Clean up numerous conv. monitor bugs; fix #7103 --- .../abstract/geary-abstract-account.vala | 4 +- src/engine/api/geary-account.vala | 15 +- .../api/geary-conversation-monitor.vala | 215 +++++++++++------- src/engine/api/geary-search-folder.vala | 48 +++- src/engine/imap-db/imap-db-account.vala | 14 +- .../imap-db/imap-db-email-identifier.vala | 10 +- .../imap-engine-generic-account.vala | 7 +- 7 files changed, 202 insertions(+), 111 deletions(-) diff --git a/src/engine/abstract/geary-abstract-account.vala b/src/engine/abstract/geary-abstract-account.vala index e58396e5..61e85b60 100644 --- a/src/engine/abstract/geary-abstract-account.vala +++ b/src/engine/abstract/geary-abstract-account.vala @@ -84,8 +84,8 @@ public abstract class Geary.AbstractAccount : BaseObject, Geary.Account { Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error; public abstract async Gee.Collection? local_search_async(string keywords, - Geary.Email.Field requested_fields, bool partial_ok, int limit = 100, int offset = 0, - Gee.Collection? folder_blacklist = null, + Geary.Email.Field requested_fields, bool partial_ok, Geary.FolderPath? email_id_folder_path, + int limit = 100, int offset = 0, Gee.Collection? folder_blacklist = null, Gee.Collection? search_ids = null, Cancellable? cancellable = null) throws Error; public abstract async Gee.Collection? get_search_keywords_async( diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala index 8df584ba..16ed809e 100644 --- a/src/engine/api/geary-account.vala +++ b/src/engine/api/geary-account.vala @@ -216,15 +216,16 @@ public interface Geary.Account : BaseObject { * Performs a search with the given keyword 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 list - * is ordered descending by Geary.EmailProperties.date_received, and is - * limited to a maximum number of results and starting offset, so you can - * walk the table. limit can be negative to mean "no limit" but offset - * must not be negative. + * will only be returned if it includes all requested fields. The + * email_id_folder_path is used as the path when creating EmailIdentifiers. + * The list is ordered descending by Geary.EmailProperties.date_received, + * and is limited to a maximum number of results and starting offset, so + * you can walk the table. limit can be negative to mean "no limit" but + * offset must not be negative. */ public abstract async Gee.Collection? local_search_async(string keywords, - Geary.Email.Field requested_fields, bool partial_ok, int limit = 100, int offset = 0, - Gee.Collection? folder_blacklist = null, + Geary.Email.Field requested_fields, bool partial_ok, Geary.FolderPath? email_id_folder_path, + int limit = 100, int offset = 0, Gee.Collection? folder_blacklist = null, Gee.Collection? search_ids = null, Cancellable? cancellable = null) throws Error; /** diff --git a/src/engine/api/geary-conversation-monitor.vala b/src/engine/api/geary-conversation-monitor.vala index da2f8081..8f0710a9 100644 --- a/src/engine/api/geary-conversation-monitor.vala +++ b/src/engine/api/geary-conversation-monitor.vala @@ -100,12 +100,17 @@ public class Geary.ConversationMonitor : BaseObject { } public void add(Email email) { - // since Email is mutable (and Conversations itself mutates them, and callers might as - // well), don't replace known email with new - // - // TODO: Combine new email with old email - if (emails.has_key(email.id)) - return; + Email? existing = emails.get(email.id); + if (existing != null) { + // We "promote" out-of-folder emails to in-folder emails so we + // always have the most useful version. + // FIXME: this assumes that all data about the existing and new + // email are identical. That might not be the case. + if (existing.id.get_folder_path() == null && email.id.get_folder_path() != null) + remove(existing); + else + return; + } emails.set(email.id, email); date_ascending.add(email); @@ -270,17 +275,20 @@ public class Geary.ConversationMonitor : BaseObject { } public override async void execute_async() { - monitor.remove_emails(removed_ids); + yield monitor.remove_emails_async(removed_ids); } } private class FillWindowOperation : ConversationOperation { - public FillWindowOperation(ConversationMonitor monitor) { + private bool is_insert; + + public FillWindowOperation(ConversationMonitor monitor, bool is_insert) { base(monitor); + this.is_insert = is_insert; } public override async void execute_async() { - yield monitor.fill_window_async(); + yield monitor.fill_window_async(is_insert); } } @@ -319,13 +327,36 @@ public class Geary.ConversationMonitor : BaseObject { } } + private class ProcessJobContext : BaseObject { + public Gee.HashSet new_conversations = + new Gee.HashSet(); + + public Gee.MultiMap appended_conversations = + new Gee.HashMultiMap(); + + public Gee.HashSet existing_emails = + new Gee.HashSet(); + + public Gee.HashMap geary_id_map = + new Gee.HashMap(); + + public Gee.HashMap message_id_map = + new Gee.HashMap(); + + public bool inside_scan; + + public ProcessJobContext(bool inside_scan) { + this.inside_scan = inside_scan; + } + } + public Geary.Folder folder { get; private set; } public bool reestablish_connections { get; set; default = true; } public bool is_monitoring { get; private set; default = false; } public int min_window_count { get { return _min_window_count; } set { _min_window_count = value; - operation_queue.add(new FillWindowOperation(this)); + operation_queue.add(new FillWindowOperation(this, avoid_email_id_comparisons)); } } @@ -343,6 +374,12 @@ public class Geary.ConversationMonitor : BaseObject { private bool reseed_notified = false; private int _min_window_count = 0; private ConversationOperationQueue operation_queue = new ConversationOperationQueue(); + // TODO: this hack is a quick way to solve the problem of id-based loads + // not working for the SearchFolder because its EmailIdentifiers are just + // row ids. Really, the solution is to make all EmailIdentifiers ordered. + // If true, this treats all fill-window operations as inserts, which means + // the whole list gets reloaded. + private bool avoid_email_id_comparisons = false; /** * "monitoring-started" is fired when the Conversations folder has been opened for monitoring. @@ -482,6 +519,10 @@ public class Geary.ConversationMonitor : BaseObject { _min_window_count = min_window_count; folder.account.information.notify["imap-credentials"].connect(on_imap_credentials_notified); + + // See the definition of this field; basically if it's the search + // folder, we can't do any id-based loading. This is a hack. + avoid_email_id_comparisons = (folder is Geary.SearchFolder); } ~ConversationMonitor() { @@ -577,7 +618,7 @@ public class Geary.ConversationMonitor : BaseObject { // the reseed has to wait until the folder's remote is opened (handled in on_folder_opened) if (reseed_now) operation_queue.add(new ReseedOperation(this, "already opened")); - operation_queue.add(new FillWindowOperation(this)); + operation_queue.add(new FillWindowOperation(this, avoid_email_id_comparisons)); folder.email_appended.connect(on_folder_email_appended); folder.email_removed.connect(on_folder_email_removed); @@ -678,8 +719,8 @@ public class Geary.ConversationMonitor : BaseObject { Cancellable? cancellable) throws Error { notify_scan_started(); try { - yield start_process_email_async(yield folder.list_email_async(low, count, - required_fields, flags, cancellable)); + yield process_email_async(yield folder.list_email_async(low, count, + required_fields, flags, cancellable), new ProcessJobContext(true)); } catch (Error err) { list_error(err); } @@ -694,8 +735,8 @@ public class Geary.ConversationMonitor : BaseObject { Geary.Folder.ListFlags flags, Cancellable? cancellable) throws Error { notify_scan_started(); try { - yield start_process_email_async(yield folder.list_email_by_id_async(initial_id, - count, required_fields, flags, cancellable)); + yield process_email_async(yield folder.list_email_by_id_async(initial_id, + count, required_fields, flags, cancellable), new ProcessJobContext(true)); } catch (Error err) { list_error(err); throw err; @@ -707,8 +748,8 @@ public class Geary.ConversationMonitor : BaseObject { notify_scan_started(); try { - yield start_process_email_async(yield folder.list_email_by_sparse_id_async(ids, - required_fields, flags, cancellable)); + yield process_email_async(yield folder.list_email_by_sparse_id_async(ids, + required_fields, flags, cancellable), new ProcessJobContext(true)); } catch (Error err) { list_error(err); } @@ -720,21 +761,9 @@ public class Geary.ConversationMonitor : BaseObject { notify_scan_completed(); } - private async void start_process_email_async(Gee.Collection? emails) { - yield process_email_async(emails, new Gee.HashSet(), - new Gee.HashMultiMap(), - new Gee.HashMap(), - new Gee.HashMap()); - } - - private async void process_email_async(Gee.Collection? emails, - Gee.HashSet job_new_conversations, - Gee.MultiMap job_appended_conversations, - Gee.HashMap job_geary_id_map, - Gee.HashMap job_message_id_map) { + private async void process_email_async(Gee.Collection? emails, ProcessJobContext job) { if (emails == null || emails.size == 0) { - process_email_complete(job_new_conversations, job_appended_conversations, - job_geary_id_map, job_message_id_map); + process_email_complete(job); return; } @@ -749,11 +778,10 @@ public class Geary.ConversationMonitor : BaseObject { // of messages with no Message-ID being loaded twice (most often encountered when // the first pass is loading messages directly from the database and the second pass // are messages loaded from both) - // - // TODO: Combine fields from this email with existing email so the monitor is holding - // the freshest stuff ... this may require more signals - if (geary_id_map.has_key(email.id) || job_geary_id_map.has_key(email.id)) + if (geary_id_map.has_key(email.id) || job.geary_id_map.has_key(email.id)) { + job.existing_emails.add(email); continue; + } // Right now, all threading is done with Message-IDs (no parsing of subject lines, etc.) // If a message doesn't have a Message-ID, it's treated as its own conversation @@ -763,7 +791,7 @@ public class Geary.ConversationMonitor : BaseObject { ImplConversation? conversation = null; if (ancestors != null) { foreach (RFC822.MessageID ancestor in ancestors) { - conversation = message_id_map.get(ancestor) ?? job_message_id_map.get(ancestor); + conversation = message_id_map.get(ancestor) ?? job.message_id_map.get(ancestor); if (conversation != null) break; } @@ -772,18 +800,18 @@ public class Geary.ConversationMonitor : BaseObject { // create new conversation if not seen before if (conversation == null) { conversation = new ImplConversation(this); - job_new_conversations.add(conversation); + job.new_conversations.add(conversation); } - job_appended_conversations.set(conversation, email); + job.appended_conversations.set(conversation, email); // map email identifier to email (for later removal) - job_geary_id_map.set(email.id, conversation); + job.geary_id_map.set(email.id, conversation); // map ancestors to this conversation if (ancestors != null) { foreach (RFC822.MessageID ancestor in ancestors) { - job_message_id_map.set(ancestor, conversation); + job.message_id_map.set(ancestor, conversation); // Log every new ancestor for later searching. if (!new_message_ids.contains(ancestor)) @@ -794,8 +822,7 @@ public class Geary.ConversationMonitor : BaseObject { // Expand the conversation to include any Message-IDs we know we need // and may have on disk, but aren't in the folder. - yield expand_conversations(new_message_ids, job_new_conversations, job_appended_conversations, - job_geary_id_map, job_message_id_map); + yield expand_conversations_async(new_message_ids, job); Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::process_email completed: %d emails", folder.to_string(), emails.size); @@ -830,14 +857,10 @@ public class Geary.ConversationMonitor : BaseObject { return blacklist; } - private async void expand_conversations(Gee.Set needed_message_ids, - Gee.HashSet job_new_conversations, - Gee.MultiMap job_appended_conversations, - Gee.HashMap job_geary_id_map, - Gee.HashMap job_message_id_map) { + private async void expand_conversations_async(Gee.Set needed_message_ids, + ProcessJobContext job) { if (needed_message_ids.size == 0) { - process_email_complete(job_new_conversations, job_appended_conversations, - job_geary_id_map, job_message_id_map); + process_email_complete(job); return; } @@ -859,8 +882,7 @@ public class Geary.ConversationMonitor : BaseObject { } catch (Error err) { debug("Unable to search local mail for conversations: %s", err.message); - process_email_complete(job_new_conversations, job_appended_conversations, - job_geary_id_map, job_message_id_map); + process_email_complete(job); return; } @@ -879,41 +901,44 @@ public class Geary.ConversationMonitor : BaseObject { // process them as through they're been loaded from the folder; this, in turn, may // require more local searching of email - yield process_email_async(needed_messages.values, job_new_conversations, - job_appended_conversations, job_geary_id_map, job_message_id_map); + yield process_email_async(needed_messages.values, job); Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::expand_conversations completed: %d email ids (%d found)", folder.to_string(), needed_message_ids.size, needed_messages.size); } - private void process_email_complete(Gee.HashSet job_new_conversations, - Gee.MultiMap job_appended_conversations, - Gee.HashMap job_geary_id_map, - Gee.HashMap job_message_id_map) { - foreach(ImplConversation conversation in job_new_conversations) + private void process_email_complete(ProcessJobContext job) { + foreach(ImplConversation conversation in job.new_conversations) conversations.add(conversation); - foreach (ImplConversation conversation in job_appended_conversations.get_keys()) { - foreach (Geary.Email email in job_appended_conversations.get(conversation)) + foreach (ImplConversation conversation in job.appended_conversations.get_keys()) { + foreach (Geary.Email email in job.appended_conversations.get(conversation)) conversation.add(email); } - foreach (Geary.EmailIdentifier id in job_geary_id_map.keys) - geary_id_map.set(id, job_geary_id_map.get(id)); + foreach (Geary.EmailIdentifier id in job.geary_id_map.keys) + geary_id_map.set(id, job.geary_id_map.get(id)); - foreach (Geary.RFC822.MessageID message_id in job_message_id_map.keys) - message_id_map.set(message_id, job_message_id_map.get(message_id)); + foreach (Geary.RFC822.MessageID message_id in job.message_id_map.keys) + message_id_map.set(message_id, job.message_id_map.get(message_id)); - if (job_new_conversations.size > 0) - notify_conversations_added(job_new_conversations); - - foreach (ImplConversation conversation in job_appended_conversations.get_keys()) { - if (!job_new_conversations.contains(conversation)) - notify_conversation_appended(conversation, job_appended_conversations.get(conversation)); + foreach (Geary.Email email in job.existing_emails) { + ImplConversation conversation = geary_id_map.get(email.id); + // Call add again to make sure it's working with the "best copy". + conversation.add(email); } - - notify_scan_completed(); + + if (job.new_conversations.size > 0) + notify_conversations_added(job.new_conversations); + + foreach (ImplConversation conversation in job.appended_conversations.get_keys()) { + if (!job.new_conversations.contains(conversation)) + notify_conversation_appended(conversation, job.appended_conversations.get(conversation)); + } + + if (job.inside_scan) + notify_scan_completed(); } private void on_folder_email_appended(Gee.Collection appended_ids) { @@ -922,7 +947,7 @@ public class Geary.ConversationMonitor : BaseObject { private void on_folder_email_removed(Gee.Collection removed_ids) { operation_queue.add(new RemoveOperation(this, removed_ids)); - operation_queue.add(new FillWindowOperation(this)); + operation_queue.add(new FillWindowOperation(this, avoid_email_id_comparisons)); } private async void append_emails_async(Gee.Collection appended_ids) { @@ -932,12 +957,12 @@ public class Geary.ConversationMonitor : BaseObject { yield load_by_sparse_id(appended_ids, Geary.Folder.ListFlags.NONE, null); } - private void remove_emails(Gee.Collection removed_ids) { + private async void remove_emails_async(Gee.Collection removed_ids) { debug("%d messages(s) removed to %s, trimming/removing conversations...", removed_ids.size, folder.to_string()); - Gee.HashSet removed = new Gee.HashSet(); - Gee.HashMultiMap trimmed = new Gee.HashMultiMap(); + Gee.HashSet removed = new Gee.HashSet(); + Gee.HashMultiMap trimmed = new Gee.HashMultiMap(); foreach (Geary.EmailIdentifier removed_id in removed_ids) { ImplConversation conversation; if (!geary_id_map.unset(removed_id, out conversation)) { @@ -968,16 +993,19 @@ public class Geary.ConversationMonitor : BaseObject { trimmed.set(conversation, found); if (conversation.get_count(true) == 0) { - // remove non-folder message id's from the message_id_map to truly drop the + // remove non-folder id's from the id maps to truly drop the // conversation (that's all that's remaining in Conversation at this point) ... - // the Conversation must be *completely* dropped from this reverse lookup map, + // the Conversation must be *completely* dropped from these reverse lookup maps, // otherwise future messages coming in will look like appends and not new // conversations foreach (RFC822.MessageID message_id in conversation.message_ids) message_id_map.unset(message_id); assert(!message_id_map.values.contains(conversation)); + foreach (EmailIdentifier id in conversation.get_email_ids()) + geary_id_map.unset(id); + assert(!geary_id_map.values.contains(conversation)); - bool is_removed = conversations.remove((ImplConversation) conversation); + bool is_removed = conversations.remove(conversation); if (is_removed) { debug("Removing Email ID %s evaporates conversation %s", removed_id.to_string(), conversation.to_string()); @@ -986,22 +1014,32 @@ public class Geary.ConversationMonitor : BaseObject { debug("WARNING: Conversation %s already removed from master list (Email ID %s)", conversation.to_string(), removed_id.to_string()); } + + conversation.clear_owner(); } - - conversation.clear_owner(); } // for Conversations that have been removed, don't notify they're trimmed - foreach (Conversation conversation in removed) + foreach (ImplConversation conversation in removed) trimmed.remove_all(conversation); - foreach (Conversation conversation in trimmed.get_keys()) { + foreach (ImplConversation conversation in trimmed.get_keys()) { foreach (Email email in trimmed.get(conversation)) notify_conversation_trimmed(conversation, email); } foreach (Conversation conversation in removed) notify_conversation_removed(conversation); + + // For any still-existing conversations that we've trimmed messages + // from, do a search for any messages that should still be there due to + // full conversations. This way, some removed messages are instead + // "demoted" to out-of-folder emails. This is kind of inefficient, but + // it doesn't seem like there's a way around it. + Gee.HashSet search_message_ids = new Gee.HashSet(); + foreach (ImplConversation conversation in trimmed.get_keys()) + search_message_ids.add_all(conversation.message_ids); + yield expand_conversations_async(search_message_ids, new ProcessJobContext(false)); } private void on_folder_email_flags_changed(Gee.Map map) { @@ -1022,7 +1060,7 @@ public class Geary.ConversationMonitor : BaseObject { private void on_folder_email_count_changed(int new_count, Geary.Folder.CountChangeReason reason) { // Only trap INSERTED here because append/remove is handled above. if ((reason & Geary.Folder.CountChangeReason.INSERTED) != 0) - operation_queue.add(new FillWindowOperation(this)); + operation_queue.add(new FillWindowOperation(this, true)); } private Geary.EmailIdentifier? get_lowest_email_id() { @@ -1167,14 +1205,14 @@ public class Geary.ConversationMonitor : BaseObject { /** * Attempts to load enough conversations to fill min_window_count. */ - private async void fill_window_async() { + private async void fill_window_async(bool is_insert) { if (!is_monitoring || min_window_count <= conversations.size) return; int initial_message_count = geary_id_map.size; Geary.EmailIdentifier? low_id = get_lowest_email_id(); - if (low_id != null) { + if (low_id != null && !is_insert) { // Load at least as many messages as remianing conversations. int num_to_load = min_window_count - conversations.size; if (num_to_load < WINDOW_FILL_MESSAGE_COUNT) @@ -1187,7 +1225,8 @@ public class Geary.ConversationMonitor : BaseObject { debug("Error filling conversation window: %s", e.message); } } else { - // No existing messages, need to start from scratch. + // No existing messages or an insert invalidated our existing list, + // need to start from scratch. try { yield load_async(-1, min_window_count, Folder.ListFlags.NONE, cancellable_monitor); } catch(Error e) { @@ -1197,7 +1236,7 @@ public class Geary.ConversationMonitor : BaseObject { // Run again to make sure we're full unless we ran out of messages. if (geary_id_map.size != initial_message_count) - operation_queue.add(new FillWindowOperation(this)); + operation_queue.add(new FillWindowOperation(this, is_insert)); } } diff --git a/src/engine/api/geary-search-folder.vala b/src/engine/api/geary-search-folder.vala index eb041199..3d0e1ce3 100644 --- a/src/engine/api/geary-search-folder.vala +++ b/src/engine/api/geary-search-folder.vala @@ -101,7 +101,7 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder { // list_email_async() etc., but this leads to some more // complications when redoing the search. Gee.Collection? _new_results = yield account.local_search_async( - keywords, Geary.Email.Field.PROPERTIES, false, MAX_RESULT_EMAILS, 0, + keywords, Geary.Email.Field.PROPERTIES, false, get_path(), MAX_RESULT_EMAILS, 0, exclude_folders, null, cancellable); if (_new_results == null) { @@ -176,6 +176,9 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder { public override async Gee.List? list_email_async(int low, int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { + if (low >= 0) + error("Search folder can't list email positionally"); + // TODO: // * This is a temporary implementation that can't handle positional addressing. // * Fetch emails as a batch, not one at a time. @@ -207,9 +210,46 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder { public override async Gee.List? list_email_by_id_async(Geary.EmailIdentifier initial_id, int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { - // TODO: This method is not currently called, but is required by the interface. Before completing - // this feature, it should either be implemented either here or in AbstractLocalFolder. - error("Search folder does not implement list_email_by_id_async"); + // TODO: as above, this is incomplete and inefficient. + int result_mutex_token = yield result_mutex.claim_async(); + + Geary.EmailIdentifier[] ids = new Geary.EmailIdentifier[search_results.size]; + int initial_index = -1; + int i = 0; + foreach (Geary.Email email in search_results) { + if (email.id.equal_to(initial_id)) + initial_index = i; + ids[i++] = email.id; + } + + Gee.List results = new Gee.ArrayList(); + Error? error = null; + if (initial_index >= 0) { + try { + // A negative count means we walk forwards in our array and + // vice versa. + int real_count = count.abs(); + int increment = (count < 0 ? 1 : -1); + i = initial_index; + if ((flags & Folder.ListFlags.EXCLUDING_ID) != 0) + i += increment; + else + ++real_count; + int end = i + real_count * increment; + + for (; i >= 0 && i < search_results.size && i != end; i += increment) + results.add(yield fetch_email_async(ids[i], required_fields, flags, cancellable)); + } catch (Error e) { + error = e; + } + } + + result_mutex.release(ref result_mutex_token); + + if (error != null) + throw error; + + return (results.size == 0 ? null : results); } public override async Gee.List? list_email_by_sparse_id_async( diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala index b74151e6..341c85da 100644 --- a/src/engine/imap-db/imap-db-account.vala +++ b/src/engine/imap-db/imap-db-account.vala @@ -525,7 +525,7 @@ private class Geary.ImapDB.Account : BaseObject { // Ignore any messages that don't have the required fields. if (partial_ok || row.fields.fulfills(requested_fields)) { - Geary.Email email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id)); + Geary.Email email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id, null)); Geary.ImapDB.Folder.do_add_attachments(cx, email, id, cancellable); Gee.Set? folders = do_find_email_folders(cx, id, cancellable); @@ -619,8 +619,8 @@ private class Geary.ImapDB.Account : BaseObject { } public async Gee.Collection? search_async(string prepared_query, - Geary.Email.Field requested_fields, bool partial_ok, int limit = 100, int offset = 0, - Gee.Collection? folder_blacklist = null, + Geary.Email.Field requested_fields, bool partial_ok, Geary.FolderPath? email_id_folder_path, + int limit = 100, int offset = 0, Gee.Collection? folder_blacklist = null, Gee.Collection? search_ids = null, Cancellable? cancellable = null) throws Error { Gee.Collection search_results = new Gee.HashSet(); @@ -655,7 +655,8 @@ private class Geary.ImapDB.Account : BaseObject { cx, id, requested_fields, cancellable); if (partial_ok || row.fields.fulfills(requested_fields)) { - Geary.Email email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id)); + Geary.Email email = row.to_email(-1, + new Geary.ImapDB.EmailIdentifier(id, email_id_folder_path)); Geary.ImapDB.Folder.do_add_attachments(cx, email, id, cancellable); search_results.add(email); } @@ -739,7 +740,8 @@ private class Geary.ImapDB.Account : BaseObject { "Message %s only fulfills %Xh fields (required: %Xh)", email_id.to_string(), row.fields, required_fields); - email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(email_id.ordering)); + email = row.to_email(-1, + new Geary.ImapDB.EmailIdentifier(email_id.ordering, email_id.get_folder_path())); Geary.ImapDB.Folder.do_add_attachments(cx, email, email_id.ordering, cancellable); return Db.TransactionOutcome.DONE; @@ -822,7 +824,7 @@ private class Geary.ImapDB.Account : BaseObject { MessageRow row = Geary.ImapDB.Folder.do_fetch_message_row( cx, id, search_fields, cancellable); - Geary.Email email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id)); + Geary.Email email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id, null)); Geary.ImapDB.Folder.do_add_attachments(cx, email, id, cancellable); Geary.ImapDB.Folder.do_add_email_to_search_table(cx, id, email, cancellable); diff --git a/src/engine/imap-db/imap-db-email-identifier.vala b/src/engine/imap-db/imap-db-email-identifier.vala index c1f4c39e..3cc585ce 100644 --- a/src/engine/imap-db/imap-db-email-identifier.vala +++ b/src/engine/imap-db/imap-db-email-identifier.vala @@ -5,8 +5,16 @@ */ private class Geary.ImapDB.EmailIdentifier : Geary.EmailIdentifier { - public EmailIdentifier(int64 message_id) { + public Geary.FolderPath? folder_path { get; private set; } + + public EmailIdentifier(int64 message_id, Geary.FolderPath? folder_path) { base (message_id); + + this.folder_path = folder_path; + } + + public override Geary.FolderPath? get_folder_path() { + return folder_path; } public override bool equal_to(Geary.EmailIdentifier o) { diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala b/src/engine/imap-engine/imap-engine-generic-account.vala index df709168..e8f4c16c 100644 --- a/src/engine/imap-engine/imap-engine-generic-account.vala +++ b/src/engine/imap-engine/imap-engine-generic-account.vala @@ -478,8 +478,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { } public override async Gee.Collection? local_search_async(string keywords, - Geary.Email.Field requested_fields, bool partial_ok, int limit = 100, int offset = 0, - Gee.Collection? folder_blacklist = null, + Geary.Email.Field requested_fields, bool partial_ok, Geary.FolderPath? email_id_folder_path, + int limit = 100, int offset = 0, Gee.Collection? folder_blacklist = null, Gee.Collection? search_ids = null, Cancellable? cancellable = null) throws Error { if (offset < 0) throw new EngineError.BAD_PARAMETERS("Offset must not be negative"); @@ -487,7 +487,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { previous_prepared_search_query = local.prepare_search_query(keywords); return yield local.search_async(local.prepare_search_query(keywords), - requested_fields, partial_ok, limit, offset, folder_blacklist, search_ids, cancellable); + requested_fields, partial_ok, email_id_folder_path, limit, offset, + folder_blacklist, search_ids, cancellable); } public override async Gee.Collection? get_search_keywords_async(