1use core::{
67 mem,
68 num::{NonZeroU32, NonZeroU64},
69 sync::atomic::{AtomicBool, Ordering},
70};
71
72use alloc::{
73 collections::{BTreeMap, VecDeque},
74 string::String,
75 sync::Arc,
76 vec,
77 vec::Vec,
78};
79
80use imap_codec::{
81 fragmentizer::Fragmentizer,
82 imap_types::{
83 command::SelectParameter,
84 core::{Atom, Vec1},
85 extensions::enable::CapabilityEnable,
86 fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
87 flag::{Flag, FlagFetch},
88 mailbox::Mailbox,
89 response::Capability,
90 sequence::SequenceSet,
91 },
92};
93use log::{debug, trace};
94use thiserror::Error;
95
96use crate::{
97 coroutine::*,
98 rfc2177::idle::{ImapIdle, ImapIdleError, ImapIdleOptions, ImapIdleYield},
99 rfc3501::{
100 examine::{ImapMailboxExamine, ImapMailboxExamineError, ImapMailboxExamineOptions},
101 fetch::{ImapMessageFetch, ImapMessageFetchError, ImapMessageFetchOptions},
102 select::ImapMailboxSelectData,
103 },
104 rfc5161::enable::{ImapExtensionEnable, ImapExtensionEnableError},
105};
106
107#[derive(Clone, Debug)]
112pub enum ImapMailboxWatchEvent {
113 EnvelopeAdded {
115 uid: NonZeroU32,
117 items: Vec<MessageDataItem<'static>>,
119 },
120 FlagsAdded {
122 uid: NonZeroU32,
124 flags: Vec<Flag<'static>>,
126 },
127 FlagsRemoved {
129 uid: NonZeroU32,
131 flags: Vec<Flag<'static>>,
133 },
134 EnvelopeRemoved {
136 uid: NonZeroU32,
138 },
139}
140
141#[derive(Debug, Error)]
143pub enum ImapMailboxWatchError {
144 #[error("IMAP server does not advertise QRESYNC")]
146 QresyncUnsupported,
147 #[error("IMAP server did not return UIDVALIDITY in EXAMINE response")]
150 MissingUidValidity,
151 #[error("IMAP server did not return HIGHESTMODSEQ in EXAMINE response")]
154 MissingHighestModSeq,
155 #[error("Invalid `1:*` sequence set: {0}")]
157 InvalidSequenceSet(String),
158 #[error("IMAP EXAMINE error")]
160 Examine(#[from] ImapMailboxExamineError),
161 #[error("IMAP FETCH error")]
163 Fetch(#[from] ImapMessageFetchError),
164 #[error("IMAP IDLE error")]
166 Idle(#[from] ImapIdleError),
167 #[error("IMAP ENABLE error")]
169 Enable(#[from] ImapExtensionEnableError),
170}
171
172#[derive(Debug)]
174pub enum ImapMailboxWatchYield {
175 WantsRead,
177 WantsWrite(Vec<u8>),
179 Event(ImapMailboxWatchEvent),
181}
182
183enum State {
184 EnableQresync(ImapExtensionEnable),
185 ExamineInitial(ImapMailboxExamine),
186 FetchBaseline(ImapMessageFetch),
187 BeginIdle,
188 Idle(ImapIdle),
189 ExamineQresync(ImapMailboxExamine),
190 EmitDeltas,
191 Terminal,
192}
193
194pub struct ImapMailboxWatch {
196 state: State,
197 shutdown: Arc<AtomicBool>,
198 idle_done: Arc<AtomicBool>,
199 idle_saw_data: bool,
200 mailbox: Mailbox<'static>,
201 uid_validity: Option<NonZeroU32>,
202 highest_mod_seq: u64,
203 shadow: BTreeMap<NonZeroU32, Vec<Flag<'static>>>,
204 pending: VecDeque<ImapMailboxWatchEvent>,
205}
206
207impl ImapMailboxWatch {
208 pub fn new(
210 capability: &[Capability<'static>],
211 mailbox: Mailbox<'static>,
212 shutdown: Arc<AtomicBool>,
213 ) -> Result<Self, ImapMailboxWatchError> {
214 if !capability.contains(&Capability::QResync) {
215 return Err(ImapMailboxWatchError::QresyncUnsupported);
216 }
217
218 let condstore = CapabilityEnable::CondStore;
221 let qresync = CapabilityEnable::from(
223 Atom::try_from("QRESYNC").expect("`QRESYNC` is a syntactically valid IMAP atom"),
224 );
225 let capabilities =
226 Vec1::try_from(vec![condstore, qresync]).expect("two capabilities is non-empty");
227 let enable = ImapExtensionEnable::new(capabilities);
228
229 Ok(Self {
230 state: State::EnableQresync(enable),
231 shutdown,
232 idle_done: Arc::new(AtomicBool::new(false)),
233 idle_saw_data: false,
234 mailbox,
235 uid_validity: None,
236 highest_mod_seq: 0,
237 shadow: BTreeMap::new(),
238 pending: VecDeque::new(),
239 })
240 }
241
242 fn compute_deltas(&mut self, data: &ImapMailboxSelectData) {
243 for uid in &data.vanished_earlier {
244 if self.shadow.remove(uid).is_some() {
245 self.pending
246 .push_back(ImapMailboxWatchEvent::EnvelopeRemoved { uid: *uid });
247 }
248 }
249
250 for fetch in &data.changed {
251 let items_vec: Vec<MessageDataItem<'static>> =
252 fetch.items.clone().into_inner().into_iter().collect();
253 let (uid_opt, new_flags) = extract_uid_flags(&items_vec);
254 let Some(uid) = uid_opt else {
255 continue;
256 };
257
258 match self.shadow.get(&uid).cloned() {
259 None => {
260 self.shadow.insert(uid, new_flags);
261 self.pending
262 .push_back(ImapMailboxWatchEvent::EnvelopeAdded {
263 uid,
264 items: items_vec,
265 });
266 }
267 Some(old_flags) => {
268 let added: Vec<Flag<'static>> = new_flags
269 .iter()
270 .filter(|f| !old_flags.contains(f))
271 .cloned()
272 .collect();
273 let removed: Vec<Flag<'static>> = old_flags
274 .iter()
275 .filter(|f| !new_flags.contains(f))
276 .cloned()
277 .collect();
278 self.shadow.insert(uid, new_flags);
279 if !added.is_empty() {
280 self.pending
281 .push_back(ImapMailboxWatchEvent::FlagsAdded { uid, flags: added });
282 }
283 if !removed.is_empty() {
284 self.pending.push_back(ImapMailboxWatchEvent::FlagsRemoved {
285 uid,
286 flags: removed,
287 });
288 }
289 }
290 }
291 }
292 }
293}
294
295impl ImapCoroutine for ImapMailboxWatch {
296 type Yield = ImapMailboxWatchYield;
297 type Return = Result<(), ImapMailboxWatchError>;
298
299 fn resume(
300 &mut self,
301 fragmentizer: &mut Fragmentizer,
302 mut arg: Option<&[u8]>,
303 ) -> ImapCoroutineState<Self::Yield, Self::Return> {
304 if self.shutdown.load(Ordering::SeqCst) {
305 self.idle_done.store(true, Ordering::SeqCst);
306 }
307
308 loop {
309 let state = mem::replace(&mut self.state, State::Terminal);
310
311 match state {
312 State::EnableQresync(mut enable) => match enable.resume(fragmentizer, arg.take()) {
313 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
314 self.state = State::EnableQresync(enable);
315 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsRead);
316 }
317 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
318 self.state = State::EnableQresync(enable);
319 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsWrite(
320 bytes,
321 ));
322 }
323 ImapCoroutineState::Complete(Ok(enabled)) => {
324 debug!("enabled qresync");
325 trace!("{enabled:?}");
326 let parameters = vec![SelectParameter::CondStore];
327 let examine = ImapMailboxExamine::new(
328 self.mailbox.clone(),
329 ImapMailboxExamineOptions { parameters },
330 );
331 self.state = State::ExamineInitial(examine);
332 }
333 ImapCoroutineState::Complete(Err(err)) => {
334 return ImapCoroutineState::Complete(Err(err.into()));
335 }
336 },
337
338 State::ExamineInitial(mut examine) => {
339 match examine.resume(fragmentizer, arg.take()) {
340 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
341 self.state = State::ExamineInitial(examine);
342 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsRead);
343 }
344 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
345 self.state = State::ExamineInitial(examine);
346 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsWrite(
347 bytes,
348 ));
349 }
350 ImapCoroutineState::Complete(Ok(data)) => {
351 let Some(uid_validity) = data.uid_validity else {
352 return ImapCoroutineState::Complete(Err(
353 ImapMailboxWatchError::MissingUidValidity,
354 ));
355 };
356 let Some(highest_mod_seq) = data.highest_mod_seq else {
357 return ImapCoroutineState::Complete(Err(
358 ImapMailboxWatchError::MissingHighestModSeq,
359 ));
360 };
361
362 self.uid_validity = Some(uid_validity);
363 self.highest_mod_seq = highest_mod_seq;
364 debug!("examined mailbox with condstore");
365 trace!("uid_validity: {uid_validity}");
366 trace!("highest_mod_seq: {highest_mod_seq}");
367
368 let sequence_set: SequenceSet = match "1:*".try_into() {
369 Ok(s) => s,
370 Err(_) => {
371 return ImapCoroutineState::Complete(Err(
372 ImapMailboxWatchError::InvalidSequenceSet("1:*".into()),
373 ));
374 }
375 };
376 let item_names =
377 MacroOrMessageDataItemNames::MessageDataItemNames(vec![
378 MessageDataItemName::Uid,
379 MessageDataItemName::Flags,
380 ]);
381 let fetch = ImapMessageFetch::new(
382 sequence_set,
383 item_names,
384 ImapMessageFetchOptions::default(),
385 );
386 self.state = State::FetchBaseline(fetch);
387 }
388 ImapCoroutineState::Complete(Err(err)) => {
389 return ImapCoroutineState::Complete(Err(err.into()));
390 }
391 }
392 }
393
394 State::FetchBaseline(mut fetch) => match fetch.resume(fragmentizer, arg.take()) {
395 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
396 self.state = State::FetchBaseline(fetch);
397 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsRead);
398 }
399 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
400 self.state = State::FetchBaseline(fetch);
401 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsWrite(
402 bytes,
403 ));
404 }
405 ImapCoroutineState::Complete(Ok(data)) => {
406 for (_seq, items) in data {
407 let items_vec = items.into_inner();
408 if let (Some(uid), flags) = extract_uid_flags(&items_vec) {
409 self.shadow.insert(uid, flags);
410 }
411 }
412 debug!("seeded baseline shadow");
413 trace!("uids: {}", self.shadow.len());
414 self.state = State::BeginIdle;
415 }
416 ImapCoroutineState::Complete(Err(err)) => {
417 return ImapCoroutineState::Complete(Err(err.into()));
418 }
419 },
420
421 State::BeginIdle => {
422 if self.shutdown.load(Ordering::SeqCst) {
423 return ImapCoroutineState::Complete(Ok(()));
424 }
425
426 self.idle_done.store(false, Ordering::SeqCst);
427 self.idle_saw_data = false;
428 let idle = ImapIdle::new(self.idle_done.clone(), ImapIdleOptions::default());
429 self.state = State::Idle(idle);
430 }
431
432 State::Idle(mut idle) => match idle.resume(fragmentizer, arg.take()) {
433 ImapCoroutineState::Yielded(ImapIdleYield::Event(_)) => {
434 debug!("idle saw untagged data");
435 self.idle_saw_data = true;
436 self.idle_done.store(true, Ordering::SeqCst);
437 self.state = State::Idle(idle);
438 }
439 ImapCoroutineState::Yielded(ImapIdleYield::WantsRead) => {
440 self.state = State::Idle(idle);
441 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsRead);
442 }
443 ImapCoroutineState::Yielded(ImapIdleYield::WantsWrite(bytes)) => {
444 self.state = State::Idle(idle);
445 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsWrite(
446 bytes,
447 ));
448 }
449 ImapCoroutineState::Complete(Ok(())) => {
450 if self.shutdown.load(Ordering::SeqCst) {
451 return ImapCoroutineState::Complete(Ok(()));
452 }
453
454 if self.idle_saw_data {
455 let uid_validity = self.uid_validity.unwrap();
457 let modseq = NonZeroU64::new(self.highest_mod_seq)
458 .unwrap_or_else(|| NonZeroU64::new(1).expect("1 is non-zero"));
459 let parameters = vec![SelectParameter::QResync {
460 uid_validity,
461 mod_sequence_value: modseq,
462 known_uids: None,
463 seq_match_data: None,
464 }];
465 let examine = ImapMailboxExamine::new(
466 self.mailbox.clone(),
467 ImapMailboxExamineOptions { parameters },
468 );
469 self.state = State::ExamineQresync(examine);
470 } else {
471 debug!("idle timed out with no data, restarting");
472 self.state = State::BeginIdle;
473 }
474 }
475 ImapCoroutineState::Complete(Err(err)) => {
476 return ImapCoroutineState::Complete(Err(err.into()));
477 }
478 },
479
480 State::ExamineQresync(mut examine) => {
481 match examine.resume(fragmentizer, arg.take()) {
482 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
483 self.state = State::ExamineQresync(examine);
484 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsRead);
485 }
486 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
487 self.state = State::ExamineQresync(examine);
488 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsWrite(
489 bytes,
490 ));
491 }
492 ImapCoroutineState::Complete(Ok(data)) => {
493 self.compute_deltas(&data);
494 if let Some(new_modseq) = data.highest_mod_seq {
495 self.highest_mod_seq = new_modseq;
496 }
497 self.state = State::EmitDeltas;
498 }
499 ImapCoroutineState::Complete(Err(err)) => {
500 return ImapCoroutineState::Complete(Err(err.into()));
501 }
502 }
503 }
504
505 State::EmitDeltas => {
506 if let Some(event) = self.pending.pop_front() {
507 self.state = State::EmitDeltas;
508 return ImapCoroutineState::Yielded(ImapMailboxWatchYield::Event(event));
509 }
510 self.state = State::BeginIdle;
511 }
512
513 State::Terminal => {
514 self.state = State::Terminal;
515 return ImapCoroutineState::Complete(Ok(()));
516 }
517 }
518 }
519 }
520}
521
522fn extract_uid_flags(
525 items: &[MessageDataItem<'static>],
526) -> (Option<NonZeroU32>, Vec<Flag<'static>>) {
527 let mut uid = None;
528 let mut flags = Vec::new();
529 for item in items {
530 match item {
531 MessageDataItem::Uid(u) => uid = Some(*u),
532 MessageDataItem::Flags(fs) => {
533 flags = fs
534 .iter()
535 .filter_map(|f| match f {
536 FlagFetch::Flag(flag) => Some(flag.clone()),
537 _ => None,
538 })
539 .collect();
540 }
541 _ => {}
542 }
543 }
544 (uid, flags)
545}