1use core::{fmt, mem};
57
58use alloc::string::{String, ToString};
59
60use thiserror::Error;
61
62use crate::{
63 coroutine::*,
64 item::{build_paths, locate::*},
65 path::VdirPath,
66 vdir_try,
67};
68
69#[derive(Clone, Debug, Error)]
71pub enum VdirItemCopyError {
72 #[error("Vdir item copy failed: unexpected arg {0:?}")]
75 UnexpectedArg(Option<VdirReply>),
76
77 #[error(transparent)]
79 Locate(#[from] VdirItemLocateError),
80}
81
82#[derive(Clone, Debug, Default, Eq, PartialEq)]
84pub struct VdirItemCopyOptions {}
85
86#[derive(Debug)]
88pub struct VdirItemCopy {
89 state: State,
90 #[allow(dead_code)]
91 opts: VdirItemCopyOptions,
92}
93
94impl VdirItemCopy {
95 pub fn new(
99 source: impl Into<VdirPath>,
100 target: impl Into<VdirPath>,
101 id: impl ToString,
102 opts: VdirItemCopyOptions,
103 ) -> Self {
104 let id = id.to_string();
105 let inner = VdirItemLocate::new(source, &id, VdirItemLocateOptions::default());
106 Self {
107 opts,
108 state: State::Locate {
109 target: target.into(),
110 id,
111 inner,
112 },
113 }
114 }
115}
116
117impl VdirCoroutine for VdirItemCopy {
118 type Yield = VdirYield;
119 type Return = Result<(), VdirItemCopyError>;
120
121 fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return> {
122 match (&mut self.state, arg) {
123 (State::Locate { target, id, inner }, arg) => {
124 let out = vdir_try!(inner, arg);
125 let (tmp_path, final_path) = build_paths(target, id, out.kind);
126 let pairs = vec![(out.path, tmp_path.clone())];
127 self.state = State::Copy {
128 tmp_path,
129 final_path,
130 };
131 VdirCoroutineState::Yielded(VdirYield::WantsCopy(pairs))
132 }
133 (
134 State::Copy {
135 tmp_path,
136 final_path,
137 },
138 Some(VdirReply::Copy),
139 ) => {
140 let pairs = vec![(mem::take(tmp_path), mem::take(final_path))];
141 self.state = State::Rename;
142 VdirCoroutineState::Yielded(VdirYield::WantsRename(pairs))
143 }
144 (State::Rename, Some(VdirReply::Rename)) => VdirCoroutineState::Complete(Ok(())),
145 (_, arg) => {
146 let err = VdirItemCopyError::UnexpectedArg(arg);
147 VdirCoroutineState::Complete(Err(err))
148 }
149 }
150 }
151}
152
153#[derive(Debug)]
154enum State {
155 Locate {
156 target: VdirPath,
157 id: String,
158 inner: VdirItemLocate,
159 },
160 Copy {
161 tmp_path: VdirPath,
162 final_path: VdirPath,
163 },
164 Rename,
165}
166
167impl fmt::Display for State {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 match self {
170 Self::Locate { .. } => f.write_str("locate source item"),
171 Self::Copy { .. } => f.write_str("copy into tmp"),
172 Self::Rename => f.write_str("rename into place"),
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use alloc::collections::BTreeMap;
180
181 use super::*;
182
183 #[test]
184 fn copies_located_source_through_tmp() {
185 let mut cor =
186 VdirItemCopy::new("root/a", "root/b", "alice", VdirItemCopyOptions::default());
187
188 match cor.resume(None) {
189 VdirCoroutineState::Yielded(VdirYield::WantsFileExists(_)) => {}
190 state => panic!("expected WantsFileExists, got {state:?}"),
191 }
192
193 let vcf = VdirPath::from("root/a/alice.vcf");
194 let mut exists = BTreeMap::new();
195 exists.insert(vcf.clone(), true);
196 exists.insert(VdirPath::from("root/a/alice.ics"), false);
197
198 let staged = VdirPath::from("root/b/alice.vcf.tmp");
201 let pairs = match cor.resume(Some(VdirReply::FileExists(exists))) {
202 VdirCoroutineState::Yielded(VdirYield::WantsCopy(pairs)) => pairs,
203 state => panic!("expected WantsCopy, got {state:?}"),
204 };
205 assert_eq!(pairs, vec![(vcf, staged.clone())]);
206
207 let pairs = match cor.resume(Some(VdirReply::Copy)) {
208 VdirCoroutineState::Yielded(VdirYield::WantsRename(pairs)) => pairs,
209 state => panic!("expected WantsRename, got {state:?}"),
210 };
211 assert_eq!(pairs, vec![(staged, VdirPath::from("root/b/alice.vcf"))]);
212
213 match cor.resume(Some(VdirReply::Rename)) {
214 VdirCoroutineState::Complete(Ok(())) => {}
215 state => panic!("expected Complete(Ok), got {state:?}"),
216 }
217 }
218
219 #[test]
220 fn locate_error_is_forwarded() {
221 let mut cor =
222 VdirItemCopy::new("root/a", "root/b", "alice", VdirItemCopyOptions::default());
223 let _ = cor.resume(None);
224
225 let err = match cor.resume(Some(VdirReply::DirCreate)) {
226 VdirCoroutineState::Complete(Err(err)) => err,
227 state => panic!("expected Complete(Err), got {state:?}"),
228 };
229 assert!(matches!(err, VdirItemCopyError::Locate(_)));
230 }
231}