1use core::{fmt, str};
50
51use alloc::collections::{BTreeMap, BTreeSet};
52
53use log::trace;
54use thiserror::Error;
55
56use crate::{coroutine::*, flag::types::M2dirFlags, m2dir::types::M2dir, path::M2dirPath};
57
58#[derive(Clone, Debug, Error)]
60pub enum M2dirFlagSetError {
61 #[error("M2DIR SET FLAGS failed: unexpected coroutine arg")]
62 UnexpectedArg,
63 #[error("M2DIR SET FLAGS failed: missing coroutine arg")]
64 MissingArg,
65}
66
67#[derive(Clone, Debug, Default, Eq, PartialEq)]
69pub struct M2dirFlagSetOptions {}
70
71pub struct M2dirFlagSet {
73 flags_path: M2dirPath,
74 flags: M2dirFlags,
75 state: State,
76 #[allow(dead_code)]
77 opts: M2dirFlagSetOptions,
78}
79
80impl M2dirFlagSet {
81 pub fn new(
84 m2dir: &M2dir,
85 id: impl AsRef<str>,
86 flags: M2dirFlags,
87 opts: M2dirFlagSetOptions,
88 ) -> Self {
89 Self {
90 flags_path: m2dir.flags_path(id.as_ref()),
91 flags,
92 state: State::Start,
93 opts,
94 }
95 }
96}
97
98impl M2dirCoroutine for M2dirFlagSet {
99 type Yield = M2dirYield;
100 type Return = Result<(), M2dirFlagSetError>;
101
102 fn resume(&mut self, arg: Option<M2dirArg>) -> M2dirCoroutineState<Self::Yield, Self::Return> {
103 trace!("set flags: {}", self.state);
104
105 match (&self.state, arg) {
106 (State::Start, None) => {
107 self.state = State::Done;
108
109 if self.flags.is_empty() {
110 trace!("wants flags remove at {}", self.flags_path);
111 let paths = BTreeSet::from_iter([self.flags_path.clone()]);
112 M2dirCoroutineState::Yielded(M2dirYield::WantsFileRemove(paths))
113 } else {
114 trace!(
115 "wants flags write at {} ({} flags)",
116 self.flags_path,
117 self.flags.len(),
118 );
119 let serialized = self.flags.to_meta().into_bytes();
120 let files = BTreeMap::from_iter([(self.flags_path.clone(), serialized)]);
121 M2dirCoroutineState::Yielded(M2dirYield::WantsFileCreate(files))
122 }
123 }
124 (State::Done, Some(M2dirArg::FileCreate | M2dirArg::FileRemove)) => {
125 trace!("flags set at {}", self.flags_path);
126 M2dirCoroutineState::Complete(Ok(()))
127 }
128 (_, Some(_)) => {
129 let err = M2dirFlagSetError::UnexpectedArg;
130 M2dirCoroutineState::Complete(Err(err))
131 }
132 (_, None) => {
133 let err = M2dirFlagSetError::MissingArg;
134 M2dirCoroutineState::Complete(Err(err))
135 }
136 }
137 }
138}
139
140enum State {
141 Start,
142 Done,
143}
144
145impl fmt::Display for State {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 match self {
148 Self::Start => f.write_str("start"),
149 Self::Done => f.write_str("done"),
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use alloc::vec::Vec;
157
158 use super::*;
159
160 #[test]
161 fn non_empty_flags_yields_file_create_then_completes_ok() {
162 let m2dir = M2dir::from_path("/tmp/inbox");
163 let mut flags = M2dirFlags::default();
164 flags.insert("$seen");
165
166 let mut set = M2dirFlagSet::new(&m2dir, "entry", flags, M2dirFlagSetOptions::default());
167
168 let files = expect_wants_file_create(&mut set, None);
169 assert_eq!(files.len(), 1);
170 let (path, bytes) = files.into_iter().next().unwrap();
171 assert!(path.as_str().contains("entry.flags"));
172 assert_eq!(str::from_utf8(&bytes).unwrap(), "$seen\n");
173
174 expect_complete_ok(&mut set, Some(M2dirArg::FileCreate));
175 }
176
177 #[test]
178 fn empty_flags_yields_file_remove_then_completes_ok() {
179 let m2dir = M2dir::from_path("/tmp/inbox");
180 let mut set = M2dirFlagSet::new(
181 &m2dir,
182 "entry",
183 M2dirFlags::default(),
184 M2dirFlagSetOptions::default(),
185 );
186
187 let paths = expect_wants_file_remove(&mut set, None);
188 assert_eq!(paths.len(), 1);
189
190 expect_complete_ok(&mut set, Some(M2dirArg::FileRemove));
191 }
192
193 #[test]
194 fn missing_arg_at_done_returns_missing_arg_error() {
195 let m2dir = M2dir::from_path("/tmp/inbox");
196 let mut flags = M2dirFlags::default();
197 flags.insert("$seen");
198
199 let mut set = M2dirFlagSet::new(&m2dir, "entry", flags, M2dirFlagSetOptions::default());
200 let _ = expect_wants_file_create(&mut set, None);
201
202 let err = expect_complete_err(&mut set, None);
203 assert!(matches!(err, M2dirFlagSetError::MissingArg));
204 }
205
206 #[test]
207 fn unexpected_arg_at_start_returns_unexpected_arg_error() {
208 let m2dir = M2dir::from_path("/tmp/inbox");
209 let mut set = M2dirFlagSet::new(
210 &m2dir,
211 "entry",
212 M2dirFlags::default(),
213 M2dirFlagSetOptions::default(),
214 );
215
216 let err = expect_complete_err(&mut set, Some(M2dirArg::FileCreate));
217 assert!(matches!(err, M2dirFlagSetError::UnexpectedArg));
218 }
219
220 #[test]
221 fn unexpected_arg_kind_at_done_returns_unexpected_arg_error() {
222 let m2dir = M2dir::from_path("/tmp/inbox");
223 let mut flags = M2dirFlags::default();
224 flags.insert("$seen");
225
226 let mut set = M2dirFlagSet::new(&m2dir, "entry", flags, M2dirFlagSetOptions::default());
227 let _ = expect_wants_file_create(&mut set, None);
228
229 let err = expect_complete_err(&mut set, Some(M2dirArg::DirRemove));
230 assert!(matches!(err, M2dirFlagSetError::UnexpectedArg));
231 }
232
233 fn expect_wants_file_create(
236 cor: &mut M2dirFlagSet,
237 arg: Option<M2dirArg>,
238 ) -> BTreeMap<M2dirPath, Vec<u8>> {
239 match cor.resume(arg) {
240 M2dirCoroutineState::Yielded(M2dirYield::WantsFileCreate(files)) => files,
241 state => panic!("expected WantsFileCreate, got {state:?}"),
242 }
243 }
244
245 fn expect_wants_file_remove(
246 cor: &mut M2dirFlagSet,
247 arg: Option<M2dirArg>,
248 ) -> BTreeSet<M2dirPath> {
249 match cor.resume(arg) {
250 M2dirCoroutineState::Yielded(M2dirYield::WantsFileRemove(paths)) => paths,
251 state => panic!("expected WantsFileRemove, got {state:?}"),
252 }
253 }
254
255 fn expect_complete_ok(cor: &mut M2dirFlagSet, arg: Option<M2dirArg>) {
256 match cor.resume(arg) {
257 M2dirCoroutineState::Complete(Ok(())) => {}
258 state => panic!("expected Complete(Ok), got {state:?}"),
259 }
260 }
261
262 fn expect_complete_err(cor: &mut M2dirFlagSet, arg: Option<M2dirArg>) -> M2dirFlagSetError {
263 match cor.resume(arg) {
264 M2dirCoroutineState::Complete(Err(err)) => err,
265 state => panic!("expected Complete(Err), got {state:?}"),
266 }
267 }
268}