From 418de075822a7a50730245a1e89731ed8c017e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bellegarde?= Date: Mon, 2 Oct 2023 13:48:24 +0200 Subject: [PATCH] engine: When opening a folder, sanitize remote unseen. Messages may have been marked as read while waiting in open_remote_session_locked(). Unsure we handle the diff when remote session opened. Fix #1023 Fix #364 --- src/engine/imap-db/imap-db-folder.vala | 4 ++- .../imap-engine-generic-account.vala | 9 +++++++ .../imap-engine-minimal-folder.vala | 11 ++++++++ .../imap-engine/imap-engine-replay-queue.vala | 26 ++++++++++++++++--- .../replay-ops/imap-engine-mark-email.vala | 11 ++++++-- 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala index 15aacb4e..28a93222 100644 --- a/src/engine/imap-db/imap-db-folder.vala +++ b/src/engine/imap-db/imap-db-folder.vala @@ -1060,7 +1060,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics { return deleted_email_ids; } - public async void mark_email_async(Gee.Collection to_mark, + public async int mark_email_async(Gee.Collection to_mark, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, Cancellable? cancellable) throws Error { int unread_change = 0; // Negative means messages are read, positive means unread. @@ -1120,6 +1120,8 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics { // Signal changes so other folders can be updated. if (unread_status.size > 0) unread_updated(unread_status); + + return unread_change; } internal async Gee.List? get_email_uids_async( diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala b/src/engine/imap-engine/imap-engine-generic-account.vala index 2db97164..6bc4dc1f 100644 --- a/src/engine/imap-engine/imap-engine-generic-account.vala +++ b/src/engine/imap-engine/imap-engine-generic-account.vala @@ -1373,6 +1373,15 @@ internal class Geary.ImapEngine.UpdateRemoteFolders : AccountOperation { // always update, openable or not; have the folder update the UID info the next time // it's opened try { + // Some emails may have been marked as read locally while + // updating remote folders + if (minimal_folder.replay_queue != null && + minimal_folder.replay_queue.pending_unread_change() != 0) { + remote_folder.properties.set_status_unseen( + remote_folder.properties.unseen + + minimal_folder.replay_queue.pending_unread_change() + ); + } yield minimal_folder.local_folder.update_folder_status( remote_folder.properties, false, cancellable ); diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala b/src/engine/imap-engine/imap-engine-minimal-folder.vala index 14558d49..5293fb62 100644 --- a/src/engine/imap-engine/imap-engine-minimal-folder.vala +++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala @@ -1112,6 +1112,13 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport return; } + // Some emails may have been marked as read locally while + // claiming folder session, handle the diff here. + session.folder.properties.set_status_unseen( + session.folder.properties.unseen + + this.replay_queue.pending_unread_change() + ); + // Update the local folder's totals and UID values after // normalisation, so it does not mistake the remote's current // state with our previous state @@ -1371,6 +1378,10 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport replay_queue.schedule(mark); yield mark.wait_for_ready_async(cancellable); + + // Cancel any remote update, we just updated locally unread_count, + // incoming data will have an invalid unseen value + this.account.cancel_remote_update(); } public virtual async void diff --git a/src/engine/imap-engine/imap-engine-replay-queue.vala b/src/engine/imap-engine/imap-engine-replay-queue.vala index 103900bd..7aeb5e96 100644 --- a/src/engine/imap-engine/imap-engine-replay-queue.vala +++ b/src/engine/imap-engine/imap-engine-replay-queue.vala @@ -397,6 +397,24 @@ private class Geary.ImapEngine.ReplayQueue : BaseObject, Logging.Source { closed(); } + /** + * Get pending unread change. + */ + public int pending_unread_change() { + int unread_change = 0; + Gee.Collection replay_ops = traverse( + remote_queue.get_all() + ).to_array_list(); + replay_ops.add(this.remote_op_active); + foreach (ReplayOperation op in replay_ops) { + if (op is ImapEngine.MarkEmail) { + ImapEngine.MarkEmail mark_email = op as ImapEngine.MarkEmail; + unread_change += mark_email.unread_change; + } + } + return unread_change; + } + /** {@inheritDoc} */ public Logging.State to_logging_state() { return new Logging.State( @@ -447,7 +465,7 @@ private class Geary.ImapEngine.ReplayQueue : BaseObject, Logging.Source { break; } - local_op_active = op; + this.local_op_active = op; // If this is a Close operation, shut down the queue after processing it if (op is CloseReplayQueue) @@ -526,7 +544,7 @@ private class Geary.ImapEngine.ReplayQueue : BaseObject, Logging.Source { failed(op); } - local_op_active = null; + this.local_op_active = null; } debug("ReplayQueue.do_replay_local_async %s exiting", to_string()); @@ -547,7 +565,7 @@ private class Geary.ImapEngine.ReplayQueue : BaseObject, Logging.Source { break; } - remote_op_active = op; + this.remote_op_active = op; // ReplayClose means this queue (and the folder) are closing, so handle errors a little // differently @@ -641,7 +659,7 @@ private class Geary.ImapEngine.ReplayQueue : BaseObject, Logging.Source { else failed(op); - remote_op_active = null; + this.remote_op_active = null; } debug("ReplayQueue.do_replay_remote_async %s exiting", to_string()); diff --git a/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala index e8017761..694b953f 100644 --- a/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala +++ b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala @@ -13,6 +13,8 @@ private class Geary.ImapEngine.MarkEmail : Geary.ImapEngine.SendReplayOperation private Gee.Map? original_flags = null; private Cancellable? cancellable; + internal int unread_change { get; private set; default=0; } + public MarkEmail(MinimalFolder engine, Gee.Collection to_mark, EmailFlags? flags_to_add, @@ -46,8 +48,12 @@ private class Geary.ImapEngine.MarkEmail : Geary.ImapEngine.SendReplayOperation if (original_flags == null || original_flags.size == 0) return ReplayOperation.Status.COMPLETED; - yield engine.local_folder.mark_email_async(original_flags.keys, flags_to_add, flags_to_remove, - cancellable); + this.unread_change = yield engine.local_folder.mark_email_async( + original_flags.keys, + flags_to_add, + flags_to_remove, + cancellable + ); // We can't rely on email identifier for remote replay // An email identifier id can match multiple uids @@ -71,6 +77,7 @@ private class Geary.ImapEngine.MarkEmail : Geary.ImapEngine.SendReplayOperation msg_sets, flags_to_add, flags_to_remove, cancellable ); } + this.unread_change = 0; } public override async void backout_local_async() throws Error {