Skip to main content

io_email/message/m2dir/
add.rs

1//! m2dir message-add coroutine: wraps
2//! [`io_m2dir::entry::store::M2dirEntryStore`] and, when `flags` is
3//! non-empty, chains [`M2dirFlagSet`] to persist the sidecar.
4//!
5//! The store coroutine probes pid + 4 random bytes per the m2dir
6//! `<date>,<checksum>.<nonce>` id convention.
7//!
8//! # Example
9//!
10//! ```rust,ignore
11//! use io_email::message::m2dir::add::M2dirMessageAdd;
12//!
13//! let id = client.run(M2dirMessageAdd::new(&client.root, "INBOX", &flags, raw)?)?;
14//! ```
15//!
16//! [`M2dirFlagSet`]: io_m2dir::flag::set::M2dirFlagSet
17
18use alloc::{string::String, vec::Vec};
19use core::mem;
20use std::path::PathBuf;
21
22use io_m2dir::{
23    coroutine::*,
24    entry::store::{
25        M2dirEntryStore as InnerStore, M2dirEntryStoreError as StoreErr,
26        M2dirEntryStoreOptions as StoreOpts,
27    },
28    flag::{
29        set::{
30            M2dirFlagSet as InnerFlagSet, M2dirFlagSetError as FlagSetErr,
31            M2dirFlagSetOptions as FlagSetOpts,
32        },
33        types::M2dirFlags,
34    },
35    m2dir::types::M2dir,
36};
37use log::trace;
38use thiserror::Error;
39
40use crate::{
41    flag::types::Flag,
42    m2dir::convert::{InvalidMailboxName, flags_to_m2dir, resolve_mailbox},
43};
44
45/// Errors produced by [`M2dirMessageAdd`].
46#[derive(Debug, Error)]
47pub enum M2dirMessageAddError {
48    #[error(transparent)]
49    Store(#[from] StoreErr),
50    #[error(transparent)]
51    SetFlags(#[from] FlagSetErr),
52    #[error(transparent)]
53    InvalidMailbox(#[from] InvalidMailboxName),
54}
55
56/// I/O-free coroutine appending a raw message to an m2dir mailbox.
57pub struct M2dirMessageAdd {
58    state: State,
59    m2dir: M2dir,
60    flags: M2dirFlags,
61}
62
63impl M2dirMessageAdd {
64    pub fn new(
65        root: impl Into<PathBuf>,
66        mailbox: &str,
67        flags: &[Flag],
68        bytes: Vec<u8>,
69    ) -> Result<Self, M2dirMessageAddError> {
70        trace!("prepare m2dir message add");
71        let m2dir = resolve_mailbox(root, mailbox)?;
72        let store = InnerStore::new(m2dir.clone(), bytes, StoreOpts::default());
73        Ok(Self {
74            state: State::Storing(store),
75            m2dir,
76            flags: flags_to_m2dir(flags),
77        })
78    }
79}
80
81enum State {
82    Storing(InnerStore),
83    SettingFlags { set: InnerFlagSet, id: String },
84    Done,
85}
86
87impl M2dirCoroutine for M2dirMessageAdd {
88    type Yield = M2dirYield;
89    type Return = Result<String, M2dirMessageAddError>;
90
91    fn resume(&mut self, arg: Option<M2dirArg>) -> M2dirCoroutineState<Self::Yield, Self::Return> {
92        match mem::replace(&mut self.state, State::Done) {
93            State::Storing(mut store) => match store.resume(arg) {
94                M2dirCoroutineState::Yielded(y) => {
95                    self.state = State::Storing(store);
96                    M2dirCoroutineState::Yielded(y)
97                }
98                M2dirCoroutineState::Complete(Ok(entry)) => {
99                    if self.flags.is_empty() {
100                        M2dirCoroutineState::Complete(Ok(entry.id().into()))
101                    } else {
102                        let id: String = entry.id().into();
103                        let set = InnerFlagSet::new(
104                            &self.m2dir,
105                            &id,
106                            self.flags.clone(),
107                            FlagSetOpts::default(),
108                        );
109                        self.state = State::SettingFlags { set, id };
110                        M2dirCoroutine::resume(self, None)
111                    }
112                }
113                M2dirCoroutineState::Complete(Err(err)) => {
114                    M2dirCoroutineState::Complete(Err(err.into()))
115                }
116            },
117            State::SettingFlags { mut set, id } => match set.resume(arg) {
118                M2dirCoroutineState::Yielded(y) => {
119                    self.state = State::SettingFlags { set, id };
120                    M2dirCoroutineState::Yielded(y)
121                }
122                M2dirCoroutineState::Complete(Ok(())) => M2dirCoroutineState::Complete(Ok(id)),
123                M2dirCoroutineState::Complete(Err(err)) => {
124                    M2dirCoroutineState::Complete(Err(err.into()))
125                }
126            },
127            State::Done => unreachable!("M2dirMessageAdd resumed after completion"),
128        }
129    }
130}