use core::fmt;
use alloc::string::{String, ToString};
use thiserror::Error;
use crate::{coroutine::*, item::locate::*, path::VdirPath, vdir_try};
#[derive(Clone, Debug, Error)]
pub enum VdirItemCopyError {
#[error("Vdir item copy failed: unexpected arg {0:?}")]
UnexpectedArg(Option<VdirReply>),
#[error(transparent)]
Locate(#[from] VdirItemLocateError),
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct VdirItemCopyOptions {}
#[derive(Debug)]
pub struct VdirItemCopy {
state: State,
#[allow(dead_code)]
opts: VdirItemCopyOptions,
}
impl VdirItemCopy {
pub fn new(
source: impl Into<VdirPath>,
target: impl Into<VdirPath>,
id: impl ToString,
opts: VdirItemCopyOptions,
) -> Self {
let id = id.to_string();
let inner = VdirItemLocate::new(source, &id, VdirItemLocateOptions::default());
Self {
opts,
state: State::Locate {
target: target.into(),
id,
inner,
},
}
}
}
impl VdirCoroutine for VdirItemCopy {
type Yield = VdirYield;
type Return = Result<(), VdirItemCopyError>;
fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return> {
match (&mut self.state, arg) {
(State::Locate { target, id, inner }, arg) => {
let out = vdir_try!(inner, arg);
let ext = out.kind.extension();
let target_path = target.join(&format!("{id}.{ext}"));
let pairs = vec![(out.path, target_path)];
self.state = State::AwaitCopy;
VdirCoroutineState::Yielded(VdirYield::WantsCopy(pairs))
}
(State::AwaitCopy, Some(VdirReply::Copy)) => VdirCoroutineState::Complete(Ok(())),
(_, arg) => {
let err = VdirItemCopyError::UnexpectedArg(arg);
VdirCoroutineState::Complete(Err(err))
}
}
}
}
#[derive(Debug)]
enum State {
Locate {
target: VdirPath,
id: String,
inner: VdirItemLocate,
},
AwaitCopy,
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Locate { .. } => f.write_str("locate source item"),
Self::AwaitCopy => f.write_str("await copy reply"),
}
}
}
#[cfg(test)]
mod tests {
use alloc::collections::BTreeMap;
use super::*;
#[test]
fn copies_located_source_to_target() {
let mut cor =
VdirItemCopy::new("root/a", "root/b", "alice", VdirItemCopyOptions::default());
match cor.resume(None) {
VdirCoroutineState::Yielded(VdirYield::WantsFileExists(_)) => {}
state => panic!("expected WantsFileExists, got {state:?}"),
}
let vcf = VdirPath::from("root/a/alice.vcf");
let mut exists = BTreeMap::new();
exists.insert(vcf.clone(), true);
exists.insert(VdirPath::from("root/a/alice.ics"), false);
let pairs = match cor.resume(Some(VdirReply::FileExists(exists))) {
VdirCoroutineState::Yielded(VdirYield::WantsCopy(pairs)) => pairs,
state => panic!("expected WantsCopy, got {state:?}"),
};
assert_eq!(pairs, vec![(vcf, VdirPath::from("root/b/alice.vcf"))]);
match cor.resume(Some(VdirReply::Copy)) {
VdirCoroutineState::Complete(Ok(())) => {}
state => panic!("expected Complete(Ok), got {state:?}"),
}
}
#[test]
fn locate_error_is_forwarded() {
let mut cor =
VdirItemCopy::new("root/a", "root/b", "alice", VdirItemCopyOptions::default());
let _ = cor.resume(None);
let err = match cor.resume(Some(VdirReply::DirCreate)) {
VdirCoroutineState::Complete(Err(err)) => err,
state => panic!("expected Complete(Err), got {state:?}"),
};
assert!(matches!(err, VdirItemCopyError::Locate(_)));
}
}