Skip to content

Commit 1e2d406

Browse files
committed
fix: check UIDNEXT with a STATUS command before going IDLE
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.
1 parent 9cd3a75 commit 1e2d406

File tree

1 file changed

+26
-1
lines changed

1 file changed

+26
-1
lines changed

src/imap/idle.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use futures_lite::FutureExt;
88
use super::session::Session;
99
use super::Imap;
1010
use crate::config::Config;
11-
use crate::imap::{client::IMAP_TIMEOUT, FolderMeaning};
11+
use crate::imap::{client::IMAP_TIMEOUT, get_uid_next, FolderMeaning};
1212
use crate::log::LogExt;
1313
use crate::{context::Context, scheduler::InterruptInfo};
1414

@@ -39,6 +39,31 @@ impl Session {
3939
return Ok((self, info));
4040
}
4141

42+
if let Some(folder) = watch_folder.as_ref() {
43+
// Despite checking for unsolicited EXISTS above,
44+
// we may have missed EXISTS if the message was
45+
// received when the folder was not selected.
46+
let status = self
47+
.status(folder, "(UIDNEXT)")
48+
.await
49+
.context("STATUS (UIDNEXT) error for {folder:?}")?;
50+
if let Some(uid_next) = status.uid_next {
51+
let expected_uid_next = get_uid_next(context, folder)
52+
.await
53+
.with_context(|| format!("failed to get old UID NEXT for folder {folder}"))?;
54+
if uid_next > expected_uid_next {
55+
info!(
56+
context,
57+
"Skipping IDLE because UIDNEXT indicates there are new messages."
58+
);
59+
return Ok((self, info));
60+
}
61+
} else {
62+
warn!(context, "STATUS {folder} (UIDNEXT) did not return UIDNEXT");
63+
// Go to IDLE anyway if STATUS is broken.
64+
}
65+
}
66+
4267
if let Ok(info) = idle_interrupt_receiver.try_recv() {
4368
info!(context, "skip idle, got interrupt {:?}", info);
4469
return Ok((self, info));

0 commit comments

Comments
 (0)