io_email/flag/imap/
store.rs1use alloc::{string::String, vec::Vec};
16use core::mem;
17
18use io_imap::{
19 codec::fragmentizer::Fragmentizer,
20 coroutine::{ImapCoroutine, ImapCoroutineState, ImapYield},
21 rfc3501::{
22 select::{ImapMailboxSelect, ImapMailboxSelectError, ImapMailboxSelectOptions},
23 store::{ImapMessageStore, ImapMessageStoreError, ImapMessageStoreOptions},
24 },
25 types::flag::StoreType,
26};
27use log::trace;
28use thiserror::Error;
29
30use crate::{
31 flag::types::{Flag, FlagOp},
32 imap::convert::{InvalidMailboxName, InvalidUidSet, flag_from, parse_mailbox, parse_uids},
33};
34
35#[derive(Debug, Error)]
37pub enum ImapFlagStoreError {
38 #[error(transparent)]
39 Select(#[from] ImapMailboxSelectError),
40 #[error(transparent)]
41 Store(#[from] ImapMessageStoreError),
42 #[error("invalid IMAP mailbox `{0}`")]
43 InvalidMailbox(String),
44 #[error("empty UID set")]
45 EmptyUidSet,
46 #[error("invalid message UID `{0}`")]
47 InvalidUid(String),
48 #[error("coroutine was resumed after completion")]
49 ResumedAfterDone,
50}
51
52impl From<InvalidMailboxName> for ImapFlagStoreError {
53 fn from(err: InvalidMailboxName) -> Self {
54 Self::InvalidMailbox(err.0)
55 }
56}
57
58impl From<InvalidUidSet> for ImapFlagStoreError {
59 fn from(err: InvalidUidSet) -> Self {
60 match err {
61 InvalidUidSet::Empty => Self::EmptyUidSet,
62 InvalidUidSet::Invalid(s) => Self::InvalidUid(s),
63 }
64 }
65}
66
67pub struct ImapFlagStore {
69 state: State,
70}
71
72impl ImapFlagStore {
73 pub fn new(
76 mailbox: &str,
77 ids: &[&str],
78 flags: &[Flag],
79 op: FlagOp,
80 auto_select: bool,
81 ) -> Result<Self, ImapFlagStoreError> {
82 trace!("prepare IMAP flag store (auto_select={auto_select} op={op:?})");
83 let mbox = parse_mailbox(mailbox)?;
84 let sequence_set = parse_uids(ids)?;
85 let imap_flags: Vec<_> = flags.iter().map(flag_from).collect();
86 let kind = match op {
87 FlagOp::Add => StoreType::Add,
88 FlagOp::Set => StoreType::Replace,
89 FlagOp::Remove => StoreType::Remove,
90 };
91
92 let store = ImapMessageStore::new(
93 sequence_set,
94 kind,
95 imap_flags,
96 ImapMessageStoreOptions { uid: true },
97 );
98 let state = if auto_select {
99 State::Selecting {
100 select: ImapMailboxSelect::new(mbox, ImapMailboxSelectOptions::default()),
101 store,
102 }
103 } else {
104 State::Storing(store)
105 };
106 Ok(Self { state })
107 }
108}
109
110#[allow(clippy::large_enum_variant)]
114enum State {
115 Selecting {
116 select: ImapMailboxSelect,
117 store: ImapMessageStore,
118 },
119 Storing(ImapMessageStore),
120 Done,
121}
122
123impl ImapCoroutine for ImapFlagStore {
124 type Yield = ImapYield;
125 type Return = Result<(), ImapFlagStoreError>;
126
127 fn resume(
128 &mut self,
129 fragmentizer: &mut Fragmentizer,
130 mut bytes: Option<&[u8]>,
131 ) -> ImapCoroutineState<Self::Yield, Self::Return> {
132 loop {
133 match mem::replace(&mut self.state, State::Done) {
134 State::Selecting { mut select, store } => {
135 match select.resume(fragmentizer, bytes.take()) {
136 ImapCoroutineState::Yielded(yielded) => {
137 self.state = State::Selecting { select, store };
138 return ImapCoroutineState::Yielded(yielded);
139 }
140 ImapCoroutineState::Complete(Ok(_)) => {
141 self.state = State::Storing(store);
142 }
143 ImapCoroutineState::Complete(Err(err)) => {
144 return ImapCoroutineState::Complete(Err(err.into()));
145 }
146 }
147 }
148 State::Storing(mut store) => match store.resume(fragmentizer, bytes.take()) {
149 ImapCoroutineState::Yielded(yielded) => {
150 self.state = State::Storing(store);
151 return ImapCoroutineState::Yielded(yielded);
152 }
153 ImapCoroutineState::Complete(Ok(_)) => {
154 return ImapCoroutineState::Complete(Ok(()));
155 }
156 ImapCoroutineState::Complete(Err(err)) => {
157 return ImapCoroutineState::Complete(Err(err.into()));
158 }
159 },
160 State::Done => {
161 return ImapCoroutineState::Complete(Err(ImapFlagStoreError::ResumedAfterDone));
162 }
163 }
164 }
165 }
166}