Skip to main content

io_email/message/maildir/
copy.rs

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