Commit 35150701 authored by Michael Gratton's avatar Michael Gratton 🤞

Merge branch 'buzzert/deleted_flags' into 'master'

Fixes bug where messages marked for deletion still appear in message list

See merge request !54
parents 4b3c1676 d694e849
Pipeline #55461 failed with stages
in 21 minutes
......@@ -30,6 +30,10 @@ public class Geary.EmailFlags : Geary.NamedFlags {
public static NamedFlag DRAFT { owned get {
return new NamedFlag("DRAFT");
} }
public static NamedFlag DELETED { owned get {
return new NamedFlag("DELETED");
} }
/// Signifies a message in our outbox that has been sent but we're still
/// keeping around for other purposes, i.e. pushing up to Sent Mail.
......@@ -73,5 +77,9 @@ public class Geary.EmailFlags : Geary.NamedFlags {
public inline bool is_outbox_sent() {
return contains(OUTBOX_SENT);
}
public inline bool is_deleted() {
return contains(DELETED);
}
}
......@@ -685,8 +685,13 @@ public class Geary.App.ConversationMonitor : BaseObject {
if (!job.emails.has_key(email.id)) {
job.emails.set(email.id, email);
// Expand conversations whose messages have ancestors, and aren't marked
// for deletion.
Geary.EmailFlags? flags = email.email_flags;
bool marked_for_deletion = (flags != null) ? flags.is_deleted() : false;
Gee.Set<RFC822.MessageID>? ancestors = email.get_ancestors();
if (ancestors != null) {
if (ancestors != null && !marked_for_deletion) {
Geary.traverse<RFC822.MessageID>(ancestors)
.filter(id => !new_message_ids.contains(id))
.add_all_to(new_message_ids);
......@@ -845,10 +850,28 @@ public class Geary.App.ConversationMonitor : BaseObject {
private void on_account_email_flags_changed(Geary.Folder folder,
Gee.Map<EmailIdentifier,EmailFlags> map) {
Gee.HashSet<EmailIdentifier> inserted_ids = new Gee.HashSet<EmailIdentifier>();
Gee.HashSet<EmailIdentifier> removed_ids = new Gee.HashSet<EmailIdentifier>();
Gee.HashSet<Conversation> removed_conversations = new Gee.HashSet<Conversation>();
foreach (EmailIdentifier id in map.keys) {
Conversation? conversation = this.conversations.get_by_email_identifier(id);
if (conversation == null)
if (conversation == null) {
if (folder == this.base_folder) {
// Check to see if the incoming message is sorted later than the last message in the
// window. If it is, don't resurrect it since it likely hasn't been loaded yet.
Geary.EmailIdentifier? lowest = this.window_lowest;
if (lowest != null) {
if (lowest.natural_sort_comparator(id) < 0) {
debug("Unflagging email %s for deletion resurrects conversation", id.to_string());
inserted_ids.add(id);
} else {
debug("Not resurrecting undeleted email %s outside of window", id.to_string());
}
}
}
continue;
}
Email? email = conversation.get_email_by_id(id);
if (email == null)
......@@ -856,7 +879,33 @@ public class Geary.App.ConversationMonitor : BaseObject {
email.set_flags(map.get(id));
notify_email_flags_changed(conversation, email);
// Remove conversation if get_emails yields an empty collection -- this probably means
// the conversation was deleted.
if (conversation.get_emails(Geary.App.Conversation.Ordering.NONE).size == 0) {
debug("Flagging email %s for deletion evaporates conversation %s",
id.to_string(), conversation.to_string());
this.conversations.remove_conversation(conversation);
removed_conversations.add(conversation);
removed_ids.add(id);
}
}
// Notify about inserted messages
if (inserted_ids.size > 0) {
this.queue.add(new InsertOperation(this, inserted_ids));
}
// Notify self about removed conversations
// NOTE: We are only notifying the conversation monitor about the removed conversations instead of
// enqueuing a RemoveOperation, because these messages haven't actually been removed. They're only
// hidden at the conversation-level for being marked as deleted.
removed(
removed_conversations,
new Gee.HashMultiMap<Conversation, Email>(),
(folder == this.base_folder) ? removed_ids : null
);
}
private void on_operation_error(ConversationOperation op, Error err) {
......
......@@ -180,6 +180,16 @@ public class Geary.App.Conversation : BaseObject {
return get_single_email(Ordering.RECV_DATE_DESCENDING, location);
}
public Gee.Collection<Email>
get_emails_flagged_for_deletion(Location location,
Gee.Collection<FolderPath>? blacklist = null) {
Gee.Collection<Email> emails = get_emails(Ordering.NONE, location, blacklist, false);
Iterable<Email> filtered = traverse<Email>(emails);
return filtered.filter(
(e) => e.email_flags.is_deleted()
).to_array_list();
}
/**
* Returns the conversation's email, possibly sorted and filtered.
*
......@@ -191,7 +201,8 @@ public class Geary.App.Conversation : BaseObject {
public Gee.List<Email>
get_emails(Ordering ordering,
Location location = Location.ANYWHERE,
Gee.Collection<FolderPath>? blacklist = null) {
Gee.Collection<FolderPath>? blacklist = null,
bool filter_deleted = true) {
Gee.Collection<Email> email;
switch (ordering) {
case Ordering.SENT_DATE_ASCENDING:
......@@ -233,6 +244,13 @@ public class Geary.App.Conversation : BaseObject {
break;
}
// Filter emails waiting to be expunged (\DELETED)
if (filter_deleted) {
filtered = filtered.filter(
(e) => (e.email_flags != null) ? !e.email_flags.is_deleted() : true
);
}
if (blacklist != null && !blacklist.is_empty) {
if (blacklist.size == 1) {
FolderPath blacklist_path =
......
......@@ -191,7 +191,14 @@ private class Geary.App.ConversationSet : BaseObject {
* Removes a conversation from the set.
*/
public void remove_conversation(Conversation conversation) {
foreach (Geary.Email conversation_email in conversation.get_emails(Conversation.Ordering.NONE))
Gee.Collection<Email> conversation_emails = conversation.get_emails(
Conversation.Ordering.NONE, // ordering
Conversation.Location.ANYWHERE, // location
null, // blacklist
false // filter deleted (false, so we remove emails that are flagged for deletion too)
);
foreach (Geary.Email conversation_email in conversation_emails)
remove_email_from_conversation(conversation, conversation_email);
if (!_conversations.remove(conversation))
......
......@@ -21,6 +21,9 @@ public class Geary.Imap.EmailFlags : Geary.EmailFlags {
if (flags.contains(MessageFlag.DRAFT))
add(DRAFT);
if (flags.contains(MessageFlag.DELETED))
add(DELETED);
}
/**
......@@ -66,6 +69,9 @@ public class Geary.Imap.EmailFlags : Geary.EmailFlags {
if (flag.equal_to(DRAFT))
message_flags.add(MessageFlag.DRAFT);
if (flag.equal_to(DELETED))
message_flags.add(MessageFlag.DELETED);
}
base.notify_added(added);
......@@ -84,6 +90,9 @@ public class Geary.Imap.EmailFlags : Geary.EmailFlags {
if (flag.equal_to(DRAFT))
message_flags.remove(MessageFlag.DRAFT);
if (flag.equal_to(DELETED))
message_flags.remove(MessageFlag.DELETED);
}
base.notify_removed(removed);
......
......@@ -115,6 +115,8 @@ public class Geary.Imap.MessageFlag : Geary.Imap.Flag {
msg_flags_add.add(MessageFlag.LOAD_REMOTE_IMAGES);
if (email_flags_add.contains(Geary.EmailFlags.DRAFT))
msg_flags_add.add(MessageFlag.DRAFT);
if (email_flags_add.contains(Geary.EmailFlags.DELETED))
msg_flags_add.add(MessageFlag.DELETED);
}
if (email_flags_remove != null) {
......@@ -126,6 +128,8 @@ public class Geary.Imap.MessageFlag : Geary.Imap.Flag {
msg_flags_remove.add(MessageFlag.LOAD_REMOTE_IMAGES);
if (email_flags_remove.contains(Geary.EmailFlags.DRAFT))
msg_flags_remove.add(MessageFlag.DRAFT);
if (email_flags_remove.contains(Geary.EmailFlags.DELETED))
msg_flags_remove.add(MessageFlag.DELETED);
}
}
......
......@@ -26,6 +26,7 @@ class Geary.App.ConversationMonitorTest : TestCase {
add_test("base_folder_message_appended", base_folder_message_appended);
add_test("base_folder_message_removed", base_folder_message_removed);
add_test("external_folder_message_appended", external_folder_message_appended);
add_test("conversation_marked_as_deleted", conversation_marked_as_deleted);
}
public override void set_up() {
......@@ -343,6 +344,30 @@ class Geary.App.ConversationMonitorTest : TestCase {
"Appended email not present in conversation");
}
public void conversation_marked_as_deleted() throws Error {
Email e1 = setup_email(1);
Gee.MultiMap<EmailIdentifier,FolderPath> paths =
new Gee.HashMultiMap<EmailIdentifier,FolderPath>();
paths.set(e1.id, this.base_folder.path);
ConversationMonitor monitor = setup_monitor({e1}, paths);
assert_int(1, monitor.size, "Conversation count");
// Mark message as deleted
Gee.HashMap<EmailIdentifier,EmailFlags> flags_changed =
new Gee.HashMap<EmailIdentifier,EmailFlags>();
flags_changed.set(e1.id, new EmailFlags.with(EmailFlags.DELETED));
this.account.email_flags_changed(this.base_folder, flags_changed);
this.base_folder.expect_call("list_email_by_sparse_id_async");
this.base_folder.expect_call("list_email_by_id_async");
wait_for_signal(monitor, "email-flags-changed");
assert_int(0, monitor.size, "Conversation count should now be zero after being marked deleted.");
}
private Email setup_email(int id, Email? references = null) {
Email email = new Email(new MockEmailIdentifer(id));
DateTime now = new DateTime.now_local();
......
......@@ -23,6 +23,7 @@ class Geary.App.ConversationTest : TestCase {
add_test("get_emails", get_emails);
add_test("get_emails_by_location", get_emails_by_location);
add_test("get_emails_blacklist", get_emails_blacklist);
add_test("get_emails_marked_for_deletion", get_emails_marked_for_deletion);
}
public override void set_up() {
......@@ -244,6 +245,19 @@ class Geary.App.ConversationTest : TestCase {
);
}
public void get_emails_marked_for_deletion() throws GLib.Error {
Geary.Email e1 = setup_email(1);
e1.set_flags(new Geary.EmailFlags.with(Geary.EmailFlags.DELETED));
this.test.add(e1, singleton(this.base_folder.path));
assert_int(
0, this.test.get_emails(Conversation.Ordering.NONE,
Conversation.Location.ANYWHERE
).size,
"Message marked for deletion still present in conversation"
);
}
private Gee.Collection<E> singleton<E>(E element) {
Gee.LinkedList<E> collection = new Gee.LinkedList<E>();
collection.add(element);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment