Skip to main content

io_email/message/m2dir/
move.rs

1//! m2dir message-move coroutine: per id chains [`M2dirEntryGet`] +
2//! [`M2dirEntryStore`] + [`M2dirEntryDelete`].
3//!
4//! Flags are not propagated; the .meta/\<id\>.flags sidecar must be
5//! re-applied after the move when needed.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use io_email::message::m2dir::r#move::M2dirMessageMove;
11//!
12//! client.run(M2dirMessageMove::new(&client.root, "INBOX", "Archive", &["msg-id"])?)?;
13//! ```
14//!
15//! [`M2dirEntryGet`]: io_m2dir::entry::get::M2dirEntryGet
16//! [`M2dirEntryStore`]: io_m2dir::entry::store::M2dirEntryStore
17//! [`M2dirEntryDelete`]: io_m2dir::entry::delete::M2dirEntryDelete
18
19use alloc::{collections::VecDeque, string::String};
20use core::mem;
21use std::path::PathBuf;
22
23use io_m2dir::{
24    coroutine::*,
25    entry::{
26        delete::{
27            M2dirEntryDelete as InnerDelete, M2dirEntryDeleteError as DeleteErr,
28            M2dirEntryDeleteOptions as DeleteOpts,
29        },
30        get::{
31            M2dirEntryGet as InnerGet, M2dirEntryGetError as GetErr,
32            M2dirEntryGetOptions as GetOpts,
33        },
34        store::{
35            M2dirEntryStore as InnerStore, M2dirEntryStoreError as StoreErr,
36            M2dirEntryStoreOptions as StoreOpts,
37        },
38    },
39    m2dir::types::M2dir,
40};
41use log::trace;
42use thiserror::Error;
43
44use crate::m2dir::convert::{InvalidMailboxName, resolve_mailbox};
45
46/// Errors produced by [`M2dirMessageMove`].
47#[derive(Debug, Error)]
48pub enum M2dirMessageMoveError {
49    #[error(transparent)]
50    Get(#[from] GetErr),
51    #[error(transparent)]
52    Store(#[from] StoreErr),
53    #[error(transparent)]
54    Delete(#[from] DeleteErr),
55    #[error(transparent)]
56    InvalidMailbox(#[from] InvalidMailboxName),
57}
58
59/// I/O-free coroutine moving every id from `from` to `to`.
60pub struct M2dirMessageMove {
61    source: M2dir,
62    target: M2dir,
63    pending: VecDeque<String>,
64    current_id: Option<String>,
65    stage: Stage,
66}
67
68impl M2dirMessageMove {
69    pub fn new(
70        root: impl Into<PathBuf>,
71        from: &str,
72        to: &str,
73        ids: &[&str],
74    ) -> Result<Self, M2dirMessageMoveError> {
75        trace!("prepare m2dir message move");
76        let root = root.into();
77        let source = resolve_mailbox(&root, from)?;
78        let target = resolve_mailbox(&root, to)?;
79        Ok(Self {
80            source,
81            target,
82            pending: ids.iter().map(|s| (*s).into()).collect(),
83            current_id: None,
84            stage: Stage::Idle,
85        })
86    }
87}
88
89enum Stage {
90    Idle,
91    Getting(InnerGet),
92    Storing(InnerStore),
93    Deleting(InnerDelete),
94}
95
96impl M2dirCoroutine for M2dirMessageMove {
97    type Yield = M2dirYield;
98    type Return = Result<(), M2dirMessageMoveError>;
99
100    fn resume(&mut self, arg: Option<M2dirArg>) -> M2dirCoroutineState<Self::Yield, Self::Return> {
101        let mut arg = arg;
102        loop {
103            if matches!(self.stage, Stage::Idle) {
104                let Some(id) = self.pending.pop_front() else {
105                    return M2dirCoroutineState::Complete(Ok(()));
106                };
107                self.stage = Stage::Getting(InnerGet::new(
108                    self.source.clone(),
109                    id.clone(),
110                    GetOpts::default(),
111                ));
112                self.current_id = Some(id);
113            }
114            match mem::replace(&mut self.stage, Stage::Idle) {
115                Stage::Idle => unreachable!(),
116                Stage::Getting(mut get) => match get.resume(arg.take()) {
117                    M2dirCoroutineState::Yielded(y) => {
118                        self.stage = Stage::Getting(get);
119                        return M2dirCoroutineState::Yielded(y);
120                    }
121                    M2dirCoroutineState::Complete(Ok(ok)) => {
122                        self.stage = Stage::Storing(InnerStore::new(
123                            self.target.clone(),
124                            ok.contents,
125                            StoreOpts::default(),
126                        ));
127                    }
128                    M2dirCoroutineState::Complete(Err(err)) => {
129                        return M2dirCoroutineState::Complete(Err(err.into()));
130                    }
131                },
132                Stage::Storing(mut store) => match store.resume(arg.take()) {
133                    M2dirCoroutineState::Yielded(y) => {
134                        self.stage = Stage::Storing(store);
135                        return M2dirCoroutineState::Yielded(y);
136                    }
137                    M2dirCoroutineState::Complete(Ok(_entry)) => {
138                        let id = self.current_id.take().expect("current_id set when storing");
139                        self.stage = Stage::Deleting(InnerDelete::new(
140                            self.source.clone(),
141                            id,
142                            DeleteOpts::default(),
143                        ));
144                    }
145                    M2dirCoroutineState::Complete(Err(err)) => {
146                        return M2dirCoroutineState::Complete(Err(err.into()));
147                    }
148                },
149                Stage::Deleting(mut delete) => match delete.resume(arg.take()) {
150                    M2dirCoroutineState::Yielded(y) => {
151                        self.stage = Stage::Deleting(delete);
152                        return M2dirCoroutineState::Yielded(y);
153                    }
154                    M2dirCoroutineState::Complete(Ok(())) => {
155                        // NOTE: loop back to the next id or finish.
156                    }
157                    M2dirCoroutineState::Complete(Err(err)) => {
158                        return M2dirCoroutineState::Complete(Err(err.into()));
159                    }
160                },
161            }
162        }
163    }
164}