Skip to main content

io_email/flag/m2dir/
store.rs

1//! m2dir flag-store coroutine: walks every id and drives one per-id
2//! inner ([`M2dirFlagAdd`] / [`M2dirFlagSet`] / [`M2dirFlagRemove`])
3//! against the per-id .meta/\<id\>.flags sidecar.
4//!
5//! # Example
6//!
7//! ```rust,ignore
8//! use io_email::{flag::FlagOp, flag::m2dir::store::M2dirFlagStore};
9//!
10//! client.run(M2dirFlagStore::new(&client.root, "INBOX", &["msg-id"], &flags, FlagOp::Add)?)?;
11//! ```
12//!
13//! [`M2dirFlagAdd`]: io_m2dir::flag::add::M2dirFlagAdd
14//! [`M2dirFlagSet`]: io_m2dir::flag::set::M2dirFlagSet
15//! [`M2dirFlagRemove`]: io_m2dir::flag::remove::M2dirFlagRemove
16
17use alloc::{collections::VecDeque, string::String};
18use std::path::PathBuf;
19
20use io_m2dir::{
21    coroutine::*,
22    flag::{
23        add::{
24            M2dirFlagAdd as InnerAdd, M2dirFlagAddError as AddErr, M2dirFlagAddOptions as AddOpts,
25        },
26        remove::{
27            M2dirFlagRemove as InnerRemove, M2dirFlagRemoveError as RemoveErr,
28            M2dirFlagRemoveOptions as RemoveOpts,
29        },
30        set::{
31            M2dirFlagSet as InnerSet, M2dirFlagSetError as SetErr, M2dirFlagSetOptions as SetOpts,
32        },
33        types::M2dirFlags,
34    },
35    m2dir::types::M2dir,
36};
37use log::trace;
38use thiserror::Error;
39
40use crate::{
41    flag::types::{Flag, FlagOp},
42    m2dir::convert::{InvalidMailboxName, flags_to_m2dir, resolve_mailbox},
43};
44
45/// Errors produced by [`M2dirFlagStore`].
46#[derive(Debug, Error)]
47pub enum M2dirFlagStoreError {
48    #[error(transparent)]
49    Add(#[from] AddErr),
50    #[error(transparent)]
51    Set(#[from] SetErr),
52    #[error(transparent)]
53    Remove(#[from] RemoveErr),
54    #[error(transparent)]
55    InvalidMailbox(#[from] InvalidMailboxName),
56}
57
58/// I/O-free coroutine applying a flag store across every id in turn.
59pub struct M2dirFlagStore {
60    m2dir: M2dir,
61    flags: M2dirFlags,
62    op: FlagOp,
63    pending: VecDeque<String>,
64    current: Option<Stage>,
65}
66
67impl M2dirFlagStore {
68    pub fn new(
69        root: impl Into<PathBuf>,
70        mailbox: &str,
71        ids: &[&str],
72        flags: &[Flag],
73        op: FlagOp,
74    ) -> Result<Self, M2dirFlagStoreError> {
75        trace!("prepare m2dir flag store ({op:?})");
76        let m2dir = resolve_mailbox(root, mailbox)?;
77        Ok(Self {
78            m2dir,
79            flags: flags_to_m2dir(flags),
80            op,
81            pending: ids.iter().map(|s| (*s).into()).collect(),
82            current: None,
83        })
84    }
85}
86
87enum Stage {
88    Add(InnerAdd),
89    Set(InnerSet),
90    Remove(InnerRemove),
91}
92
93impl Stage {
94    fn start(m2dir: &M2dir, id: String, flags: M2dirFlags, op: FlagOp) -> Self {
95        match op {
96            FlagOp::Add => Stage::Add(InnerAdd::new(m2dir, id, flags, AddOpts::default())),
97            FlagOp::Set => Stage::Set(InnerSet::new(m2dir, id, flags, SetOpts::default())),
98            FlagOp::Remove => {
99                Stage::Remove(InnerRemove::new(m2dir, id, flags, RemoveOpts::default()))
100            }
101        }
102    }
103}
104
105impl M2dirCoroutine for M2dirFlagStore {
106    type Yield = M2dirYield;
107    type Return = Result<(), M2dirFlagStoreError>;
108
109    fn resume(&mut self, arg: Option<M2dirArg>) -> M2dirCoroutineState<Self::Yield, Self::Return> {
110        let mut arg = arg;
111        loop {
112            if self.current.is_none() {
113                let Some(id) = self.pending.pop_front() else {
114                    return M2dirCoroutineState::Complete(Ok(()));
115                };
116                self.current = Some(Stage::start(&self.m2dir, id, self.flags.clone(), self.op));
117            }
118            let stage = self.current.as_mut().unwrap();
119            let inner_state = match stage {
120                Stage::Add(inner) => match inner.resume(arg.take()) {
121                    M2dirCoroutineState::Yielded(y) => M2dirCoroutineState::Yielded(y),
122                    M2dirCoroutineState::Complete(r) => {
123                        M2dirCoroutineState::Complete(r.map_err(M2dirFlagStoreError::from))
124                    }
125                },
126                Stage::Set(inner) => match inner.resume(arg.take()) {
127                    M2dirCoroutineState::Yielded(y) => M2dirCoroutineState::Yielded(y),
128                    M2dirCoroutineState::Complete(r) => {
129                        M2dirCoroutineState::Complete(r.map_err(M2dirFlagStoreError::from))
130                    }
131                },
132                Stage::Remove(inner) => match inner.resume(arg.take()) {
133                    M2dirCoroutineState::Yielded(y) => M2dirCoroutineState::Yielded(y),
134                    M2dirCoroutineState::Complete(r) => {
135                        M2dirCoroutineState::Complete(r.map_err(M2dirFlagStoreError::from))
136                    }
137                },
138            };
139            match inner_state {
140                M2dirCoroutineState::Yielded(y) => return M2dirCoroutineState::Yielded(y),
141                M2dirCoroutineState::Complete(Ok(())) => {
142                    self.current = None;
143                }
144                M2dirCoroutineState::Complete(Err(err)) => {
145                    return M2dirCoroutineState::Complete(Err(err));
146                }
147            }
148        }
149    }
150}