Skip to content

Commit

Permalink
fix: check UIDNEXT with a STATUS command before going IDLE
Browse files Browse the repository at this point in the history
This prevents accidentally going IDLE
when the last new message has arrived
while the folder was closed.
For example, this happened in some tests:
1. INBOX is selected to fetch, move and delete messages.
2. One of the messages is deleted.
3. INBOX is closed to expunge the message.
4. A new message arrives.
5. INBOX is selected with (CONDSTORE) to sync flags.
6. Delta Chat goes into IDLE without downloading the new message.

To determine that a new message has arrived
we need to notice that UIDNEXT has advanced when selecting the folder.
However, some servers such as Winmail Pro Mail Server 5.1.0616
do not return UIDNEXT in response to SELECT command.

To avoid interdependencies with the code
SELECTing the folder and having to implement
STATUS fallback after each SELECT even when we
may not want to go IDLE due to interrupt or unsolicited EXISTS,
we simply call STATUS unconditionally before IDLE.
  • Loading branch information
link2xt authored and hpk42 committed Nov 5, 2023
1 parent ee53136 commit 2e50abe
Showing 1 changed file with 26 additions and 1 deletion.
27 changes: 26 additions & 1 deletion src/imap/idle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use futures_lite::FutureExt;
use super::session::Session;
use super::Imap;
use crate::config::Config;
use crate::imap::{client::IMAP_TIMEOUT, FolderMeaning};
use crate::imap::{client::IMAP_TIMEOUT, get_uid_next, FolderMeaning};
use crate::log::LogExt;
use crate::{context::Context, scheduler::InterruptInfo};

Expand Down Expand Up @@ -39,6 +39,31 @@ impl Session {
return Ok((self, info));
}

if let Some(folder) = watch_folder.as_ref() {
// Despite checking for unsolicited EXISTS above,
// we may have missed EXISTS if the message was
// received when the folder was not selected.
let status = self
.status(folder, "(UIDNEXT)")
.await
.context("STATUS (UIDNEXT) error for {folder:?}")?;
if let Some(uid_next) = status.uid_next {
let expected_uid_next = get_uid_next(context, folder)
.await
.with_context(|| format!("failed to get old UID NEXT for folder {folder}"))?;
if uid_next > expected_uid_next {
info!(
context,
"Skipping IDLE because UIDNEXT indicates there are new messages."
);
return Ok((self, info));
}
} else {
warn!(context, "STATUS {folder} (UIDNEXT) did not return UIDNEXT");
// Go to IDLE anyway if STATUS is broken.
}
}

if let Ok(info) = idle_interrupt_receiver.try_recv() {
info!(context, "skip idle, got interrupt {:?}", info);
return Ok((self, info));
Expand Down

0 comments on commit 2e50abe

Please sign in to comment.