diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py
index ad4d65eaa..5d595b9e8 100644
--- a/python/tests/test_1_online.py
+++ b/python/tests/test_1_online.py
@@ -1979,6 +1979,32 @@ def test_connectivity(acfactory, lp):
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
+def test_all_work_done(acfactory, lp):
+ """
+ Tests that calling start_io() immediately followed by maybe_network()
+ and then waiting for all_work_done() reliably fetches the messages
+ delivered while account was offline.
+ In other words, connectivity should not change to a state
+ where all_work_done() returns true until IMAP connection goes idle.
+ """
+ ac1, ac2 = acfactory.get_online_accounts(2)
+
+ ac1.stop_io()
+ ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
+
+ ac1.direct_imap.select_config_folder("inbox")
+ with ac1.direct_imap.idle() as idle1:
+ ac2.create_chat(ac1).send_text("Hi")
+ idle1.wait_for_new_message()
+
+ ac1.start_io()
+ ac1.maybe_network()
+ ac1._evtracker.wait_for_all_work_done()
+ msgs = ac1.create_chat(ac2).get_messages()
+ assert len(msgs) == 1
+ assert msgs[0].text == "Hi"
+
+
def test_fetch_deleted_msg(acfactory, lp):
"""This is a regression test: Messages with \\Deleted flag were downloaded again and again,
hundreds of times, because uid_next was not updated.
diff --git a/src/scheduler.rs b/src/scheduler.rs
index b41bb0d0e..e31fc99ba 100644
--- a/src/scheduler.rs
+++ b/src/scheduler.rs
@@ -590,7 +590,7 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder_meaning: Folder
.log_err(ctx)
.ok();
- connection.connectivity.set_connected(ctx).await;
+ connection.connectivity.set_idle(ctx).await;
ctx.emit_event(EventType::ImapInboxIdle);
let Some(session) = connection.session.take() else {
@@ -727,7 +727,7 @@ async fn smtp_loop(
// Fake Idle
info!(ctx, "smtp fake idle - started");
match &connection.last_send_error {
- None => connection.connectivity.set_connected(&ctx).await,
+ None => connection.connectivity.set_idle(&ctx).await,
Some(err) => connection.connectivity.set_err(&ctx, err).await,
}
diff --git a/src/scheduler/connectivity.rs b/src/scheduler/connectivity.rs
index 104cdd584..bbe629f87 100644
--- a/src/scheduler/connectivity.rs
+++ b/src/scheduler/connectivity.rs
@@ -33,10 +33,19 @@ enum DetailedConnectivity {
#[default]
Uninitialized,
Connecting,
- Working,
- InterruptingIdle,
+
+ /// Connection is just established, but there may be work to do.
Connected,
+ /// There is actual work to do, e.g. there are messages in SMTP queue
+ /// or we detected a message that should be downloaded.
+ Working,
+
+ InterruptingIdle,
+
+ /// Connection is established and is idle.
+ Idle,
+
/// The folder was configured not to be watched or configured_*_folder is not set
NotConfigured,
}
@@ -54,6 +63,8 @@ impl DetailedConnectivity {
// Just don't return a connectivity, probably the folder is configured not to be
// watched or there is e.g. no "Sent" folder, so we are not interested in it
DetailedConnectivity::NotConfigured => None,
+
+ DetailedConnectivity::Idle => Some(Connectivity::Connected),
}
}
@@ -65,7 +76,8 @@ impl DetailedConnectivity {
DetailedConnectivity::Connecting => "".to_string(),
DetailedConnectivity::Working
| DetailedConnectivity::InterruptingIdle
- | DetailedConnectivity::Connected => "".to_string(),
+ | DetailedConnectivity::Connected
+ | DetailedConnectivity::Idle => "".to_string(),
}
}
@@ -75,9 +87,9 @@ impl DetailedConnectivity {
DetailedConnectivity::Uninitialized => "Not started".to_string(),
DetailedConnectivity::Connecting => stock_str::connecting(context).await,
DetailedConnectivity::Working => stock_str::updating(context).await,
- DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Connected => {
- stock_str::connected(context).await
- }
+ DetailedConnectivity::InterruptingIdle
+ | DetailedConnectivity::Connected
+ | DetailedConnectivity::Idle => stock_str::connected(context).await,
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
}
}
@@ -94,9 +106,9 @@ impl DetailedConnectivity {
// We don't know any more than that the last message was sent successfully;
// since sending the last message, connectivity could have changed, which we don't notice
// until another message is sent
- DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Connected => {
- stock_str::last_msg_sent_successfully(context).await
- }
+ DetailedConnectivity::InterruptingIdle
+ | DetailedConnectivity::Connected
+ | DetailedConnectivity::Idle => stock_str::last_msg_sent_successfully(context).await,
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
}
}
@@ -108,8 +120,9 @@ impl DetailedConnectivity {
DetailedConnectivity::Connecting => false,
DetailedConnectivity::Working => false,
DetailedConnectivity::InterruptingIdle => false,
- DetailedConnectivity::Connected => true,
+ DetailedConnectivity::Connected => false, // Just connected, there may still be work to do.
DetailedConnectivity::NotConfigured => true,
+ DetailedConnectivity::Idle => true,
}
}
}
@@ -141,6 +154,9 @@ impl ConnectivityStore {
pub(crate) async fn set_not_configured(&self, context: &Context) {
self.set(context, DetailedConnectivity::NotConfigured).await;
}
+ pub(crate) async fn set_idle(&self, context: &Context) {
+ self.set(context, DetailedConnectivity::Idle).await;
+ }
async fn get_detailed(&self) -> DetailedConnectivity {
self.0.lock().await.deref().clone()
@@ -164,6 +180,7 @@ pub(crate) async fn idle_interrupted(inbox: ConnectivityStore, oboxes: Vec