Skip to main content

io_email/message/imap/
copy.rs

1//! IMAP message-copy coroutine: optional SELECT then UID COPY (RFC
2//! 3501 ยง6.4.7).
3//!
4//! # Example
5//!
6//! ```rust,ignore
7//! use io_email::message::imap::copy::ImapMessageCopy;
8//!
9//! client.run(ImapMessageCopy::new("INBOX", "Archive", &["12"], true)?)?;
10//! ```
11
12use alloc::string::String;
13use core::mem;
14
15use io_imap::{
16    codec::fragmentizer::Fragmentizer,
17    coroutine::{ImapCoroutine, ImapCoroutineState, ImapYield},
18    rfc3501::{
19        copy::{
20            ImapMessageCopy as InnerImapMessageCopy,
21            ImapMessageCopyError as InnerImapMessageCopyError, ImapMessageCopyOptions,
22        },
23        select::{ImapMailboxSelect, ImapMailboxSelectError, ImapMailboxSelectOptions},
24    },
25};
26use log::trace;
27use thiserror::Error;
28
29use crate::imap::convert::{InvalidMailboxName, InvalidUidSet, parse_mailbox, parse_uids};
30
31/// Errors produced by [`ImapMessageCopy`].
32#[derive(Debug, Error)]
33pub enum ImapMessageCopyError {
34    #[error(transparent)]
35    Select(#[from] ImapMailboxSelectError),
36    #[error(transparent)]
37    Copy(#[from] InnerImapMessageCopyError),
38    #[error("invalid IMAP mailbox `{0}`")]
39    InvalidMailbox(String),
40    #[error("invalid message UID `{0}`")]
41    InvalidUid(String),
42    #[error("empty UID set")]
43    EmptyUidSet,
44    #[error("coroutine was resumed after completion")]
45    ResumedAfterDone,
46}
47
48impl From<InvalidMailboxName> for ImapMessageCopyError {
49    fn from(err: InvalidMailboxName) -> Self {
50        Self::InvalidMailbox(err.0)
51    }
52}
53
54impl From<InvalidUidSet> for ImapMessageCopyError {
55    fn from(err: InvalidUidSet) -> Self {
56        match err {
57            InvalidUidSet::Empty => Self::EmptyUidSet,
58            InvalidUidSet::Invalid(s) => Self::InvalidUid(s),
59        }
60    }
61}
62
63/// I/O-free coroutine copying a UID set across mailboxes.
64pub struct ImapMessageCopy {
65    state: State,
66}
67
68impl ImapMessageCopy {
69    pub fn new(
70        from: &str,
71        to: &str,
72        ids: &[&str],
73        auto_select: bool,
74    ) -> Result<Self, ImapMessageCopyError> {
75        trace!("prepare IMAP message copy (auto_select={auto_select})");
76        let src = parse_mailbox(from)?;
77        let dst = parse_mailbox(to)?;
78        let sequence_set = parse_uids(ids)?;
79        let copy =
80            InnerImapMessageCopy::new(sequence_set, dst, ImapMessageCopyOptions { uid: true });
81        let state = if auto_select {
82            State::Selecting {
83                select: ImapMailboxSelect::new(src, ImapMailboxSelectOptions::default()),
84                copy,
85            }
86        } else {
87            State::Copying(copy)
88        };
89        Ok(Self { state })
90    }
91}
92
93#[allow(clippy::large_enum_variant)] // see flag_store.rs for rationale
94enum State {
95    Selecting {
96        select: ImapMailboxSelect,
97        copy: InnerImapMessageCopy,
98    },
99    Copying(InnerImapMessageCopy),
100    Done,
101}
102
103impl ImapCoroutine for ImapMessageCopy {
104    type Yield = ImapYield;
105    type Return = Result<(), ImapMessageCopyError>;
106
107    fn resume(
108        &mut self,
109        fragmentizer: &mut Fragmentizer,
110        mut bytes: Option<&[u8]>,
111    ) -> ImapCoroutineState<Self::Yield, Self::Return> {
112        loop {
113            match mem::replace(&mut self.state, State::Done) {
114                State::Selecting { mut select, copy } => {
115                    match select.resume(fragmentizer, bytes.take()) {
116                        ImapCoroutineState::Yielded(yielded) => {
117                            self.state = State::Selecting { select, copy };
118                            return ImapCoroutineState::Yielded(yielded);
119                        }
120                        ImapCoroutineState::Complete(Ok(_)) => {
121                            self.state = State::Copying(copy);
122                        }
123                        ImapCoroutineState::Complete(Err(err)) => {
124                            return ImapCoroutineState::Complete(Err(err.into()));
125                        }
126                    }
127                }
128                State::Copying(mut copy) => match copy.resume(fragmentizer, bytes.take()) {
129                    ImapCoroutineState::Yielded(yielded) => {
130                        self.state = State::Copying(copy);
131                        return ImapCoroutineState::Yielded(yielded);
132                    }
133                    ImapCoroutineState::Complete(Ok(_copyuid)) => {
134                        return ImapCoroutineState::Complete(Ok(()));
135                    }
136                    ImapCoroutineState::Complete(Err(err)) => {
137                        return ImapCoroutineState::Complete(Err(err.into()));
138                    }
139                },
140                State::Done => {
141                    return ImapCoroutineState::Complete(Err(
142                        ImapMessageCopyError::ResumedAfterDone,
143                    ));
144                }
145            }
146        }
147    }
148}