Skip to main content

io_email/message/maildir/
move.rs

1//! Maildir message-move coroutine: chains one MaildirEntryMove per
2//! id between Maildirs; target subdir mirrors the source subdir.
3//!
4//! # Example
5//!
6//! ```rust,ignore
7//! use io_email::message::maildir::r#move::MaildirMessageMove;
8//!
9//! client.run(MaildirMessageMove::new(&client.store, "INBOX", "Archive", &["msg-id"])?)?;
10//! ```
11
12use alloc::{collections::VecDeque, string::String};
13
14use io_maildir::{
15    coroutine::*,
16    entry::r#move::{MaildirEntryMove as InnerMove, MaildirEntryMoveError as InnerErr},
17    maildir::types::Maildir,
18    store::MaildirStore,
19};
20use log::trace;
21use thiserror::Error;
22
23use crate::maildir::convert::{InvalidMailboxName, mailbox_path};
24
25/// Errors produced by [`MaildirMessageMove`].
26#[derive(Debug, Error)]
27pub enum MaildirMessageMoveError {
28    #[error(transparent)]
29    Move(#[from] InnerErr),
30    #[error(transparent)]
31    InvalidMailbox(#[from] InvalidMailboxName),
32}
33
34/// I/O-free coroutine moving every id from `from` to `to`.
35pub struct MaildirMessageMove {
36    source: Maildir,
37    target: Maildir,
38    pending: VecDeque<String>,
39    current: Option<InnerMove>,
40}
41
42impl MaildirMessageMove {
43    pub fn new(
44        store: &MaildirStore,
45        from: &str,
46        to: &str,
47        ids: &[&str],
48    ) -> Result<Self, MaildirMessageMoveError> {
49        trace!("prepare Maildir message move");
50        let source = Maildir::from_path(store.resolve(&mailbox_path(from)?));
51        let target = Maildir::from_path(store.resolve(&mailbox_path(to)?));
52        Ok(Self {
53            source,
54            target,
55            pending: ids.iter().map(|s| (*s).into()).collect(),
56            current: None,
57        })
58    }
59}
60
61impl MaildirCoroutine for MaildirMessageMove {
62    type Yield = MaildirYield;
63    type Return = Result<(), MaildirMessageMoveError>;
64
65    fn resume(
66        &mut self,
67        arg: Option<MaildirReply>,
68    ) -> MaildirCoroutineState<Self::Yield, Self::Return> {
69        let mut arg = arg;
70        loop {
71            if self.current.is_none() {
72                let Some(id) = self.pending.pop_front() else {
73                    return MaildirCoroutineState::Complete(Ok(()));
74                };
75                self.current = Some(InnerMove::new(
76                    id,
77                    self.source.clone(),
78                    self.target.clone(),
79                    None,
80                ));
81            }
82
83            let inner = self.current.as_mut().unwrap();
84            match inner.resume(arg.take()) {
85                MaildirCoroutineState::Complete(Ok(())) => {
86                    self.current = None;
87                }
88                MaildirCoroutineState::Yielded(y) => return MaildirCoroutineState::Yielded(y),
89                MaildirCoroutineState::Complete(Err(err)) => {
90                    return MaildirCoroutineState::Complete(Err(err.into()));
91                }
92            }
93        }
94    }
95}