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