[geary/wip/713611-conversation-message-delete: 8/10] Substantially clean up source and API for main Conversation-related classes.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/713611-conversation-message-delete: 8/10] Substantially clean up source and API for main Conversation-related classes.
- Date: Fri, 15 Dec 2017 04:12:32 +0000 (UTC)
commit 07c1cb6da8e98dfa1c67ed1ccfd81f2ec5d8ddf3
Author: Michael James Gratton <mike vee net>
Date: Thu Dec 7 15:49:35 2017 +1100
Substantially clean up source and API for main Conversation-related classes.
src/client/application/geary-controller.vala | 4 +-
.../conversation-list/conversation-list-store.vala | 10 +-
.../conversation-viewer/conversation-list-box.vala | 2 +-
src/engine/app/app-conversation-monitor.vala | 829 ++++++++++---------
src/engine/app/app-conversation.vala | 176 +++--
.../conversation-monitor/app-conversation-set.vala | 327 ++++----
test/engine/app/app-conversation-set-test.vala | 178 +----
test/engine/app/app-conversation-test.vala | 10 +-
8 files changed, 743 insertions(+), 793 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 8f2fa50..65cd6b4 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -329,13 +329,13 @@ public class GearyController : Geary.BaseObject {
// Close the ConversationMonitor
if (current_conversations != null) {
debug("Stopping conversation monitor for %s...",
- this.current_conversations.folder.to_string());
+ this.current_conversations.base_folder.to_string());
try {
yield this.current_conversations.stop_monitoring_async(null);
} catch (Error err) {
debug(
"Error closing conversation monitor %s at shutdown: %s",
- this.current_conversations.folder.to_string(),
+ this.current_conversations.base_folder.to_string(),
err.message
);
} finally {
diff --git a/src/client/conversation-list/conversation-list-store.vala
b/src/client/conversation-list/conversation-list-store.vala
index c3fc1dd..695e1ed 100644
--- a/src/client/conversation-list/conversation-list-store.vala
+++ b/src/client/conversation-list/conversation-list-store.vala
@@ -106,7 +106,7 @@ public class ConversationListStore : Gtk.ListStore {
Priority.LOW, 60, update_date_strings
);
this.email_store = new Geary.App.EmailStore(
- conversations.folder.account
+ conversations.base_folder.account
);
GearyApplication.instance.config.settings.changed[Configuration.DISPLAY_PREVIEW_KEY].connect(
on_display_preview_changed);
@@ -182,7 +182,7 @@ public class ConversationListStore : Gtk.ListStore {
if (emails.size < 1)
return;
- debug("Displaying %d previews for %s...", emails.size, conversation_monitor.folder.to_string());
+ debug("Displaying %d previews for %s...", emails.size, conversation_monitor.base_folder.to_string());
foreach (Geary.Email email in emails) {
Geary.App.Conversation? conversation = conversation_monitor.get_conversation_for_email(email.id);
if (conversation != null)
@@ -190,7 +190,7 @@ public class ConversationListStore : Gtk.ListStore {
else
debug("Couldn't find conversation for %s", email.id.to_string());
}
- debug("Displayed %d previews for %s", emails.size, conversation_monitor.folder.to_string());
+ debug("Displayed %d previews for %s", emails.size, conversation_monitor.base_folder.to_string());
}
private async Gee.Collection<Geary.Email> do_get_previews_async(
@@ -278,8 +278,8 @@ public class ConversationListStore : Gtk.ListStore {
FormattedConversationData conversation_data = new FormattedConversationData(
conversation,
preview,
- this.conversations.folder,
- this.conversations.folder.account.information.get_all_mailboxes()
+ this.conversations.base_folder,
+ this.conversations.base_folder.account.information.get_all_mailboxes()
);
Gtk.TreePath? path = get_path(iter);
diff --git a/src/client/conversation-viewer/conversation-list-box.vala
b/src/client/conversation-viewer/conversation-list-box.vala
index 48f1d74..241d24a 100644
--- a/src/client/conversation-viewer/conversation-list-box.vala
+++ b/src/client/conversation-viewer/conversation-list-box.vala
@@ -860,7 +860,7 @@ public class ConversationListBox : Gtk.ListBox {
// Should be able to edit draft emails from any
// conversation. This test should be more like "is in drafts
// folder"
- bool is_in_folder = this.conversation.is_in_current_folder(email.id);
+ bool is_in_folder = this.conversation.is_in_base_folder(email.id);
bool is_draft = (this.is_draft_folder && is_in_folder);
bool is_sent = false;
diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala
index 47f525d..f607317 100644
--- a/src/engine/app/app-conversation-monitor.vala
+++ b/src/engine/app/app-conversation-monitor.vala
@@ -11,32 +11,41 @@ public class Geary.App.ConversationMonitor : BaseObject {
*/
public const Geary.Email.Field REQUIRED_FIELDS = Geary.Email.Field.REFERENCES |
Geary.Email.Field.FLAGS | Geary.Email.Field.DATE;
-
+
// # of messages to load at a time as we attempt to fill the min window.
private const int WINDOW_FILL_MESSAGE_COUNT = 5;
-
+
private class ProcessJobContext : BaseObject {
public Gee.HashMap<Geary.EmailIdentifier, Geary.Email> emails
= new Gee.HashMap<Geary.EmailIdentifier, Geary.Email>();
-
+
public bool inside_scan;
-
+
public ProcessJobContext(bool inside_scan) {
this.inside_scan = inside_scan;
}
}
-
- public Geary.Folder folder { get; private set; }
+
+
+ /** Folder from which the conversation is originating. */
+ public Folder base_folder { get; private set; }
+
+ /** Determines if this monitor is monitoring the base folder. */
public bool is_monitoring { get; private set; default = false; }
+
+ /** Minimum number of emails large conversations should contain. */
public int min_window_count { get { return _min_window_count; }
set {
_min_window_count = value;
operation_queue.add(new FillWindowOperation(this, false));
}
}
-
- public Geary.ProgressMonitor progress_monitor { get { return operation_queue.progress_monitor; } }
-
+
+ /** Indicates process loading conversations. */
+ public Geary.ProgressMonitor progress_monitor {
+ get { return operation_queue.progress_monitor; }
+ }
+
private ConversationSet conversations = new ConversationSet();
private Geary.Email.Field required_fields;
private Geary.Folder.OpenFlags open_flags;
@@ -44,24 +53,27 @@ public class Geary.App.ConversationMonitor : BaseObject {
private bool reseed_notified = false;
private int _min_window_count = 0;
private ConversationOperationQueue operation_queue = new ConversationOperationQueue();
-
+
+
/**
* "monitoring-started" is fired when the Conversations folder has been opened for monitoring.
*/
public virtual signal void monitoring_started() {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::monitoring_started",
- folder.to_string());
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::monitoring_started",
+ this.base_folder.to_string());
}
-
+
/**
* "monitoring-stopped" is fired when the Geary.Folder object has closed (either due to error
* or user) and the Conversations object is therefore unable to continue monitoring.
*/
public virtual signal void monitoring_stopped() {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::monitoring_stopped",
- folder.to_string());
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::monitoring_stopped",
+ this.base_folder.to_string());
}
-
+
/**
* "scan-started" is fired whenever beginning to load messages into the Conversations object.
*
@@ -71,44 +83,49 @@ public class Geary.App.ConversationMonitor : BaseObject {
* only a single "scan-completed" is fired to indicate multiple loads have finished.
*/
public virtual signal void scan_started() {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::scan_started",
- folder.to_string());
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::scan_started",
+ this.base_folder.to_string());
}
-
+
/**
* "scan-error" is fired when an Error is encounted while loading messages. It will be followed
* by a "scan-completed" signal.
*/
public virtual signal void scan_error(Error err) {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::scan_error %s",
- folder.to_string(), err.message);
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::scan_error %s",
+ this.base_folder.to_string(), err.message);
}
-
+
/**
* "scan-completed" is fired when the scan of the email has finished.
*/
public virtual signal void scan_completed() {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::scan_completed",
- folder.to_string());
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::scan_completed",
+ this.base_folder.to_string());
}
-
+
/**
* "seed-completed" is fired when the folder has opened and email has been populated.
*/
public virtual signal void seed_completed() {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::seed_completed",
- folder.to_string());
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::seed_completed",
+ this.base_folder.to_string());
}
-
+
/**
* "conversations-added" indicates that one or more new Conversations have been detected while
* processing email, either due to a user-initiated load request or due to monitoring.
*/
public virtual signal void conversations_added(Gee.Collection<Conversation> conversations) {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::conversations_added %d",
- folder.to_string(), conversations.size);
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::conversations_added %d",
+ this.base_folder.to_string(), conversations.size);
}
-
+
/**
* "conversations-removed" is fired when all the email in a Conversation has been removed.
* It's possible this will be called without a signal alerting that it's emails have been
@@ -119,21 +136,23 @@ public class Geary.App.ConversationMonitor : BaseObject {
* user call to manually remove email from Conversations.
*/
public virtual signal void conversations_removed(Gee.Collection<Conversation> conversations) {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::conversations_removed %d",
- folder.to_string(), conversations.size);
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::conversations_removed %d",
+ this.base_folder.to_string(), conversations.size);
}
-
+
/**
* "conversation-appended" is fired when one or more Email objects have been added to the
* specified Conversation. This can happen due to a user-initiated load or while monitoring
* the Folder.
*/
public virtual signal void conversation_appended(Conversation conversation,
- Gee.Collection<Geary.Email> email) {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::conversation_appended",
- folder.to_string());
+ Gee.Collection<Email> email) {
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::conversation_appended",
+ this.base_folder.to_string());
}
-
+
/**
* "conversation-trimmed" is fired when one or more Emails have been removed from the Folder,
* and therefore from the specified Conversation. If the trimmed Email is the last usable
@@ -145,11 +164,12 @@ public class Geary.App.ConversationMonitor : BaseObject {
* This is only called when monitoring is enabled.
*/
public virtual signal void conversation_trimmed(Conversation conversation,
- Gee.Collection<Geary.Email> email) {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::conversation_trimmed",
- folder.to_string());
+ Gee.Collection<Email> email) {
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::conversation_trimmed",
+ this.base_folder.to_string());
}
-
+
/**
* "email-flags-changed" is fired when the flags of an email in a conversation have changed,
* as reported by the monitored folder. The local copy of the Email is updated and this
@@ -159,104 +179,63 @@ public class Geary.App.ConversationMonitor : BaseObject {
* is fired. To know of all changes to all flags, subscribe to the Geary.Folder's
* "email-flags-changed" signal.
*/
- public virtual signal void email_flags_changed(Conversation conversation, Geary.Email email) {
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::email_flag_changed",
- folder.to_string());
+ public virtual signal void email_flags_changed(Conversation conversation,
+ Email email) {
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::email_flag_changed",
+ this.base_folder.to_string());
}
-
+
/**
* Creates a conversation monitor for the given folder.
*
- * @param folder Folder to monitor
+ * @param base_folder the Folder to monitor
* @param open_flags See {@link Geary.Folder}
* @param required_fields See {@link Geary.Folder}
* @param min_window_count Minimum number of conversations that will be loaded
*/
- public ConversationMonitor(Geary.Folder folder, Geary.Folder.OpenFlags open_flags,
- Geary.Email.Field required_fields, int min_window_count) {
- this.folder = folder;
+ public ConversationMonitor(Folder base_folder,
+ Folder.OpenFlags open_flags,
+ Email.Field required_fields,
+ int min_window_count) {
+ this.base_folder = base_folder;
this.open_flags = open_flags;
this.required_fields = required_fields | REQUIRED_FIELDS;
- _min_window_count = min_window_count;
- }
-
- protected virtual void notify_monitoring_started() {
- monitoring_started();
- }
-
- protected virtual void notify_monitoring_stopped() {
- monitoring_stopped();
- }
-
- protected virtual void notify_scan_started() {
- scan_started();
- }
-
- protected virtual void notify_scan_error(Error err) {
- scan_error(err);
- }
-
- protected virtual void notify_scan_completed() {
- scan_completed();
- }
-
- protected virtual void notify_seed_completed() {
- seed_completed();
- }
-
- protected virtual void notify_conversations_added(Gee.Collection<Conversation> conversations) {
- conversations_added(conversations);
- }
-
- protected virtual void notify_conversations_removed(Gee.Collection<Conversation> conversations) {
- conversations_removed(conversations);
- }
-
- protected virtual void notify_conversation_appended(Conversation conversation,
- Gee.Collection<Geary.Email> emails) {
- conversation_appended(conversation, emails);
- }
-
- protected virtual void notify_conversation_trimmed(Conversation conversation,
- Gee.Collection<Geary.Email> emails) {
- conversation_trimmed(conversation, emails);
- }
-
- protected virtual void notify_email_flags_changed(Conversation conversation, Geary.Email email) {
- conversation.email_flags_changed(email);
- email_flags_changed(conversation, email);
+ this._min_window_count = min_window_count;
}
public int get_conversation_count() {
return conversations.size;
}
-
+
public Gee.Collection<Conversation> get_conversations() {
return conversations.conversations;
}
-
+
public Geary.App.Conversation? get_conversation_for_email(Geary.EmailIdentifier email_id) {
return conversations.get_by_email_identifier(email_id);
}
-
+
public async bool start_monitoring_async(Cancellable? cancellable = null)
throws Error {
if (is_monitoring)
return false;
-
+
// set before yield to guard against reentrancy
is_monitoring = true;
-
+
cancellable_monitor = cancellable;
-
+
// Double check that the last run of the queue got stopped and that
// it's empty.
if (operation_queue.is_processing)
yield operation_queue.stop_processing_async(cancellable_monitor);
operation_queue.clear();
-
- bool reseed_now = (folder.get_open_state() != Geary.Folder.OpenState.CLOSED);
-
+
+ bool reseed_now = (
+ this.base_folder.get_open_state() != Geary.Folder.OpenState.CLOSED
+ );
+
// Add the necessary initial operations ahead of anything the folder
// might add as it opens.
operation_queue.add(new LocalLoadOperation(this));
@@ -266,51 +245,41 @@ public class Geary.App.ConversationMonitor : BaseObject {
operation_queue.add(new ReseedOperation(this, "already opened"));
operation_queue.add(new FillWindowOperation(this, false));
- this.folder.email_appended.connect(on_folder_email_appended);
- this.folder.email_inserted.connect(on_folder_email_inserted);
- this.folder.email_removed.connect(on_folder_email_removed);
- this.folder.opened.connect(on_folder_opened);
- this.folder.account.email_appended.connect(on_account_email_appended);
- this.folder.account.email_inserted.connect(on_account_email_inserted);
- this.folder.account.email_removed.connect(on_account_email_removed);
- this.folder.account.email_flags_changed.connect(on_account_email_flags_changed);
+ this.base_folder.email_appended.connect(on_folder_email_appended);
+ this.base_folder.email_inserted.connect(on_folder_email_inserted);
+ this.base_folder.email_removed.connect(on_folder_email_removed);
+ this.base_folder.opened.connect(on_folder_opened);
+ this.base_folder.account.email_appended.connect(on_account_email_appended);
+ this.base_folder.account.email_inserted.connect(on_account_email_inserted);
+ this.base_folder.account.email_removed.connect(on_account_email_removed);
+ this.base_folder.account.email_flags_changed.connect(on_account_email_flags_changed);
try {
- yield folder.open_async(open_flags, cancellable);
+ yield this.base_folder.open_async(open_flags, cancellable);
} catch (Error err) {
is_monitoring = false;
- this.folder.email_appended.disconnect(on_folder_email_appended);
- this.folder.email_inserted.disconnect(on_folder_email_inserted);
- this.folder.email_removed.disconnect(on_folder_email_removed);
- this.folder.opened.disconnect(on_folder_opened);
- this.folder.account.email_appended.disconnect(on_account_email_appended);
- this.folder.account.email_inserted.disconnect(on_account_email_inserted);
- this.folder.account.email_removed.disconnect(on_account_email_removed);
- this.folder.account.email_flags_changed.disconnect(on_account_email_flags_changed);
+ this.base_folder.email_appended.disconnect(on_folder_email_appended);
+ this.base_folder.email_inserted.disconnect(on_folder_email_inserted);
+ this.base_folder.email_removed.disconnect(on_folder_email_removed);
+ this.base_folder.opened.disconnect(on_folder_opened);
+ this.base_folder.account.email_appended.disconnect(on_account_email_appended);
+ this.base_folder.account.email_inserted.disconnect(on_account_email_inserted);
+ this.base_folder.account.email_removed.disconnect(on_account_email_removed);
+ this.base_folder.account.email_flags_changed.disconnect(on_account_email_flags_changed);
throw err;
}
-
+
notify_monitoring_started();
reseed_notified = false;
-
+
// Process operations in the background.
operation_queue.run_process_async.begin();
-
+
return true;
}
-
- internal async void local_load_async() {
- debug("ConversationMonitor seeding with local email for %s", folder.to_string());
- try {
- yield load_by_id_async(null, min_window_count, Folder.ListFlags.LOCAL_ONLY, cancellable_monitor);
- } catch (Error e) {
- debug("Error loading local messages: %s", e.message);
- }
- debug("ConversationMonitor seeded for %s", folder.to_string());
- }
-
+
/**
* Halt monitoring of the Folder and, if specified, close it. Note that the Cancellable
* supplied to start_monitoring_async() is used during monitoring but *not* for this method.
@@ -323,42 +292,258 @@ public class Geary.App.ConversationMonitor : BaseObject {
public async bool stop_monitoring_async(Cancellable? cancellable) throws Error {
if (!is_monitoring)
return false;
-
+
yield operation_queue.stop_processing_async(cancellable);
-
+
// set now to prevent reentrancy during yield or signal
is_monitoring = false;
- this.folder.email_appended.disconnect(on_folder_email_appended);
- this.folder.email_inserted.disconnect(on_folder_email_inserted);
- this.folder.email_removed.disconnect(on_folder_email_removed);
- this.folder.opened.disconnect(on_folder_opened);
- this.folder.account.email_appended.disconnect(on_account_email_appended);
- this.folder.account.email_inserted.disconnect(on_account_email_inserted);
- this.folder.account.email_removed.disconnect(on_account_email_removed);
- this.folder.account.email_flags_changed.disconnect(on_account_email_flags_changed);
+ this.base_folder.email_appended.disconnect(on_folder_email_appended);
+ this.base_folder.email_inserted.disconnect(on_folder_email_inserted);
+ this.base_folder.email_removed.disconnect(on_folder_email_removed);
+ this.base_folder.opened.disconnect(on_folder_opened);
+ this.base_folder.account.email_appended.disconnect(on_account_email_appended);
+ this.base_folder.account.email_inserted.disconnect(on_account_email_inserted);
+ this.base_folder.account.email_removed.disconnect(on_account_email_removed);
+ this.base_folder.account.email_flags_changed.disconnect(on_account_email_flags_changed);
bool closing = false;
Error? close_err = null;
try {
- closing = yield folder.close_async(cancellable);
+ closing = yield this.base_folder.close_async(cancellable);
} catch (Error err) {
// throw, but only after cleaning up (which is to say, if close_async() fails,
// then the Folder is still treated as closed, which is the best that can be
// expected; it definitely shouldn't still be considered open).
- debug("Unable to close monitored folder %s: %s", folder.to_string(), err.message);
-
+ debug("Unable to close monitored folder %s: %s",
+ this.base_folder.to_string(), err.message);
close_err = err;
}
-
+
notify_monitoring_stopped();
-
+
if (close_err != null)
throw close_err;
-
+
return closing;
}
-
+
+ internal async void local_load_async() {
+ debug("ConversationMonitor seeding with local email for %s",
+ this.base_folder.to_string());
+ try {
+ yield load_by_id_async(null, min_window_count, Folder.ListFlags.LOCAL_ONLY, cancellable_monitor);
+ } catch (Error e) {
+ debug("Error loading local messages: %s", e.message);
+ }
+ debug("ConversationMonitor seeded for %s",
+ this.base_folder.to_string());
+ }
+
+ internal async void append_emails_async(Gee.Collection<Geary.EmailIdentifier> appended_ids) {
+ debug("%d message(s) appended to %s, fetching to add to conversations...",
+ appended_ids.size, this.base_folder.to_string());
+
+ yield load_by_sparse_id(appended_ids, Geary.Folder.ListFlags.NONE, null);
+ }
+
+ internal async void remove_emails_async(Folder source_folder,
+ Gee.Collection<EmailIdentifier> removed_ids) {
+ debug("%d messages(s) removed from %s, trimming/removing conversations...",
+ removed_ids.size, source_folder.to_string()
+ );
+
+ Gee.Collection<Conversation> removed;
+ Gee.MultiMap<Conversation, Email> trimmed;
+ conversations.remove_all_emails_by_identifier(
+ source_folder.path,
+ removed_ids,
+ out removed,
+ out trimmed
+ );
+
+ // Check for conversations that have been evaporated as a
+ // result, update removed and trimmed collections to reflect
+ // any that evaporated
+ try {
+ Gee.Collection<Conversation> evaporated = yield check_conversations_in_base_folder(
+ trimmed.get_keys(), null
+ );
+ removed.add_all(evaporated);
+ foreach (Conversation target in evaporated) {
+ trimmed.remove_all(target);
+ }
+ } catch (Error err) {
+ debug("Error checking conversation for messages in %s: %s",
+ this.base_folder.path.to_string(), err.message);
+ }
+
+ // Fire signals, clean up
+
+ foreach (Conversation conversation in trimmed.get_keys())
+ notify_conversation_trimmed(conversation, trimmed.get(conversation));
+
+ if (removed.size > 0)
+ notify_conversations_removed(removed);
+
+ if (source_folder == this.base_folder) {
+ // 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<RFC822.MessageID> search_message_ids = new Gee.HashSet<RFC822.MessageID>();
+ foreach (Conversation conversation in trimmed.get_keys()) {
+ search_message_ids.add_all(conversation.get_message_ids());
+ }
+ yield expand_conversations_async(search_message_ids, new ProcessJobContext(false));
+ }
+ }
+
+ internal async void external_append_emails_async(Folder folder,
+ Gee.Collection<EmailIdentifier> appended_ids) {
+ if (get_search_blacklist().contains(folder.path))
+ return;
+
+ if (conversations.is_empty)
+ return;
+
+ debug("%d out of folder message(s) appended to %s, fetching to add to conversations...",
appended_ids.size,
+ folder.to_string());
+
+ yield external_load_by_sparse_id(folder, appended_ids, Geary.Folder.ListFlags.NONE, null);
+ }
+
+ internal async void reseed_async(string why) {
+ Geary.EmailIdentifier? earliest_id = yield get_lowest_email_id_async(null);
+ try {
+ if (earliest_id != null) {
+ debug("ConversationMonitor (%s) reseeding starting from Email ID %s on opened %s", why,
+ earliest_id.to_string(), this.base_folder.to_string());
+ yield load_by_id_async(earliest_id, int.MAX,
+ Geary.Folder.ListFlags.OLDEST_TO_NEWEST | Geary.Folder.ListFlags.INCLUDING_ID,
+ cancellable_monitor);
+ } else {
+ debug("ConversationMonitor (%s) reseeding latest %d emails on opened %s", why,
+ min_window_count, this.base_folder.to_string());
+ yield load_by_id_async(null, min_window_count, Geary.Folder.ListFlags.NONE,
cancellable_monitor);
+ }
+ } catch (Error e) {
+ debug("Reseed error: %s", e.message);
+ }
+
+ if (!reseed_notified) {
+ reseed_notified = true;
+ notify_seed_completed();
+ }
+ }
+
+ /**
+ * Attempts to load enough conversations to fill min_window_count.
+ */
+ internal async void fill_window_async(bool is_insert) {
+ if (!is_monitoring)
+ return;
+
+ if (!is_insert && min_window_count <= conversations.size)
+ return;
+
+ int initial_message_count = conversations.get_email_count();
+
+ // only do local-load if the Folder isn't completely opened, otherwise this operation
+ // will block other (more important) operations while it waits for the folder to
+ // remote-open
+ Folder.ListFlags flags;
+ switch (this.base_folder.get_open_state()) {
+ case Folder.OpenState.CLOSED:
+ case Folder.OpenState.LOCAL:
+ case Folder.OpenState.OPENING:
+ flags = Folder.ListFlags.LOCAL_ONLY;
+ break;
+
+ case Folder.OpenState.BOTH:
+ case Folder.OpenState.REMOTE:
+ flags = Folder.ListFlags.NONE;
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ Geary.EmailIdentifier? low_id = yield get_lowest_email_id_async(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)
+ num_to_load = WINDOW_FILL_MESSAGE_COUNT;
+
+ try {
+ yield load_by_id_async(low_id, num_to_load, flags, cancellable_monitor);
+ } catch(Error e) {
+ debug("Error filling conversation window: %s", e.message);
+ }
+ } else {
+ // No existing messages or an insert invalidated our existing list,
+ // need to start from scratch.
+ try {
+ yield load_by_id_async(null, min_window_count, flags, cancellable_monitor);
+ } catch(Error e) {
+ debug("Error filling conversation window: %s", e.message);
+ }
+ }
+
+ // Run again to make sure we're full unless we ran out of messages.
+ if (conversations.get_email_count() != initial_message_count)
+ operation_queue.add(new FillWindowOperation(this, is_insert));
+ }
+
+ protected virtual void notify_monitoring_started() {
+ monitoring_started();
+ }
+
+ protected virtual void notify_monitoring_stopped() {
+ monitoring_stopped();
+ }
+
+ protected virtual void notify_scan_started() {
+ scan_started();
+ }
+
+ protected virtual void notify_scan_error(Error err) {
+ scan_error(err);
+ }
+
+ protected virtual void notify_scan_completed() {
+ scan_completed();
+ }
+
+ protected virtual void notify_seed_completed() {
+ seed_completed();
+ }
+
+ protected virtual void notify_conversations_added(Gee.Collection<Conversation> conversations) {
+ conversations_added(conversations);
+ }
+
+ protected virtual void notify_conversations_removed(Gee.Collection<Conversation> conversations) {
+ conversations_removed(conversations);
+ }
+
+ protected virtual void notify_conversation_appended(Conversation conversation,
+ Gee.Collection<Geary.Email> emails) {
+ conversation_appended(conversation, emails);
+ }
+
+ protected virtual void notify_conversation_trimmed(Conversation conversation,
+ Gee.Collection<Geary.Email> emails) {
+ conversation_trimmed(conversation, emails);
+ }
+
+ protected virtual void notify_email_flags_changed(Conversation conversation, Geary.Email email) {
+ conversation.email_flags_changed(email);
+ email_flags_changed(conversation, email);
+ }
+
/**
* See Geary.Folder.list_email_by_id_async() for details of how these parameters operate. Instead
* of returning emails, this method will load the Conversations object with them sorted into
@@ -368,42 +553,58 @@ public class Geary.App.ConversationMonitor : BaseObject {
Geary.Folder.ListFlags flags, Cancellable? cancellable) throws Error {
notify_scan_started();
try {
- yield process_email_async(yield folder.list_email_by_id_async(initial_id,
- count, required_fields, flags, cancellable), new ProcessJobContext(true));
+ yield process_email_async(
+ yield this.base_folder.list_email_by_id_async(
+ initial_id, count, required_fields, flags, cancellable
+ ),
+ new ProcessJobContext(true)
+ );
} catch (Error err) {
list_error(err);
throw err;
}
}
-
+
private async void load_by_sparse_id(Gee.Collection<Geary.EmailIdentifier> ids,
Geary.Folder.ListFlags flags, Cancellable? cancellable) {
notify_scan_started();
-
+
try {
- yield process_email_async(yield folder.list_email_by_sparse_id_async(ids,
- required_fields, flags, cancellable), new ProcessJobContext(true));
+ yield process_email_async(
+ yield this.base_folder.list_email_by_sparse_id_async(
+ ids, required_fields, flags, cancellable
+ ),
+ new ProcessJobContext(true)
+ );
} catch (Error err) {
list_error(err);
}
}
-
- private async void external_load_by_sparse_id(Geary.Folder folder,
- Gee.Collection<Geary.EmailIdentifier> ids, Geary.Folder.ListFlags flags, Cancellable? cancellable) {
+
+ /**
+ * Loads messages from outside the monitor's base folder.
+ *
+ * Since this requires opening and closing the other folder, it is
+ * handled separately.
+ */
+ private async void external_load_by_sparse_id(Folder folder,
+ Gee.Collection<EmailIdentifier> ids,
+ Folder.ListFlags flags,
+ Cancellable? cancellable) {
bool opened = false;
try {
yield folder.open_async(Geary.Folder.OpenFlags.NONE, cancellable);
opened = true;
-
+
debug("Listing %d external emails", ids.size);
-
+
// First just get the bare minimum we need to determine if we even
// care about the messages.
Gee.List<Geary.Email>? emails = yield folder.list_email_by_sparse_id_async(ids,
Geary.Email.Field.REFERENCES, flags, cancellable);
-
+
debug("List found %d emails", (emails == null ? 0 : emails.size));
-
+
Gee.HashSet<Geary.EmailIdentifier> relevant_ids = new Gee.HashSet<Geary.EmailIdentifier>();
foreach (Geary.Email email in emails) {
Gee.Set<RFC822.MessageID>? ancestors = email.get_ancestors();
@@ -411,16 +612,16 @@ public class Geary.App.ConversationMonitor : BaseObject {
Geary.traverse<RFC822.MessageID>(ancestors).any(id => conversations.has_message_id(id)))
relevant_ids.add(email.id);
}
-
+
debug("%d external emails are relevant to current conversations", relevant_ids.size);
-
+
// List the relevant messages again with the full set of fields, to
// make sure when we load them from the database we have all the
// data we need.
yield folder.list_email_by_sparse_id_async(relevant_ids, required_fields, flags, cancellable);
yield folder.close_async(cancellable);
opened = false;
-
+
Gee.ArrayList<Geary.Email> search_emails = new Gee.ArrayList<Geary.Email>();
foreach (Geary.EmailIdentifier id in relevant_ids) {
// TODO: parallelize this.
@@ -432,9 +633,9 @@ public class Geary.App.ConversationMonitor : BaseObject {
debug("Error fetching out of folder message: %s", e.message);
}
}
-
+
debug("Fetched %d relevant emails locally", search_emails.size);
-
+
yield process_email_async(search_emails, new ProcessJobContext(false));
} catch (Error e) {
debug("Error loading external emails: %s", e.message);
@@ -447,27 +648,29 @@ public class Geary.App.ConversationMonitor : BaseObject {
}
}
}
-
+
private void list_error(Error err) {
- debug("Error while assembling conversations in %s: %s", folder.to_string(), err.message);
+ debug("Error while assembling conversations in %s: %s",
+ this.base_folder.to_string(), err.message);
notify_scan_error(err);
notify_scan_completed();
}
-
+
private async void process_email_async(Gee.Collection<Geary.Email>? emails, ProcessJobContext job) {
if (emails == null || emails.size == 0) {
yield process_email_complete_async(job);
return;
}
-
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::process_email: %d emails",
- folder.to_string(), emails.size);
-
+
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::process_email: %d emails",
+ this.base_folder.to_string(), emails.size);
+
Gee.HashSet<RFC822.MessageID> new_message_ids = new Gee.HashSet<RFC822.MessageID>();
foreach (Geary.Email email in emails) {
if (!job.emails.has_key(email.id)) {
job.emails.set(email.id, email);
-
+
Gee.Set<RFC822.MessageID>? ancestors = email.get_ancestors();
if (ancestors != null) {
Geary.traverse<RFC822.MessageID>(ancestors)
@@ -476,77 +679,78 @@ public class Geary.App.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_async(new_message_ids, job);
-
- Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::process_email completed: %d
emails",
- folder.to_string(), emails.size);
+
+ Logging.debug(Logging.Flag.CONVERSATIONS,
+ "[%s] ConversationMonitor::process_email completed: %d emails",
+ this.base_folder.to_string(), emails.size);
}
-
+
private Gee.Collection<Geary.FolderPath> get_search_blacklist() {
Geary.SpecialFolderType[] blacklisted_folder_types = {
Geary.SpecialFolderType.SPAM,
Geary.SpecialFolderType.TRASH,
Geary.SpecialFolderType.DRAFTS,
};
-
+
Gee.ArrayList<Geary.FolderPath?> blacklist = new Gee.ArrayList<Geary.FolderPath?>();
foreach (Geary.SpecialFolderType type in blacklisted_folder_types) {
try {
- Geary.Folder? blacklist_folder = folder.account.get_special_folder(type);
+ Geary.Folder? blacklist_folder = this.base_folder.account.get_special_folder(type);
if (blacklist_folder != null)
blacklist.add(blacklist_folder.path);
} catch (Error e) {
debug("Error finding special folder %s on account %s: %s",
- type.to_string(), folder.account.to_string(), e.message);
+ type.to_string(), this.base_folder.account.to_string(), e.message);
}
}
-
+
// Add "no folders" so we omit results that have been deleted permanently from the server.
blacklist.add(null);
-
+
return blacklist;
}
-
+
private Geary.EmailFlags get_search_flag_blacklist() {
Geary.EmailFlags flags = new Geary.EmailFlags();
flags.add(Geary.EmailFlags.DRAFT);
-
+
return flags;
}
-
+
private async void expand_conversations_async(Gee.Set<RFC822.MessageID> needed_message_ids,
ProcessJobContext job) {
if (needed_message_ids.size == 0) {
yield process_email_complete_async(job);
return;
}
-
+
Logging.debug(Logging.Flag.CONVERSATIONS,
- "[%s] ConversationMonitor::expand_conversations: %d email ids",
- folder.to_string(), needed_message_ids.size);
-
+ "[%s] ConversationMonitor::expand_conversations: %d email ids",
+ this.base_folder.to_string(), needed_message_ids.size);
+
Gee.Collection<Geary.FolderPath> folder_blacklist = get_search_blacklist();
Geary.EmailFlags flag_blacklist = get_search_flag_blacklist();
-
+
// execute all the local search operations at once
Nonblocking.Batch batch = new Nonblocking.Batch();
foreach (RFC822.MessageID message_id in needed_message_ids) {
- batch.add(new LocalSearchOperation(folder.account, message_id, required_fields,
+ batch.add(new LocalSearchOperation(this.base_folder.account, message_id, required_fields,
folder_blacklist, flag_blacklist));
}
-
+
try {
yield batch.execute_all_async();
} catch (Error err) {
debug("Unable to search local mail for conversations: %s", err.message);
-
+
yield process_email_complete_async(job);
return;
}
-
+
// collect their results into a single collection of addt'l emails
Gee.HashMap<Geary.EmailIdentifier, Geary.Email> needed_messages = new Gee.HashMap<
Geary.EmailIdentifier, Geary.Email>();
@@ -558,16 +762,16 @@ public class Geary.App.ConversationMonitor : BaseObject {
.add_all_to_map<Geary.EmailIdentifier>(needed_messages, e => e.id);
}
}
-
+
// 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);
-
+
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);
+ "[%s] ConversationMonitor::expand_conversations completed: %d email ids (%d found)",
+ this.base_folder.to_string(), needed_message_ids.size, needed_messages.size);
}
-
+
private async void process_email_complete_async(ProcessJobContext job) {
Gee.Collection<Geary.App.Conversation>? added = null;
Gee.MultiMap<Geary.App.Conversation, Geary.Email>? appended = null;
@@ -575,215 +779,48 @@ public class Geary.App.ConversationMonitor : BaseObject {
try {
// Get known paths for all emails
Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath>? email_paths =
- yield this.folder.account.get_containing_folders_async(
+ yield this.base_folder.account.get_containing_folders_async(
job.emails.keys, null
);
// Add them to the conversation set
- yield this.conversations.add_all_emails_async(
- job.emails.values,
- email_paths,
- this.folder,
- out added,
- out appended,
- out removed_due_to_merge,
- null);
+ this.conversations.add_all_emails(
+ job.emails.values, email_paths, this.base_folder,
+ out added, out appended, out removed_due_to_merge
+ );
} catch (Error err) {
debug("Unable to add emails to conversation: %s", err.message);
-
+
// fall-through
}
-
+
if (removed_due_to_merge != null && removed_due_to_merge.size > 0) {
notify_conversations_removed(removed_due_to_merge);
}
-
+
if (added != null && added.size > 0)
notify_conversations_added(added);
-
+
if (appended != null) {
foreach (Geary.App.Conversation conversation in appended.get_keys())
notify_conversation_appended(conversation, appended.get(conversation));
}
-
+
if (job.inside_scan)
notify_scan_completed();
}
- internal async void append_emails_async(Gee.Collection<Geary.EmailIdentifier> appended_ids) {
- debug("%d message(s) appended to %s, fetching to add to conversations...", appended_ids.size,
- folder.to_string());
-
- yield load_by_sparse_id(appended_ids, Geary.Folder.ListFlags.NONE, null);
- }
-
- internal async void remove_emails_async(Folder source_folder,
- Gee.Collection<EmailIdentifier> removed_ids) {
- debug("%d messages(s) removed from %s, trimming/removing conversations...",
- removed_ids.size, source_folder.to_string()
- );
-
- Gee.Collection<Conversation> removed;
- Gee.MultiMap<Conversation, Email> trimmed;
- conversations.remove_all_emails_by_identifier(
- source_folder.path,
- removed_ids,
- out removed,
- out trimmed
- );
-
- // Check for conversations that have been evaporated as a
- // result, update removed and trimmed collections to reflect
- // any that evaporated
- try {
- Gee.Collection<Conversation> evaporated = yield check_conversations_in_base_folder(
- trimmed.get_keys(), null
- );
- removed.add_all(evaporated);
- foreach (Conversation target in evaporated) {
- trimmed.remove_all(target);
- }
- } catch (Error err) {
- debug("Error checking conversation for messages in %s: %s",
- this.folder.path.to_string(), err.message);
- }
-
- // Fire signals, clean up
-
- foreach (Conversation conversation in trimmed.get_keys())
- notify_conversation_trimmed(conversation, trimmed.get(conversation));
-
- if (removed.size > 0)
- notify_conversations_removed(removed);
-
- if (source_folder == this.folder) {
- // 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<RFC822.MessageID> search_message_ids = new Gee.HashSet<RFC822.MessageID>();
- foreach (Conversation conversation in trimmed.get_keys()) {
- search_message_ids.add_all(conversation.get_message_ids());
- }
- yield expand_conversations_async(search_message_ids, new ProcessJobContext(false));
- }
- }
-
- internal async void external_append_emails_async(Geary.Folder folder,
- Gee.Collection<Geary.EmailIdentifier> appended_ids) {
- if (get_search_blacklist().contains(folder.path))
- return;
-
- if (conversations.is_empty)
- return;
-
- debug("%d out of folder message(s) appended to %s, fetching to add to conversations...",
appended_ids.size,
- folder.to_string());
-
- yield external_load_by_sparse_id(folder, appended_ids, Geary.Folder.ListFlags.NONE, null);
- }
-
private async Geary.EmailIdentifier? get_lowest_email_id_async(Cancellable? cancellable) {
Geary.EmailIdentifier? earliest_id = null;
try {
- yield folder.find_boundaries_async(conversations.get_email_identifiers(),
+ yield this.base_folder.find_boundaries_async(conversations.get_email_identifiers(),
out earliest_id, null, cancellable);
} catch (Error e) {
debug("Error finding earliest email identifier: %s", e.message);
}
-
+
return earliest_id;
}
-
- internal async void reseed_async(string why) {
- Geary.EmailIdentifier? earliest_id = yield get_lowest_email_id_async(null);
- try {
- if (earliest_id != null) {
- debug("ConversationMonitor (%s) reseeding starting from Email ID %s on opened %s", why,
- earliest_id.to_string(), folder.to_string());
- yield load_by_id_async(earliest_id, int.MAX,
- Geary.Folder.ListFlags.OLDEST_TO_NEWEST | Geary.Folder.ListFlags.INCLUDING_ID,
- cancellable_monitor);
- } else {
- debug("ConversationMonitor (%s) reseeding latest %d emails on opened %s", why,
- min_window_count, folder.to_string());
- yield load_by_id_async(null, min_window_count, Geary.Folder.ListFlags.NONE,
cancellable_monitor);
- }
- } catch (Error e) {
- debug("Reseed error: %s", e.message);
- }
-
- if (!reseed_notified) {
- reseed_notified = true;
- notify_seed_completed();
- }
- }
-
- private void on_folder_opened(Geary.Folder.OpenState state, int count) {
- // once remote is open, reseed with messages from the earliest ID to the latest
- if (state == Geary.Folder.OpenState.BOTH || state == Geary.Folder.OpenState.REMOTE)
- operation_queue.add(new ReseedOperation(this, state.to_string()));
- }
-
- /**
- * Attempts to load enough conversations to fill min_window_count.
- */
- internal async void fill_window_async(bool is_insert) {
- if (!is_monitoring)
- return;
-
- if (!is_insert && min_window_count <= conversations.size)
- return;
-
- int initial_message_count = conversations.get_email_count();
-
- // only do local-load if the Folder isn't completely opened, otherwise this operation
- // will block other (more important) operations while it waits for the folder to
- // remote-open
- Folder.ListFlags flags;
- switch (folder.get_open_state()) {
- case Folder.OpenState.CLOSED:
- case Folder.OpenState.LOCAL:
- case Folder.OpenState.OPENING:
- flags = Folder.ListFlags.LOCAL_ONLY;
- break;
-
- case Folder.OpenState.BOTH:
- case Folder.OpenState.REMOTE:
- flags = Folder.ListFlags.NONE;
- break;
-
- default:
- assert_not_reached();
- }
-
- Geary.EmailIdentifier? low_id = yield get_lowest_email_id_async(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)
- num_to_load = WINDOW_FILL_MESSAGE_COUNT;
-
- try {
- yield load_by_id_async(low_id, num_to_load, flags, cancellable_monitor);
- } catch(Error e) {
- debug("Error filling conversation window: %s", e.message);
- }
- } else {
- // No existing messages or an insert invalidated our existing list,
- // need to start from scratch.
- try {
- yield load_by_id_async(null, min_window_count, flags, cancellable_monitor);
- } catch(Error e) {
- debug("Error filling conversation window: %s", e.message);
- }
- }
-
- // Run again to make sure we're full unless we ran out of messages.
- if (conversations.get_email_count() != initial_message_count)
- operation_queue.add(new FillWindowOperation(this, is_insert));
- }
/**
* Check conversations to see if they still exist in the base folder.
@@ -798,11 +835,11 @@ public class Geary.App.ConversationMonitor : BaseObject {
Gee.ArrayList<Conversation> evaporated = new Gee.ArrayList<Conversation>();
foreach (Geary.App.Conversation conversation in conversations) {
int count = yield conversation.get_count_in_folder_async(
- this.folder.account, this.folder.path, cancellable
+ this.base_folder.account, this.base_folder.path, cancellable
);
if (count == 0) {
debug("Evaporating conversation %s because it has no emails in %s",
- conversation.to_string(), this.folder.to_string());
+ conversation.to_string(), this.base_folder.to_string());
this.conversations.remove_conversation(conversation);
evaporated.add(conversation);
}
@@ -811,6 +848,12 @@ public class Geary.App.ConversationMonitor : BaseObject {
return evaporated;
}
+ private void on_folder_opened(Geary.Folder.OpenState state, int count) {
+ // once remote is open, reseed with messages from the earliest ID to the latest
+ if (state == Geary.Folder.OpenState.BOTH || state == Geary.Folder.OpenState.REMOTE)
+ operation_queue.add(new ReseedOperation(this, state.to_string()));
+ }
+
private void on_folder_email_appended(Gee.Collection<EmailIdentifier> appended_ids) {
operation_queue.add(new AppendOperation(this, appended_ids));
}
@@ -820,13 +863,13 @@ public class Geary.App.ConversationMonitor : BaseObject {
}
private void on_folder_email_removed(Gee.Collection<EmailIdentifier> removed_ids) {
- operation_queue.add(new RemoveOperation(this, this.folder, removed_ids));
+ operation_queue.add(new RemoveOperation(this, this.base_folder, removed_ids));
operation_queue.add(new FillWindowOperation(this, false));
}
private void on_account_email_appended(Folder folder,
Gee.Collection<EmailIdentifier> added) {
- if (folder != this.folder) {
+ if (folder != this.base_folder) {
operation_queue.add(
new ExternalAppendOperation(this, folder, added)
);
@@ -835,14 +878,14 @@ public class Geary.App.ConversationMonitor : BaseObject {
private void on_account_email_inserted(Folder folder,
Gee.Collection<EmailIdentifier> added) {
- if (folder != this.folder) {
+ if (folder != this.base_folder) {
operation_queue.add(new FillWindowOperation(this, false));
}
}
private void on_account_email_removed(Folder folder,
Gee.Collection<EmailIdentifier> removed) {
- if (folder != this.folder) {
+ if (folder != this.base_folder) {
operation_queue.add(new RemoveOperation(this, folder, removed));
}
}
diff --git a/src/engine/app/app-conversation.vala b/src/engine/app/app-conversation.vala
index 7c6e9a0..6442710 100644
--- a/src/engine/app/app-conversation.vala
+++ b/src/engine/app/app-conversation.vala
@@ -18,9 +18,9 @@ public class Geary.App.Conversation : BaseObject {
RECV_DATE_ASCENDING,
RECV_DATE_DESCENDING
}
-
+
/**
- * Specify the location of the {@link Email} in relation to the {@link Folder} being monitored
+ * Specify the location of the {@link Email} in relation to the base folder being monitored
* by the {@link Conversation}'s {@link ConversationMonitor}.
*
* IN_FOLDER represents Email that is found in the Folder the ConversationMonitor is
@@ -37,9 +37,9 @@ public class Geary.App.Conversation : BaseObject {
OUT_OF_FOLDER_IN_FOLDER,
ANYWHERE
}
-
+
private static int next_convnum = 0;
-
+
/** Folder from which the conversation originated. */
public Folder base_folder { get; private set; }
@@ -71,12 +71,12 @@ public class Geary.App.Conversation : BaseObject {
* Fired when email has been added to this conversation.
*/
public signal void appended(Geary.Email email);
-
+
/**
* Fired when email has been trimmed from this conversation.
*/
public signal void trimmed(Geary.Email email);
-
+
/**
* Fired when the flags of an email in this conversation have changed.
*/
@@ -97,7 +97,7 @@ public class Geary.App.Conversation : BaseObject {
public int get_count() {
return emails.size;
}
-
+
/**
* Returns the number of emails in the conversation in a particular folder.
*/
@@ -105,7 +105,7 @@ public class Geary.App.Conversation : BaseObject {
Cancellable? cancellable) throws Error {
Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath>? folder_map
= yield account.get_containing_folders_async(emails.keys, cancellable);
-
+
int count = 0;
if (folder_map != null) {
foreach (Geary.EmailIdentifier id in folder_map.get_keys()) {
@@ -113,10 +113,59 @@ public class Geary.App.Conversation : BaseObject {
++count;
}
}
-
+
return count;
}
-
+
+ /**
+ * Returns true if *any* message in the conversation is unread.
+ */
+ public bool is_unread() {
+ return has_flag(Geary.EmailFlags.UNREAD);
+ }
+
+ /**
+ * Returns true if any message in the conversation is not unread.
+ */
+ public bool has_any_read_message() {
+ return is_missing_flag(Geary.EmailFlags.UNREAD);
+ }
+
+ /**
+ * Returns true if *any* message in the conversation is flagged.
+ */
+ public bool is_flagged() {
+ return has_flag(Geary.EmailFlags.FLAGGED);
+ }
+
+ /**
+ * Returns the earliest (first sent) email in the Conversation.
+ */
+ public Geary.Email? get_earliest_sent_email(Location location) {
+ return get_single_email(Ordering.SENT_DATE_ASCENDING, location);
+ }
+
+ /**
+ * Returns the latest (most recently sent) email in the Conversation.
+ */
+ public Geary.Email? get_latest_sent_email(Location location) {
+ return get_single_email(Ordering.SENT_DATE_DESCENDING, location);
+ }
+
+ /**
+ * Returns the earliest (first received) email in the Conversation.
+ */
+ public Geary.Email? get_earliest_recv_email(Location location) {
+ return get_single_email(Ordering.RECV_DATE_ASCENDING, location);
+ }
+
+ /**
+ * Returns the latest (most recently received) email in the Conversation.
+ */
+ public Geary.Email? get_latest_recv_email(Location location) {
+ return get_single_email(Ordering.RECV_DATE_DESCENDING, location);
+ }
+
/**
* Returns all the email in the conversation sorted and filtered according to the specifiers.
*
@@ -131,58 +180,58 @@ public class Geary.App.Conversation : BaseObject {
case Ordering.SENT_DATE_ASCENDING:
email = sent_date_ascending;
break;
-
+
case Ordering.SENT_DATE_DESCENDING:
email = sent_date_descending;
break;
-
+
case Ordering.RECV_DATE_ASCENDING:
email = recv_date_ascending;
break;
-
+
case Ordering.RECV_DATE_DESCENDING:
email = recv_date_descending;
break;
-
+
case Ordering.NONE:
email = emails.values;
break;
-
+
default:
assert_not_reached();
}
-
+
switch (location) {
case Location.IN_FOLDER:
email = traverse<Email>(email)
- .filter((e) => !is_in_current_folder(e.id))
+ .filter((e) => !is_in_base_folder(e.id))
.to_array_list();
break;
-
+
case Location.OUT_OF_FOLDER:
email = traverse<Email>(email)
- .filter((e) => is_in_current_folder(e.id))
+ .filter((e) => is_in_base_folder(e.id))
.to_array_list();
break;
-
+
case Location.IN_FOLDER_OUT_OF_FOLDER:
case Location.OUT_OF_FOLDER_IN_FOLDER:
case Location.ANYWHERE:
// make a modifiable copy
email = traverse<Email>(email).to_array_list();
break;
-
+
default:
assert_not_reached();
}
-
+
return email;
}
/**
* Determines if the given id is in the conversation's base folder.
*/
- public bool is_in_current_folder(Geary.EmailIdentifier id) {
+ public bool is_in_base_folder(Geary.EmailIdentifier id) {
Gee.Collection<Geary.FolderPath>? paths = this.path_map.get(id);
return (paths != null && paths.contains(this.base_folder.path));
}
@@ -203,7 +252,7 @@ public class Geary.App.Conversation : BaseObject {
* Determines if an email with the give id exists in the conversation.
*/
public bool contains_email_by_id(EmailIdentifier id) {
- return emails.contains(id);
+ return emails.has_key(id);
}
/**
@@ -231,6 +280,13 @@ public class Geary.App.Conversation : BaseObject {
}
/**
+ * Returns a string representation for debugging.
+ */
+ public string to_string() {
+ return "[#%d] (%d emails)".printf(convnum, emails.size);
+ }
+
+ /**
* Add the email to the conversation if not already present.
*
* The value of `known_paths` should contain all the known {@link
@@ -311,61 +367,12 @@ public class Geary.App.Conversation : BaseObject {
this.path_map.remove(id, path);
}
- /**
- * Returns true if *any* message in the conversation is unread.
- */
- public bool is_unread() {
- return has_flag(Geary.EmailFlags.UNREAD);
- }
-
- /**
- * Returns true if any message in the conversation is not unread.
- */
- public bool has_any_read_message() {
- return is_missing_flag(Geary.EmailFlags.UNREAD);
- }
-
- /**
- * Returns true if *any* message in the conversation is flagged.
- */
- public bool is_flagged() {
- return has_flag(Geary.EmailFlags.FLAGGED);
- }
-
- /**
- * Returns the earliest (first sent) email in the Conversation.
- */
- public Geary.Email? get_earliest_sent_email(Location location) {
- return get_single_email(Ordering.SENT_DATE_ASCENDING, location);
- }
-
- /**
- * Returns the latest (most recently sent) email in the Conversation.
- */
- public Geary.Email? get_latest_sent_email(Location location) {
- return get_single_email(Ordering.SENT_DATE_DESCENDING, location);
- }
-
- /**
- * Returns the earliest (first received) email in the Conversation.
- */
- public Geary.Email? get_earliest_recv_email(Location location) {
- return get_single_email(Ordering.RECV_DATE_ASCENDING, location);
- }
-
- /**
- * Returns the latest (most recently received) email in the Conversation.
- */
- public Geary.Email? get_latest_recv_email(Location location) {
- return get_single_email(Ordering.RECV_DATE_DESCENDING, location);
- }
-
private Geary.Email? get_single_email(Ordering ordering, Location location) {
// note that the location-ordering preferences are treated as ANYWHERE by get_emails()
Gee.Collection<Geary.Email> all = get_emails(ordering, location);
if (all.size == 0)
return null;
-
+
// Because IN_FOLDER_OUT_OF_FOLDER and OUT_OF_FOLDER_IN_FOLDER are treated as ANYWHERE,
// have to do our own filtering
switch (location) {
@@ -373,30 +380,30 @@ public class Geary.App.Conversation : BaseObject {
case Location.OUT_OF_FOLDER:
case Location.ANYWHERE:
return traverse<Email>(all).first();
-
+
case Location.IN_FOLDER_OUT_OF_FOLDER:
Geary.Email? found = traverse<Email>(all)
- .first_matching((email) => is_in_current_folder(email.id));
-
+ .first_matching((email) => is_in_base_folder(email.id));
+
return found ?? traverse<Email>(all).first();
-
+
case Location.OUT_OF_FOLDER_IN_FOLDER:
Geary.Email? found = traverse<Email>(all)
- .first_matching((email) => !is_in_current_folder(email.id));
-
+ .first_matching((email) => !is_in_base_folder(email.id));
+
return found ?? traverse<Email>(all).first();
-
+
default:
assert_not_reached();
}
}
-
+
private bool check_flag(Geary.NamedFlag flag, bool contains) {
foreach (Geary.Email email in get_emails(Ordering.NONE)) {
if (email.email_flags != null && email.email_flags.contains(flag) == contains)
return true;
}
-
+
return false;
}
@@ -408,7 +415,4 @@ public class Geary.App.Conversation : BaseObject {
return check_flag(flag, false);
}
- public string to_string() {
- return "[#%d] (%d emails)".printf(convnum, emails.size);
- }
}
diff --git a/src/engine/app/conversation-monitor/app-conversation-set.vala
b/src/engine/app/conversation-monitor/app-conversation-set.vala
index 10a0269..624cd06 100644
--- a/src/engine/app/conversation-monitor/app-conversation-set.vala
+++ b/src/engine/app/conversation-monitor/app-conversation-set.vala
@@ -1,46 +1,55 @@
-/* Copyright 2016 Software Freedom Conservancy Inc.
+/*
+ * Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2017 Michael Gratton <mike vee net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
+/**
+ * Creates and maintains set of conversations by adding and removing email.
+ */
private class Geary.App.ConversationSet : BaseObject {
+
+ /** Determines the number of conversations in the set. */
+ public int size { get { return _conversations.size; } }
+
+ /** Determines the set contains no conversations. */
+ public bool is_empty { get { return _conversations.is_empty; } }
+
+ /** Returns a read-only view of conversations in the set. */
+ public Gee.Collection<Conversation> conversations {
+ owned get { return _conversations.read_only_view; }
+ }
+
private Gee.Set<Conversation> _conversations = new Gee.HashSet<Conversation>();
-
+
// Maps email ids to conversations.
private Gee.HashMap<Geary.EmailIdentifier, Conversation> email_id_map
= new Gee.HashMap<Geary.EmailIdentifier, Conversation>();
-
+
// Contains the full set of Message IDs theoretically in each conversation,
// as determined by the ancestors of all messages in the conversation.
private Gee.HashMap<Geary.RFC822.MessageID, Conversation> logical_message_id_map
= new Gee.HashMap<Geary.RFC822.MessageID, Conversation>();
-
- public int size { get { return _conversations.size; } }
- public bool is_empty { get { return _conversations.is_empty; } }
- public Gee.Collection<Conversation> conversations {
- owned get { return _conversations.read_only_view; }
- }
-
- public ConversationSet() {
- }
-
+
+
public int get_email_count() {
return email_id_map.size;
}
-
+
public Gee.Collection<Geary.EmailIdentifier> get_email_identifiers() {
return email_id_map.keys;
}
-
+
public bool contains(Conversation conversation) {
return _conversations.contains(conversation);
}
-
+
public bool has_email_identifier(Geary.EmailIdentifier id) {
return email_id_map.has_key(id);
}
-
+
/**
* Return whether the set has the given Message ID. If logical_set,
* there's no requirement that any conversation actually contain a message
@@ -50,12 +59,146 @@ private class Geary.App.ConversationSet : BaseObject {
public bool has_message_id(Geary.RFC822.MessageID message_id) {
return logical_message_id_map.has_key(message_id);
}
-
+
public Conversation? get_by_email_identifier(Geary.EmailIdentifier id) {
return email_id_map.get(id);
}
/**
+ * Adds a collection of emails to conversations in this set.
+ *
+ * This method will create and/or merge conversations as
+ * needed. The collection `emails` contains the messages to be
+ * added, and for each email in the collection, there should be an
+ * entry in `id_to_paths` that indicates the folders each message
+ * is known to belong to. The folder `base_folder` is the base
+ * folder for the conversation monitor that owns this set.
+ *
+ * The three collections returned include any conversation that
+ * were created, any that had email appended to them (and the
+ * messages that were appended), and any that were removed due to
+ * being merged into another.
+ */
+ public void add_all_emails(Gee.Collection<Email> emails,
+ Gee.MultiMap<EmailIdentifier, FolderPath>? id_to_paths,
+ Folder base_folder,
+ out Gee.Collection<Conversation> added,
+ out Gee.MultiMap<Conversation, Email> appended,
+ out Gee.Collection<Conversation> removed_due_to_merge) {
+ Gee.HashSet<Conversation> _added =
+ new Gee.HashSet<Conversation>();
+ Gee.HashMultiMap<Conversation, Geary.Email> _appended =
+ new Gee.HashMultiMap<Conversation, Geary.Email>();
+ Gee.HashSet<Conversation> _removed_due_to_merge =
+ new Gee.HashSet<Conversation>();
+
+ foreach (Geary.Email email in emails) {
+ Gee.Set<Conversation> associated = get_associated_conversations(email);
+ if (associated.size > 1) {
+ // When multiple conversations hold one or more of the Message-IDs in the email's
+ // ancestry, it means a prior email processed here didn't properly list their entire
+ // In-Reply-To or References and a split in the conversation appeared ...
+ // ConversationSet *requires* each Message-ID is associated with one and only one
+ // Conversation
+ //
+ // By doing this first, it prevents ConversationSet getting itself into a bad state
+ // where more than one Conversation thinks it "owns" a Message-ID
+ debug("Merging %d conversations due new email associating with all...", associated.size);
+
+ // Note that this call will modify the List so it only holds the to-be-axed
+ // Conversations
+ Gee.Set<Geary.Email> moved_email = new Gee.HashSet<Geary.Email>();
+ Conversation dest = merge_conversations(
+ associated, moved_email
+ );
+ assert(!associated.contains(dest));
+
+ // remove the remaining conversations from the added/appended Collections
+ _added.remove_all(associated);
+ foreach (Conversation removed_conversation in associated)
+ _appended.remove_all(removed_conversation);
+
+ // but notify caller they were merged away
+ _removed_due_to_merge.add_all(associated);
+
+ // the dest was always appended to, never created
+ if (!_added.contains(dest)) {
+ foreach (Geary.Email moved in moved_email)
+ _appended.set(dest, moved);
+ }
+
+ // Nasty ol' Email won't cause problems now -- but let's check anyway!
+ assert(get_associated_conversations(email).size <= 1);
+ }
+
+ bool added_conversation;
+ Conversation? conversation = add_email(
+ email,
+ base_folder,
+ (id_to_paths != null) ? id_to_paths.get(email.id) : null,
+ out added_conversation
+ );
+
+ if (conversation == null)
+ continue;
+
+ if (added_conversation) {
+ _added.add(conversation);
+ } else {
+ if (!_added.contains(conversation))
+ _appended.set(conversation, email);
+ }
+ }
+
+ added = _added;
+ appended = _appended;
+ removed_due_to_merge = _removed_due_to_merge;
+ }
+
+ /**
+ * Removes a number of emails from conversations in this set.
+ *
+ * This method will remove and/or trim conversations as
+ * needed. The collection `emails_ids` contains the identifiers
+ * of emails to be removed.
+ *
+ * The returned collections include any conversations that were
+ * removed (if all of their emails were removed), and any that
+ * were trimmed and the emails that were trimmed from it,
+ * respectively.
+ */
+ public void remove_all_emails_by_identifier(FolderPath source_path,
+ Gee.Collection<Geary.EmailIdentifier> ids,
+ out Gee.Collection<Conversation> removed,
+ out Gee.MultiMap<Conversation, Geary.Email> trimmed) {
+ Gee.HashSet<Conversation> _removed = new Gee.HashSet<Conversation>();
+ Gee.HashMultiMap<Conversation, Geary.Email> _trimmed
+ = new Gee.HashMultiMap<Conversation, Geary.Email>();
+
+ foreach (Geary.EmailIdentifier id in ids) {
+ Geary.Email email;
+ bool removed_conversation;
+ Conversation? conversation = remove_email_by_identifier(
+ source_path, id, out email, out removed_conversation
+ );
+
+ if (conversation == null)
+ continue;
+
+ if (removed_conversation) {
+ if (_trimmed.contains(conversation))
+ _trimmed.remove_all(conversation);
+ _removed.add(conversation);
+ } else if (!conversation.contains_email_by_id(id)) {
+ _trimmed.set(conversation, email);
+ }
+ }
+
+ removed = _removed;
+ trimmed = _trimmed;
+ }
+
+ /**
* Removes a conversation from the set.
*/
public void remove_conversation(Conversation conversation) {
@@ -76,7 +219,7 @@ private class Geary.App.ConversationSet : BaseObject {
.map_nonnull<Conversation>(a => logical_message_id_map.get(a))
.to_hash_set();
}
-
+
return Gee.Set.empty<Conversation>();
}
@@ -147,107 +290,10 @@ private class Geary.App.ConversationSet : BaseObject {
}
}
- /**
- * Adds a collection of emails to conversations in this set.
- *
- * This method will create and/or merge conversations as
- * needed. The collection `emails` contains the messages to be
- * added, and for each email in the collection, there should be an
- * entry in `id_to_paths` that indicates the folders each message
- * is known to belong to. The folder `base_folder` is the base
- * folder for the conversation monitor that owns this set.
- *
- * The three collections returned include any conversation that
- * were created, any that had email appended to them (and the
- * messages that were appended), and any that were removed due to
- * being merged into another.
- */
- public async void add_all_emails_async(
- Gee.Collection<Geary.Email> emails,
- Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath>? id_to_paths,
- Folder base_folder,
- out Gee.Collection<Conversation> added,
- out Gee.MultiMap<Conversation, Geary.Email> appended,
- out Gee.Collection<Conversation> removed_due_to_merge,
- Cancellable? cancellable)
- throws Error {
- Gee.HashSet<Conversation> _added =
- new Gee.HashSet<Conversation>();
- Gee.HashMultiMap<Conversation, Geary.Email> _appended =
- new Gee.HashMultiMap<Conversation, Geary.Email>();
- Gee.HashSet<Conversation> _removed_due_to_merge =
- new Gee.HashSet<Conversation>();
-
- foreach (Geary.Email email in emails) {
- Gee.Set<Conversation> associated = get_associated_conversations(email);
- if (associated.size > 1) {
- // When multiple conversations hold one or more of the Message-IDs in the email's
- // ancestry, it means a prior email processed here didn't properly list their entire
- // In-Reply-To or References and a split in the conversation appeared ...
- // ConversationSet *requires* each Message-ID is associated with one and only one
- // Conversation
- //
- // By doing this first, it prevents ConversationSet getting itself into a bad state
- // where more than one Conversation thinks it "owns" a Message-ID
- debug("Merging %d conversations due new email associating with all...", associated.size);
-
- // Note that this call will modify the List so it only holds the to-be-axed
- // Conversations
- Gee.Set<Geary.Email> moved_email = new Gee.HashSet<Geary.Email>();
- Conversation dest = yield merge_conversations_async(
- associated, moved_email, cancellable
- );
- assert(!associated.contains(dest));
-
- // remove the remaining conversations from the added/appended Collections
- _added.remove_all(associated);
- foreach (Conversation removed_conversation in associated)
- _appended.remove_all(removed_conversation);
-
- // but notify caller they were merged away
- _removed_due_to_merge.add_all(associated);
-
- // the dest was always appended to, never created
- if (!_added.contains(dest)) {
- foreach (Geary.Email moved in moved_email)
- _appended.set(dest, moved);
- }
-
- // Nasty ol' Email won't cause problems now -- but let's check anyway!
- assert(get_associated_conversations(email).size <= 1);
- }
-
- bool added_conversation;
- Conversation? conversation = add_email(
- email,
- base_folder,
- (id_to_paths != null) ? id_to_paths.get(email.id) : null,
- out added_conversation
- );
-
- if (conversation == null)
- continue;
-
- if (added_conversation) {
- _added.add(conversation);
- } else {
- if (!_added.contains(conversation))
- _appended.set(conversation, email);
- }
- }
-
- added = _added;
- appended = _appended;
- removed_due_to_merge = _removed_due_to_merge;
- }
-
// This method will remove the destination (merged) Conversation from the List and return it
// as the result, along with a Collection of email that must be merged into it
- private async Conversation
- merge_conversations_async(Gee.Set<Conversation> conversations,
- Gee.Set<Geary.Email> moved_email,
- Cancellable? cancellable)
- throws Error {
+ private Conversation merge_conversations(Gee.Set<Conversation> conversations,
+ Gee.Set<Email> moved_email) {
assert(conversations.size > 0);
// find the largest conversation and merge the others into it
@@ -292,7 +338,7 @@ private class Geary.App.ConversationSet : BaseObject {
// it would indicate a nasty error in our logic that we need to fix.
if (!email_id_map.unset(email.id))
error("Email %s already removed from conversation set", email.id.to_string());
-
+
Gee.Set<Geary.RFC822.MessageID>? removed_message_ids = conversation.remove(email);
if (removed_message_ids != null) {
foreach (Geary.RFC822.MessageID removed_message_id in removed_message_ids) {
@@ -353,47 +399,4 @@ private class Geary.App.ConversationSet : BaseObject {
return conversation;
}
- /**
- * Removes a number of emails from conversations in this set.
- *
- * This method will remove and/or trim conversations as
- * needed. The collection `emails_ids` contains the identifiers
- * of emails to be removed.
- *
- * The returned collections include any conversations that were
- * removed (if all of their emails were removed), and any that
- * were trimmed and the emails that were trimmed from it,
- * respectively.
- */
- public void remove_all_emails_by_identifier(FolderPath source_path,
- Gee.Collection<Geary.EmailIdentifier> ids,
- out Gee.Collection<Conversation> removed,
- out Gee.MultiMap<Conversation, Geary.Email> trimmed) {
- Gee.HashSet<Conversation> _removed = new Gee.HashSet<Conversation>();
- Gee.HashMultiMap<Conversation, Geary.Email> _trimmed
- = new Gee.HashMultiMap<Conversation, Geary.Email>();
-
- foreach (Geary.EmailIdentifier id in ids) {
- Geary.Email email;
- bool removed_conversation;
- Conversation? conversation = remove_email_by_identifier(
- source_path, id, out email, out removed_conversation
- );
-
- if (conversation == null)
- continue;
-
- if (removed_conversation) {
- if (_trimmed.contains(conversation))
- _trimmed.remove_all(conversation);
- _removed.add(conversation);
- } else if (!conversation.contains_email_by_id(id)) {
- _trimmed.set(conversation, email);
- }
- }
-
- removed = _removed;
- trimmed = _trimmed;
- }
-
}
diff --git a/test/engine/app/app-conversation-set-test.vala b/test/engine/app/app-conversation-set-test.vala
index 9a7324c..a2dc8c9 100644
--- a/test/engine/app/app-conversation-set-test.vala
+++ b/test/engine/app/app-conversation-set-test.vala
@@ -40,9 +40,9 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Email e1 = new Email(new MockEmailIdentifer(1));
Email e2 = new Email(new MockEmailIdentifer(2));
- Gee.LinkedList<Email> email = new Gee.LinkedList<Email>();
- email.add(e1);
- email.add(e2);
+ Gee.LinkedList<Email> emails = new Gee.LinkedList<Email>();
+ emails.add(e1);
+ emails.add(e2);
Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath> email_paths =
new Gee.HashMultiMap<Geary.EmailIdentifier, Geary.FolderPath>();
@@ -53,20 +53,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Gee.MultiMap<Conversation,Email>? appended = null;
Gee.Collection<Conversation>? removed = null;
- this.test.add_all_emails_async.begin(
- email,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 2);
assert(this.test.get_email_count() == 2);
@@ -96,8 +86,9 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
public void add_all_duplicate() {
Email e1 = setup_email(1);
- Gee.LinkedList<Email> email = new Gee.LinkedList<Email>();
- email.add(e1);
+ Gee.LinkedList<Email> emails = new Gee.LinkedList<Email>();
+ emails.add(e1);
+ emails.add(e1);
Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath> email_paths =
new Gee.HashMultiMap<Geary.EmailIdentifier, Geary.FolderPath>();
@@ -108,20 +99,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Gee.Collection<Conversation>? added = null;
Gee.MultiMap<Conversation,Email>? appended = null;
Gee.Collection<Conversation>? removed = null;
- this.test.add_all_emails_async.begin(
- email,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 1);
assert(this.test.get_email_count() == 1);
@@ -137,20 +118,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
added = null;
appended = null;
removed = null;
- this.test.add_all_emails_async.begin(
- email,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 1);
assert(this.test.get_email_count() == 1);
@@ -178,21 +149,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Gee.Collection<Conversation>? added = null;
Gee.MultiMap<Conversation,Email>? appended = null;
Gee.Collection<Conversation>? removed = null;
-
- this.test.add_all_emails_async.begin(
- emails,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 1);
assert(this.test.get_email_count() == 2);
@@ -219,20 +179,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
added = null;
appended = null;
removed = null;
- this.test.add_all_emails_async.begin(
- emails,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 1);
assert(this.test.get_email_count() == 3);
@@ -269,20 +219,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Gee.Collection<Conversation>? added = null;
Gee.MultiMap<Conversation,Email>? appended = null;
Gee.Collection<Conversation>? removed = null;
- this.test.add_all_emails_async.begin(
- emails,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 1);
assert(this.test.get_email_count() == 2);
@@ -322,20 +262,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Gee.Collection<Conversation>? added = null;
Gee.MultiMap<Conversation,Email>? appended = null;
Gee.Collection<Conversation>? removed = null;
- this.test.add_all_emails_async.begin(
- emails,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 1);
assert(this.test.get_email_count() == 3);
@@ -369,26 +299,16 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Gee.Collection<Conversation>? added = null;
Gee.MultiMap<Conversation,Email>? appended = null;
Gee.Collection<Conversation>? removed = null;
- this.test.add_all_emails_async.begin(
- emails,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 1);
assert(this.test.get_email_count() == 1);
Conversation convo = this.test.get_by_email_identifier(e1.id);
- assert(convo.is_in_current_folder(e1.id) == true);
+ assert(convo.is_in_base_folder(e1.id) == true);
assert(convo.get_folder_count(e1.id) == 2);
}
@@ -408,20 +328,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Gee.Collection<Conversation>? added = null;
Gee.MultiMap<Conversation,Email>? appended = null;
Gee.Collection<Conversation>? removed = null;
- this.test.add_all_emails_async.begin(
- emails,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
assert(this.test.size == 1);
assert(this.test.get_email_count() == 1);
@@ -431,7 +341,7 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
assert(removed.is_empty);
Conversation convo = this.test.get_by_email_identifier(e1.id);
- assert(convo.is_in_current_folder(e1.id) == true);
+ assert(convo.is_in_base_folder(e1.id) == true);
assert(convo.get_folder_count(e1.id) == 2);
}
@@ -518,7 +428,7 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
assert(trimmed != null);
assert(trimmed.size == 0);
- assert(convo.is_in_current_folder(e1.id) == true);
+ assert(convo.is_in_base_folder(e1.id) == true);
assert(convo.get_folder_count(e1.id) == 1);
}
@@ -553,20 +463,10 @@ class Geary.App.ConversationSetTest : Gee.TestCase {
Gee.Collection<Conversation>? added = null;
Gee.MultiMap<Conversation,Email>? appended = null;
Gee.Collection<Conversation>? removed = null;
- this.test.add_all_emails_async.begin(
- emails,
- email_paths,
- this.base_folder,
- null,
- (obj, ret) => { async_complete(ret); }
+ this.test.add_all_emails(
+ emails, email_paths, this.base_folder,
+ out added, out appended, out removed
);
- try {
- this.test.add_all_emails_async.end(
- async_result(), out added, out appended, out removed
- );
- } catch (Error error) {
- assert_not_reached();
- }
}
}
diff --git a/test/engine/app/app-conversation-test.vala b/test/engine/app/app-conversation-test.vala
index c189705..453ebeb 100644
--- a/test/engine/app/app-conversation-test.vala
+++ b/test/engine/app/app-conversation-test.vala
@@ -40,13 +40,13 @@ class Geary.App.ConversationTest : Gee.TestCase {
});
assert(this.test.add(e1, singleton(this.base_folder.path)) == true);
- assert(this.test.is_in_current_folder(e1.id) == true);
+ assert(this.test.is_in_base_folder(e1.id) == true);
assert(this.test.get_folder_count(e1.id) == 1);
assert(appended == 1);
assert(this.test.get_count() == 1);
assert(this.test.add(e2, singleton(this.base_folder.path)) == true);
- assert(this.test.is_in_current_folder(e2.id) == true);
+ assert(this.test.is_in_base_folder(e2.id) == true);
assert(this.test.get_folder_count(e2.id) == 1);
assert(appended == 2);
assert(this.test.get_count() == 2);
@@ -80,14 +80,14 @@ class Geary.App.ConversationTest : Gee.TestCase {
other_paths.add(other_path);
assert(this.test.add(e1, other_paths) == false);
- assert(this.test.is_in_current_folder(e1.id) == true);
+ assert(this.test.is_in_base_folder(e1.id) == true);
assert(this.test.get_folder_count(e1.id) == 2);
- assert(this.test.is_in_current_folder(e2.id) == true);
+ assert(this.test.is_in_base_folder(e2.id) == true);
assert(this.test.get_folder_count(e2.id) == 1);
this.test.remove_path(e1.id, other_path);
- assert(this.test.is_in_current_folder(e1.id) == true);
+ assert(this.test.is_in_base_folder(e1.id) == true);
assert(this.test.get_folder_count(e1.id) == 1);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]