Skip to main content

io_vdir/collection/
delete.rs

1//! I/O-free coroutine deleting a Vdir collection and all its contents.
2//!
3//! # Example
4//!
5//! ```rust,no_run
6//! use std::fs;
7//!
8//! use io_vdir::{collection::delete::*, coroutine::*};
9//!
10//! let opts = VdirCollectionDeleteOptions::default();
11//! let mut coroutine = VdirCollectionDelete::new("/tmp/vdir/contacts", opts);
12//! let mut arg = None;
13//!
14//! loop {
15//!     match coroutine.resume(arg.take()) {
16//!         VdirCoroutineState::Yielded(VdirYield::WantsDirRemove(paths)) => {
17//!             for path in paths {
18//!                 fs::remove_dir_all(path.as_str()).unwrap();
19//!             }
20//!             arg = Some(VdirReply::DirRemove);
21//!         }
22//!         VdirCoroutineState::Complete(Ok(())) => break,
23//!         VdirCoroutineState::Complete(Err(err)) => panic!("{err}"),
24//!         state => panic!("unexpected state {state:?}"),
25//!     }
26//! }
27//! ```
28
29use core::{fmt, mem};
30
31use alloc::collections::BTreeSet;
32
33use thiserror::Error;
34
35use crate::{coroutine::*, path::VdirPath};
36
37/// Failure causes during a [`VdirCollectionDelete`] step.
38#[derive(Clone, Debug, Error)]
39pub enum VdirCollectionDeleteError {
40    /// The driver fed back a reply that does not match the pending
41    /// request.
42    #[error("Vdir collection delete failed: unexpected arg {0:?}")]
43    UnexpectedArg(Option<VdirReply>),
44}
45
46/// Options for [`VdirCollectionDelete::new`].
47#[derive(Clone, Debug, Default, Eq, PartialEq)]
48pub struct VdirCollectionDeleteOptions {}
49
50/// Recursively removes the collection directory rooted at `path`.
51#[derive(Debug)]
52pub struct VdirCollectionDelete {
53    state: State,
54    #[allow(dead_code)]
55    opts: VdirCollectionDeleteOptions,
56}
57
58impl VdirCollectionDelete {
59    /// Creates a new coroutine that will recursively remove the
60    /// collection at `path`.
61    pub fn new(path: impl Into<VdirPath>, opts: VdirCollectionDeleteOptions) -> Self {
62        let paths = BTreeSet::from_iter([path.into()]);
63        Self {
64            opts,
65            state: State::Start { paths },
66        }
67    }
68}
69
70impl VdirCoroutine for VdirCollectionDelete {
71    type Yield = VdirYield;
72    type Return = Result<(), VdirCollectionDeleteError>;
73
74    fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return> {
75        match (&mut self.state, arg) {
76            (State::Start { paths }, None) => {
77                let paths = mem::take(paths);
78                self.state = State::AwaitRemove;
79                VdirCoroutineState::Yielded(VdirYield::WantsDirRemove(paths))
80            }
81            (State::AwaitRemove, Some(VdirReply::DirRemove)) => {
82                VdirCoroutineState::Complete(Ok(()))
83            }
84            (_, arg) => {
85                let err = VdirCollectionDeleteError::UnexpectedArg(arg);
86                VdirCoroutineState::Complete(Err(err))
87            }
88        }
89    }
90}
91
92#[derive(Debug)]
93enum State {
94    Start { paths: BTreeSet<VdirPath> },
95    AwaitRemove,
96}
97
98impl fmt::Display for State {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            Self::Start { .. } => f.write_str("start"),
102            Self::AwaitRemove => f.write_str("await remove reply"),
103        }
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn removes_collection_directory() {
113        let mut cor =
114            VdirCollectionDelete::new("root/contacts", VdirCollectionDeleteOptions::default());
115
116        let paths = match cor.resume(None) {
117            VdirCoroutineState::Yielded(VdirYield::WantsDirRemove(paths)) => paths,
118            state => panic!("expected WantsDirRemove, got {state:?}"),
119        };
120        assert!(paths.contains(&VdirPath::from("root/contacts")));
121
122        match cor.resume(Some(VdirReply::DirRemove)) {
123            VdirCoroutineState::Complete(Ok(())) => {}
124            state => panic!("expected Complete(Ok), got {state:?}"),
125        }
126    }
127
128    #[test]
129    fn unexpected_reply_returns_error() {
130        let mut cor =
131            VdirCollectionDelete::new("root/contacts", VdirCollectionDeleteOptions::default());
132        let _ = cor.resume(None);
133
134        let err = match cor.resume(Some(VdirReply::FileRemove)) {
135            VdirCoroutineState::Complete(Err(err)) => err,
136            state => panic!("expected Complete(Err), got {state:?}"),
137        };
138        assert!(matches!(err, VdirCollectionDeleteError::UnexpectedArg(_)));
139    }
140}