Skip to main content

io_email/message/imap/
move.rs

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