1use std::time::Duration;
2
3use anyhow::{Context as _, Result};
4use async_channel::Receiver;
5use async_imap::extensions::idle::IdleResponse;
6use tokio::time::timeout;
7
8use super::Imap;
9use super::session::Session;
10use crate::context::Context;
11use crate::log::warn;
12use crate::net::TIMEOUT;
13use crate::tools::{self, time_elapsed};
14
15const IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
22
23impl Session {
24 pub async fn idle(
25 mut self,
26 context: &Context,
27 idle_interrupt_receiver: Receiver<()>,
28 folder: &str,
29 ) -> Result<Self> {
30 self.select_with_uidvalidity(context, folder).await?;
31
32 if self.drain_unsolicited_responses(context)? {
33 self.new_mail = true;
34 }
35
36 if self.new_mail {
37 info!(
38 context,
39 "Skipping IDLE in {folder:?} because there may be new mail."
40 );
41 return Ok(self);
42 }
43
44 if let Ok(()) = idle_interrupt_receiver.try_recv() {
45 info!(context, "Skip IDLE in {folder:?} because we got interrupt.");
46 return Ok(self);
47 }
48
49 let mut handle = self.inner.idle();
50 handle
51 .init()
52 .await
53 .with_context(|| format!("IMAP IDLE protocol failed to init in folder {folder:?}"))?;
54
55 handle.as_mut().set_read_timeout(None);
60 let (idle_wait, interrupt) = handle.wait_with_timeout(IDLE_TIMEOUT);
61
62 info!(
63 context,
64 "IDLE entering wait-on-remote state in folder {folder:?}."
65 );
66
67 let interrupt_relay = {
70 let context = context.clone();
71 let folder = folder.to_string();
72
73 tokio::spawn(async move {
74 idle_interrupt_receiver.recv().await.ok();
75
76 info!(context, "{folder:?}: Received interrupt, stopping IDLE.");
77
78 drop(interrupt);
80 })
81 };
82
83 match idle_wait.await {
84 Ok(IdleResponse::NewData(x)) => {
85 info!(context, "{folder:?}: Idle has NewData {x:?}");
86 }
87 Ok(IdleResponse::Timeout) => {
88 info!(context, "{folder:?}: Idle-wait timeout or interruption.");
89 }
90 Ok(IdleResponse::ManualInterrupt) => {
91 info!(context, "{folder:?}: Idle wait was interrupted manually.");
92 }
93 Err(err) => {
94 warn!(context, "{folder:?}: Idle wait errored: {err:?}.");
95 }
96 }
97
98 interrupt_relay.abort();
100 interrupt_relay.await.ok();
101
102 let mut session = tokio::time::timeout(Duration::from_secs(15), handle.done())
103 .await
104 .with_context(|| format!("{folder}: IMAP IDLE protocol timed out"))?
105 .with_context(|| format!("{folder}: IMAP IDLE failed"))?;
106 session.as_mut().set_read_timeout(Some(TIMEOUT));
107 self.inner = session;
108
109 self.new_mail = true;
111
112 Ok(self)
113 }
114}
115
116impl Imap {
117 pub(crate) async fn fake_idle(
119 &mut self,
120 context: &Context,
121 watch_folder: String,
122 ) -> Result<()> {
123 let fake_idle_start_time = tools::Time::now();
124
125 info!(context, "IMAP-fake-IDLEing folder={:?}", watch_folder);
126
127 match timeout(Duration::from_secs(60), self.idle_interrupt_receiver.recv()).await {
129 Err(_) => info!(context, "Fake IDLE finished."),
130 Ok(_) => info!(context, "Fake IDLE interrupted."),
131 }
132
133 info!(
134 context,
135 "IMAP-fake-IDLE done after {:.4}s",
136 time_elapsed(&fake_idle_start_time).as_millis() as f64 / 1000.,
137 );
138 Ok(())
139 }
140}