Skip to main content

io_email/message/m2dir/
copy.rs

1//! m2dir message-copy coroutine: per id chains [`M2dirEntryGet`] then
2//! [`M2dirEntryStore`] between m2dirs.
3//!
4//! Flags are not propagated; callers needing flag-preserving copies
5//! must follow up with [`EmailClientStd::store_flags`].
6//!
7//! [`EmailClientStd::store_flags`]: crate::client::EmailClientStd::store_flags
8//!
9//! # Example
10//!
11//! ```rust,ignore
12//! use io_email::message::m2dir::copy::M2dirMessageCopy;
13//!
14//! client.run(M2dirMessageCopy::new(&client.root, "INBOX", "Archive", &["msg-id"])?)?;
15//! ```
16//!
17//! [`M2dirEntryGet`]: io_m2dir::entry::get::M2dirEntryGet
18//! [`M2dirEntryStore`]: io_m2dir::entry::store::M2dirEntryStore
19
20use alloc::{collections::VecDeque, string::String};
21use core::mem;
22use std::path::PathBuf;
23
24use io_m2dir::{
25    coroutine::*,
26    entry::{
27        get::{
28            M2dirEntryGet as InnerGet, M2dirEntryGetError as GetErr,
29            M2dirEntryGetOptions as GetOpts,
30        },
31        store::{
32            M2dirEntryStore as InnerStore, M2dirEntryStoreError as StoreErr,
33            M2dirEntryStoreOptions as StoreOpts,
34        },
35    },
36    m2dir::types::M2dir,
37};
38use log::trace;
39use thiserror::Error;
40
41use crate::m2dir::convert::{InvalidMailboxName, resolve_mailbox};
42
43/// Errors produced by [`M2dirMessageCopy`].
44#[derive(Debug, Error)]
45pub enum M2dirMessageCopyError {
46    #[error(transparent)]
47    Get(#[from] GetErr),
48    #[error(transparent)]
49    Store(#[from] StoreErr),
50    #[error(transparent)]
51    InvalidMailbox(#[from] InvalidMailboxName),
52}
53
54/// I/O-free coroutine copying every id from `from` to `to`.
55pub struct M2dirMessageCopy {
56    source: M2dir,
57    target: M2dir,
58    pending: VecDeque<String>,
59    stage: Stage,
60}
61
62impl M2dirMessageCopy {
63    pub fn new(
64        root: impl Into<PathBuf>,
65        from: &str,
66        to: &str,
67        ids: &[&str],
68    ) -> Result<Self, M2dirMessageCopyError> {
69        trace!("prepare m2dir message copy");
70        let root = root.into();
71        let source = resolve_mailbox(&root, from)?;
72        let target = resolve_mailbox(&root, to)?;
73        Ok(Self {
74            source,
75            target,
76            pending: ids.iter().map(|s| (*s).into()).collect(),
77            stage: Stage::Idle,
78        })
79    }
80}
81
82enum Stage {
83    Idle,
84    Getting(InnerGet),
85    Storing(InnerStore),
86}
87
88impl M2dirCoroutine for M2dirMessageCopy {
89    type Yield = M2dirYield;
90    type Return = Result<(), M2dirMessageCopyError>;
91
92    fn resume(&mut self, arg: Option<M2dirArg>) -> M2dirCoroutineState<Self::Yield, Self::Return> {
93        let mut arg = arg;
94        loop {
95            if matches!(self.stage, Stage::Idle) {
96                let Some(id) = self.pending.pop_front() else {
97                    return M2dirCoroutineState::Complete(Ok(()));
98                };
99                self.stage =
100                    Stage::Getting(InnerGet::new(self.source.clone(), id, GetOpts::default()));
101            }
102            match mem::replace(&mut self.stage, Stage::Idle) {
103                Stage::Idle => unreachable!(),
104                Stage::Getting(mut get) => match get.resume(arg.take()) {
105                    M2dirCoroutineState::Yielded(y) => {
106                        self.stage = Stage::Getting(get);
107                        return M2dirCoroutineState::Yielded(y);
108                    }
109                    M2dirCoroutineState::Complete(Ok(ok)) => {
110                        self.stage = Stage::Storing(InnerStore::new(
111                            self.target.clone(),
112                            ok.contents,
113                            StoreOpts::default(),
114                        ));
115                    }
116                    M2dirCoroutineState::Complete(Err(err)) => {
117                        return M2dirCoroutineState::Complete(Err(err.into()));
118                    }
119                },
120                Stage::Storing(mut store) => match store.resume(arg.take()) {
121                    M2dirCoroutineState::Yielded(y) => {
122                        self.stage = Stage::Storing(store);
123                        return M2dirCoroutineState::Yielded(y);
124                    }
125                    M2dirCoroutineState::Complete(Ok(_entry)) => {
126                        // NOTE: loop back to the next id or finish.
127                    }
128                    M2dirCoroutineState::Complete(Err(err)) => {
129                        return M2dirCoroutineState::Complete(Err(err.into()));
130                    }
131                },
132            }
133        }
134    }
135}