1use core::fmt;
43
44use alloc::string::{String, ToString};
45
46use thiserror::Error;
47
48use crate::{coroutine::*, item::locate::*, path::VdirPath, vdir_try};
49
50#[derive(Clone, Debug, Error)]
52pub enum VdirItemMoveError {
53 #[error("Vdir item move failed: unexpected arg {0:?}")]
56 UnexpectedArg(Option<VdirReply>),
57
58 #[error(transparent)]
60 Locate(#[from] VdirItemLocateError),
61}
62
63#[derive(Clone, Debug, Default, Eq, PartialEq)]
65pub struct VdirItemMoveOptions {}
66
67#[derive(Debug)]
69pub struct VdirItemMove {
70 state: State,
71 #[allow(dead_code)]
72 opts: VdirItemMoveOptions,
73}
74
75impl VdirItemMove {
76 pub fn new(
80 source: impl Into<VdirPath>,
81 target: impl Into<VdirPath>,
82 id: impl ToString,
83 opts: VdirItemMoveOptions,
84 ) -> Self {
85 let id = id.to_string();
86 let inner = VdirItemLocate::new(source, &id, VdirItemLocateOptions::default());
87 Self {
88 opts,
89 state: State::Locate {
90 target: target.into(),
91 id,
92 inner,
93 },
94 }
95 }
96}
97
98impl VdirCoroutine for VdirItemMove {
99 type Yield = VdirYield;
100 type Return = Result<(), VdirItemMoveError>;
101
102 fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return> {
103 match (&mut self.state, arg) {
104 (State::Locate { target, id, inner }, arg) => {
105 let out = vdir_try!(inner, arg);
106 let ext = out.kind.extension();
107 let target_path = target.join(&format!("{id}.{ext}"));
108 let pairs = vec![(out.path, target_path)];
109 self.state = State::AwaitRename;
110 VdirCoroutineState::Yielded(VdirYield::WantsRename(pairs))
111 }
112 (State::AwaitRename, Some(VdirReply::Rename)) => VdirCoroutineState::Complete(Ok(())),
113 (_, arg) => {
114 let err = VdirItemMoveError::UnexpectedArg(arg);
115 VdirCoroutineState::Complete(Err(err))
116 }
117 }
118 }
119}
120
121#[derive(Debug)]
122enum State {
123 Locate {
124 target: VdirPath,
125 id: String,
126 inner: VdirItemLocate,
127 },
128 AwaitRename,
129}
130
131impl fmt::Display for State {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 match self {
134 Self::Locate { .. } => f.write_str("locate source item"),
135 Self::AwaitRename => f.write_str("await rename reply"),
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use alloc::collections::BTreeMap;
143
144 use super::*;
145
146 #[test]
147 fn moves_located_source_to_target() {
148 let mut cor =
149 VdirItemMove::new("root/a", "root/b", "alice", VdirItemMoveOptions::default());
150
151 match cor.resume(None) {
152 VdirCoroutineState::Yielded(VdirYield::WantsFileExists(_)) => {}
153 state => panic!("expected WantsFileExists, got {state:?}"),
154 }
155
156 let ics = VdirPath::from("root/a/alice.ics");
157 let mut exists = BTreeMap::new();
158 exists.insert(VdirPath::from("root/a/alice.vcf"), false);
159 exists.insert(ics.clone(), true);
160
161 let pairs = match cor.resume(Some(VdirReply::FileExists(exists))) {
162 VdirCoroutineState::Yielded(VdirYield::WantsRename(pairs)) => pairs,
163 state => panic!("expected WantsRename, got {state:?}"),
164 };
165 assert_eq!(pairs, vec![(ics, VdirPath::from("root/b/alice.ics"))]);
166
167 match cor.resume(Some(VdirReply::Rename)) {
168 VdirCoroutineState::Complete(Ok(())) => {}
169 state => panic!("expected Complete(Ok), got {state:?}"),
170 }
171 }
172
173 #[test]
174 fn locate_error_is_forwarded() {
175 let mut cor =
176 VdirItemMove::new("root/a", "root/b", "alice", VdirItemMoveOptions::default());
177 let _ = cor.resume(None);
178
179 let err = match cor.resume(Some(VdirReply::DirCreate)) {
180 VdirCoroutineState::Complete(Err(err)) => err,
181 state => panic!("expected Complete(Err), got {state:?}"),
182 };
183 assert!(matches!(err, VdirItemMoveError::Locate(_)));
184 }
185}