dir_structure/
clean_dir.rs

1//! A newtype that will clean the directory it is written to, before writing
2//! the value.
3
4#[cfg(feature = "async")]
5use std::future::Future;
6use std::marker;
7use std::pin::Pin;
8#[cfg(feature = "async")]
9use std::task::Context;
10#[cfg(feature = "async")]
11use std::task::Poll;
12
13#[cfg(feature = "async")]
14use pin_project::pin_project;
15
16use crate::error::VfsResult;
17use crate::prelude::*;
18#[cfg(feature = "async")]
19use crate::traits::asy::FromRefForWriterAsync;
20#[cfg(feature = "async")]
21use crate::traits::async_vfs::VfsAsync;
22#[cfg(feature = "async")]
23use crate::traits::async_vfs::WriteSupportingVfsAsync;
24#[cfg(feature = "resolve-path")]
25use crate::traits::resolve::DynamicHasField;
26#[cfg(feature = "resolve-path")]
27use crate::traits::resolve::HAS_FIELD_MAX_LEN;
28#[cfg(feature = "resolve-path")]
29use crate::traits::resolve::HasField;
30use crate::traits::sync::DirStructureItem;
31use crate::traits::sync::FromRefForWriter;
32use crate::traits::sync::NewtypeToInner;
33use crate::traits::vfs;
34#[cfg(feature = "resolve-path")]
35use crate::traits::vfs::OwnedPathType;
36#[cfg(feature = "async")]
37use crate::traits::vfs::PathType;
38#[cfg(feature = "async")]
39use crate::traits::vfs::VfsCore;
40
41/// A newtype that will clean the directory it is written to, before writing
42/// the value.
43///
44/// This is useful when we want to write a directory structure, but we want
45/// to make sure that the directory is clean before writing it, so that there
46/// are no old files / directories left in it.
47///
48#[cfg_attr(feature = "derive", doc = "```rust")]
49#[cfg_attr(not(feature = "derive"), doc = "```rust,compile_fail")]
50/// use std::path::Path;
51///
52/// use dir_structure::traits::sync::DirStructureItem;
53/// use dir_structure::clean_dir::CleanDir;
54///
55/// #[derive(dir_structure::DirStructure)]
56/// struct Dir {
57///    #[dir_structure(path = "f.txt")]
58///    f: String,
59/// }
60///
61/// fn main() -> Result<(), Box<dyn std::error::Error>> {
62///     let d = Path::new("dir");
63///     std::fs::create_dir_all(&d)?;
64///     std::fs::write(d.join("f.txt"), "Hello, world!")?;
65///     std::fs::write(d.join("f2.txt"), "Hello, world! (2)")?;
66///     let dir = Dir::read(&d)?;
67///     assert_eq!(dir.f, "Hello, world!");
68///     assert_eq!(std::fs::read_to_string(d.join("f2.txt"))?, "Hello, world! (2)");
69///     CleanDir(dir).write(&d)?;
70///     assert_eq!(std::fs::read_to_string(d.join("f.txt"))?, "Hello, world!");
71///     assert!(!d.join("f2.txt").exists());
72///     # std::fs::remove_dir_all(&d)?;
73///     Ok(())
74/// }
75/// ```
76#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
77#[cfg_attr(feature = "assert_eq", derive(assert_eq::AssertEq))]
78pub struct CleanDir<T>(pub T);
79
80impl<'a, T, Vfs: vfs::Vfs<'a>> ReadFrom<'a, Vfs> for CleanDir<T>
81where
82    T: ReadFrom<'a, Vfs>,
83{
84    fn read_from(path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<Self, Vfs>
85    where
86        Self: Sized,
87    {
88        Ok(Self(T::read_from(path, vfs)?))
89    }
90}
91
92#[cfg(feature = "async")]
93#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
94#[pin_project]
95#[doc(hidden)]
96pub struct CleanDirReadFuture<'a, T, Vfs>
97where
98    T: ReadFromAsync<'a, Vfs> + Send + 'static,
99    Vfs: VfsAsync,
100{
101    #[pin]
102    inner: T::Future,
103}
104
105#[cfg(feature = "async")]
106#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
107impl<'a, T, Vfs: VfsAsync> Future for CleanDirReadFuture<'a, T, Vfs>
108where
109    T: ReadFromAsync<'a, Vfs> + Send + 'static,
110{
111    type Output = VfsResult<CleanDir<T>, Vfs>;
112
113    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
114        let this = self.project();
115        match this.inner.poll(cx) {
116            Poll::Ready(v) => Poll::Ready(v.map(CleanDir)),
117            Poll::Pending => Poll::Pending,
118        }
119    }
120}
121
122#[cfg(feature = "async")]
123#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
124impl<'a, T, Vfs: VfsAsync + 'a> ReadFromAsync<'a, Vfs> for CleanDir<T>
125where
126    T: ReadFromAsync<'a, Vfs> + Send + 'static,
127{
128    type Future = Pin<Box<dyn Future<Output = VfsResult<Self, Vfs>> + Send + 'a>>;
129
130    fn read_from_async(
131        path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
132        vfs: Pin<&'a Vfs>,
133    ) -> Self::Future {
134        Box::pin(async move { T::read_from_async(path, vfs).await.map(Self) })
135    }
136}
137
138impl<'a, T, Vfs: vfs::WriteSupportingVfs<'a>> WriteTo<'a, Vfs> for CleanDir<T>
139where
140    T: WriteTo<'a, Vfs>,
141{
142    fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<(), Vfs> {
143        Self::from_ref_for_writer(&self.0).write_to(path, vfs)
144    }
145}
146
147#[cfg(feature = "async")]
148#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
149impl<'a, T, Vfs: WriteSupportingVfsAsync + 'static> WriteToAsync<'a, Vfs> for CleanDir<T>
150where
151    T: WriteToAsync<'a, Vfs> + Send + Sync + 'static,
152{
153    type Future = Pin<Box<dyn Future<Output = VfsResult<(), Vfs>> + Send + 'a>>;
154
155    fn write_to_async(
156        self,
157        path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
158        vfs: Pin<&'a Vfs>,
159    ) -> Self::Future {
160        Box::pin(async move {
161            if vfs.exists(path.clone()).await? {
162                vfs.remove_dir_all(path.clone()).await?;
163            } else {
164                vfs.create_parent_dir(path.clone()).await?;
165            }
166            self.0.write_to_async(path, vfs).await
167        })
168    }
169}
170
171#[cfg(feature = "async")]
172#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
173#[pin_project(project_replace = CleanDirWriteRefFutureProjOwn)]
174#[doc(hidden)]
175pub enum CleanDirWriteRefFuture<'a, 'f, T, Vfs: WriteSupportingVfsAsync + 'static>
176where
177    T: WriteToAsyncRef<'a, Vfs> + Send + Sync + 'a,
178    <T as WriteToAsyncRef<'a, Vfs>>::Future<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
179    <Vfs as VfsAsync>::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin + 'f,
180    <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>:
181        Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
182    'a: 'f,
183{
184    Poison,
185    ExistsCheck(
186        <Vfs as VfsAsync>::ExistsFuture<'f>,
187        <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
188        Pin<&'f Vfs>,
189        &'f T,
190    ),
191    RemoveDirAll(
192        <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>,
193        <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
194        Pin<&'f Vfs>,
195        &'f T,
196    ),
197    Inner(<T as WriteToAsyncRef<'a, Vfs>>::Future<'f>),
198}
199
200#[cfg(feature = "async")]
201#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
202impl<'a, 'f, T, Vfs: WriteSupportingVfsAsync + 'static> Future
203    for CleanDirWriteRefFuture<'a, 'f, T, Vfs>
204where
205    T: WriteToAsyncRef<'a, Vfs> + Send + Sync + 'static,
206    <T as WriteToAsyncRef<'a, Vfs>>::Future<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
207    <Vfs as VfsAsync>::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin + 'f,
208    <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>:
209        Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
210    'a: 'f,
211{
212    type Output = VfsResult<(), Vfs>;
213
214    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
215        let this = self.as_mut().project_replace(Self::Poison);
216        match this {
217            CleanDirWriteRefFutureProjOwn::Poison => {
218                panic!("polled after completion")
219            }
220            CleanDirWriteRefFutureProjOwn::ExistsCheck(mut fut, path, vfs, item) => {
221                match Pin::new(&mut fut).poll(cx) {
222                    Poll::Ready(Ok(exists)) => {
223                        if exists {
224                            let fut = vfs.remove_dir_all(path.clone());
225                            self.project_replace(Self::RemoveDirAll(fut, path.clone(), vfs, item));
226                            cx.waker().wake_by_ref();
227                            Poll::Pending
228                        } else {
229                            let fut = item.write_to_async_ref(path, vfs);
230                            self.project_replace(Self::Inner(fut));
231                            cx.waker().wake_by_ref();
232                            Poll::Pending
233                        }
234                    }
235                    Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
236                    Poll::Pending => {
237                        self.project_replace(Self::ExistsCheck(fut, path, vfs, item));
238                        Poll::Pending
239                    }
240                }
241            }
242            CleanDirWriteRefFutureProjOwn::RemoveDirAll(mut fut, path, vfs, item) => {
243                match Pin::new(&mut fut).poll(cx) {
244                    Poll::Ready(Ok(())) => {
245                        let fut = item.write_to_async_ref(path.clone(), vfs);
246                        self.project_replace(Self::Inner(fut));
247                        cx.waker().wake_by_ref();
248                        Poll::Pending
249                    }
250                    Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
251                    Poll::Pending => {
252                        self.project_replace(Self::RemoveDirAll(fut, path, vfs, item));
253                        Poll::Pending
254                    }
255                }
256            }
257            CleanDirWriteRefFutureProjOwn::Inner(mut fut) => match Pin::new(&mut fut).poll(cx) {
258                Poll::Ready(v) => Poll::Ready(v),
259                Poll::Pending => {
260                    self.project_replace(Self::Inner(fut));
261                    Poll::Pending
262                }
263            },
264        }
265    }
266}
267
268#[cfg(feature = "async")]
269#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
270impl<'a, T, Vfs: WriteSupportingVfsAsync + 'static> WriteToAsyncRef<'a, Vfs> for CleanDir<T>
271where
272    T: WriteToAsyncRef<'a, Vfs> + Send + Sync + 'static,
273    for<'f> <T as WriteToAsyncRef<'a, Vfs>>::Future<'f>:
274        Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
275    for<'f> <Vfs as VfsAsync>::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin + 'f,
276    for<'f> <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>:
277        Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
278{
279    type Future<'b>
280        = CleanDirWriteRefFuture<'a, 'b, T, Vfs>
281    where
282        Self: 'b,
283        'a: 'b,
284        Vfs: 'b;
285
286    fn write_to_async_ref<'b>(
287        &'b self,
288        path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
289        vfs: Pin<&'b Vfs>,
290    ) -> Self::Future<'b>
291    where
292        'a: 'b,
293    {
294        let exists_future = vfs.exists(path.clone());
295        CleanDirWriteRefFuture::<'a, 'b, T, Vfs>::ExistsCheck(exists_future, path, vfs, &self.0)
296    }
297}
298
299impl<'a, 'vfs, T, Vfs: vfs::WriteSupportingVfs<'vfs>> FromRefForWriter<'a, 'vfs, Vfs>
300    for CleanDir<T>
301where
302    T: WriteTo<'vfs, Vfs> + 'a,
303    Vfs: 'vfs,
304    'vfs: 'a,
305{
306    type Inner = T;
307    type Wr = CleanDirRefWr<'a, 'vfs, T, Vfs>;
308
309    fn from_ref_for_writer(value: &'a Self::Inner) -> Self::Wr {
310        CleanDirRefWr(value, marker::PhantomData)
311    }
312}
313
314#[cfg(feature = "async")]
315#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
316impl<'a, T, Vfs: WriteSupportingVfsAsync + 'static> FromRefForWriterAsync<'a, Vfs> for CleanDir<T>
317where
318    T: WriteToAsyncRef<'a, Vfs> + Send + Sync + 'static,
319    for<'f> <Vfs as VfsAsync>::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin + 'f,
320    for<'f> <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>:
321        Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
322{
323    type Inner = T;
324    type Wr = CleanDirRefWr<'a, 'a, T, Vfs>;
325
326    fn from_ref_for_writer_async(value: &'a Self::Inner) -> Self::Wr {
327        CleanDirRefWr(value, marker::PhantomData)
328    }
329}
330
331impl<T> NewtypeToInner for CleanDir<T>
332where
333    T: DirStructureItem,
334{
335    type Inner = T;
336
337    fn into_inner(self) -> Self::Inner {
338        self.0
339    }
340}
341
342#[cfg(feature = "resolve-path")]
343#[cfg_attr(docsrs, doc(cfg(feature = "resolve-path")))]
344impl<const NAME: [char; HAS_FIELD_MAX_LEN], T> HasField<NAME> for CleanDir<T>
345where
346    T: HasField<NAME>,
347{
348    type Inner = <T as HasField<NAME>>::Inner;
349
350    fn resolve_path<P: OwnedPathType>(p: P) -> P {
351        T::resolve_path(p)
352    }
353}
354
355#[cfg(feature = "resolve-path")]
356#[cfg_attr(docsrs, doc(cfg(feature = "resolve-path")))]
357impl<T> DynamicHasField for CleanDir<T>
358where
359    T: DynamicHasField,
360{
361    type Inner = <T as DynamicHasField>::Inner;
362
363    fn resolve_path<P: OwnedPathType>(p: P, name: &str) -> P {
364        T::resolve_path(p, name)
365    }
366}
367
368/// [`WriteTo`] impl for [`CleanDir`]
369pub struct CleanDirRefWr<'a, 'vfs, T: ?Sized + 'a, Vfs: 'vfs>(
370    &'a T,
371    marker::PhantomData<&'vfs Vfs>,
372)
373where
374    'vfs: 'a;
375
376impl<'a, 'vfs, T, Vfs: vfs::WriteSupportingVfs<'vfs>> WriteTo<'vfs, Vfs>
377    for CleanDirRefWr<'a, 'vfs, T, Vfs>
378where
379    T: ?Sized + WriteTo<'vfs, Vfs>,
380    'vfs: 'a,
381{
382    fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'vfs Vfs>) -> VfsResult<(), Vfs> {
383        if vfs.exists(path)? {
384            vfs.remove_dir_all(path)?;
385        } else {
386            vfs.create_parent_dir(path)?;
387        }
388        self.0.write_to(path, vfs)
389    }
390}
391
392#[cfg(feature = "async")]
393#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
394impl<'a, T, Vfs: WriteSupportingVfsAsync + 'static> WriteToAsync<'a, Vfs>
395    for CleanDirRefWr<'a, 'a, T, Vfs>
396where
397    T: WriteToAsyncRef<'a, Vfs> + Send + Sync + 'static,
398    for<'f> <Vfs as VfsAsync>::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin + 'f,
399    for<'f> <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>:
400        Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
401{
402    type Future = CleanDirRefWrWriteFuture<'a, 'a, T, Vfs>;
403
404    fn write_to_async(
405        self,
406        path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
407        vfs: Pin<&'a Vfs>,
408    ) -> Self::Future {
409        let exists_future = vfs.exists(path.clone());
410        CleanDirRefWrWriteFuture::ExistsCheck(exists_future, path, vfs, self.0)
411    }
412}
413
414#[cfg(feature = "async")]
415#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
416#[pin_project(project_replace = CleanDirRefWrWriteFutureProjOwn)]
417#[doc(hidden)]
418pub enum CleanDirRefWrWriteFuture<'a, 'f, T, Vfs: WriteSupportingVfsAsync + 'static>
419where
420    T: WriteToAsyncRef<'a, Vfs> + ?Sized + 'a,
421    T::Future<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
422    <Vfs as VfsAsync>::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin + 'f,
423    <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>:
424        Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
425    'a: 'f,
426{
427    Poison,
428    ExistsCheck(
429        <Vfs as VfsAsync>::ExistsFuture<'f>,
430        <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
431        Pin<&'a Vfs>,
432        &'f T,
433    ),
434    RemoveDirAll(
435        <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>,
436        <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
437        Pin<&'f Vfs>,
438        &'f T,
439    ),
440    Write(
441        <T as WriteToAsyncRef<'a, Vfs>>::Future<'f>,
442        <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
443        Pin<&'f Vfs>,
444    ),
445}
446
447#[cfg(feature = "async")]
448#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
449impl<'a, 'f, T, Vfs: WriteSupportingVfsAsync + 'static> Future
450    for CleanDirRefWrWriteFuture<'a, 'f, T, Vfs>
451where
452    T: WriteToAsyncRef<'a, Vfs> + ?Sized + 'a,
453    T::Future<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
454    <Vfs as VfsAsync>::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin + 'f,
455    <Vfs as WriteSupportingVfsAsync>::RemoveDirAllFuture<'f>:
456        Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
457    'a: 'f,
458{
459    type Output = VfsResult<(), Vfs>;
460
461    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
462        let this = self.as_mut().project_replace(Self::Poison);
463        match this {
464            CleanDirRefWrWriteFutureProjOwn::Poison => {
465                panic!("polled after completion")
466            }
467            CleanDirRefWrWriteFutureProjOwn::ExistsCheck(mut fut, path, vfs, v) => {
468                match Pin::new(&mut fut).poll(cx) {
469                    Poll::Ready(Ok(exists)) => {
470                        if exists {
471                            let fut = vfs.remove_dir_all(path.clone());
472                            self.project_replace(Self::RemoveDirAll(fut, path.clone(), vfs, v));
473                            cx.waker().wake_by_ref();
474                            Poll::Pending
475                        } else {
476                            let fut = T::write_to_async_ref(v, path.clone(), vfs);
477                            self.project_replace(Self::Write(fut, path.clone(), vfs));
478                            cx.waker().wake_by_ref();
479                            Poll::Pending
480                        }
481                    }
482                    Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
483                    Poll::Pending => {
484                        self.project_replace(Self::ExistsCheck(fut, path, vfs, v));
485                        Poll::Pending
486                    }
487                }
488            }
489            CleanDirRefWrWriteFutureProjOwn::RemoveDirAll(mut fut, path, vfs, v) => {
490                match Pin::new(&mut fut).poll(cx) {
491                    Poll::Ready(Ok(())) => {
492                        let fut = T::write_to_async_ref(v, path.clone(), vfs);
493                        self.project_replace(Self::Write(fut, path.clone(), vfs));
494                        cx.waker().wake_by_ref();
495                        Poll::Pending
496                    }
497                    Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
498                    Poll::Pending => {
499                        self.project_replace(Self::RemoveDirAll(fut, path, vfs, v));
500                        Poll::Pending
501                    }
502                }
503            }
504            CleanDirRefWrWriteFutureProjOwn::Write(mut fut, _path, _vfs) => {
505                match Pin::new(&mut fut).poll(cx) {
506                    Poll::Ready(v) => Poll::Ready(v),
507                    Poll::Pending => {
508                        self.project_replace(Self::Write(fut, _path, _vfs));
509                        Poll::Pending
510                    }
511                }
512            }
513        }
514    }
515}