dir_structure/
deferred_read.rs

1//! A wrapper that defers the reading of a file until it is actually needed.
2//!
3//! See [`DeferredRead::perform_read`] for more details.
4
5use core::fmt;
6use core::fmt::Debug;
7use core::hash;
8use std::io;
9use std::marker;
10use std::pin::Pin;
11#[cfg(feature = "async")]
12use std::task::Context;
13#[cfg(feature = "async")]
14use std::task::Poll;
15
16#[cfg(feature = "async")]
17use pin_project::pin_project;
18
19use crate::error::Error;
20use crate::error::Result;
21use crate::error::VfsResult;
22use crate::prelude::*;
23#[cfg(feature = "async")]
24use crate::traits::async_vfs::VfsAsync;
25#[cfg(feature = "async")]
26use crate::traits::async_vfs::WriteSupportingVfsAsync;
27#[cfg(feature = "resolve-path")]
28use crate::traits::resolve::DynamicHasField;
29#[cfg(feature = "resolve-path")]
30use crate::traits::resolve::HAS_FIELD_MAX_LEN;
31#[cfg(feature = "resolve-path")]
32use crate::traits::resolve::HasField;
33use crate::traits::vfs;
34#[cfg(feature = "resolve-path")]
35use crate::traits::vfs::OwnedPathType;
36use crate::traits::vfs::PathType;
37use crate::traits::vfs::VfsCore;
38use crate::vfs::fs_vfs;
39
40/// A wrapper that defers the reading of a file until it is actually needed.
41///
42/// The only thing you can do with a [`DeferredRead`] is to call [`DeferredRead::perform_read`],
43/// which will read the file and return the value.
44///
45/// See the [`DeferredRead::perform_read`] method for more details.
46///
47/// For a version that also caches the read value, see [`DeferredReadOrOwn`](crate::deferred_read_or_own::DeferredReadOrOwn).
48#[derive(Clone)]
49#[cfg_attr(feature = "assert_eq", derive(assert_eq::AssertEq))]
50pub struct DeferredRead<'a, T, Vfs: VfsCore = fs_vfs::FsVfs, const CHECK_ON_READ: bool = false>(
51    pub <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
52    #[cfg_attr(feature = "assert_eq", assert_eq(ignore))] Pin<&'a Vfs>,
53    #[cfg_attr(feature = "assert_eq", assert_eq(ignore))] marker::PhantomData<T>,
54);
55
56impl<'a, const CHECK_ON_READ: bool, T, Vfs: VfsCore> hash::Hash
57    for DeferredRead<'a, T, Vfs, CHECK_ON_READ>
58where
59    T: hash::Hash,
60    <Vfs::Path as PathType>::OwnedPath: hash::Hash,
61{
62    fn hash<H: hash::Hasher>(&self, state: &mut H) {
63        self.0.hash(state);
64        // We don't hash the VFS or the PhantomData, since they don't affect the value.
65    }
66}
67
68impl<'a, const CHECK_ON_READ: bool, T, Vfs: VfsCore<Path = P>, P> Debug
69    for DeferredRead<'a, T, Vfs, CHECK_ON_READ>
70where
71    T: Debug,
72    P: PathType + ?Sized,
73    P::OwnedPath: Debug,
74{
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        use std::any::type_name;
77        write!(
78            f,
79            "deferred{} {} @ {:?} (vfs = {})",
80            if CHECK_ON_READ { "[checked]" } else { "" },
81            type_name::<T>(),
82            self.0,
83            type_name::<Vfs>()
84        )
85    }
86}
87
88impl<'a, const CHECK_ON_READ: bool, T, Vfs: vfs::Vfs<'a>> ReadFrom<'a, Vfs>
89    for DeferredRead<'a, T, Vfs, CHECK_ON_READ>
90where
91    T: ReadFrom<'a, Vfs>,
92{
93    fn read_from(path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<Self, Vfs>
94    where
95        Self: Sized,
96    {
97        if CHECK_ON_READ && !vfs.exists(path)? {
98            return Err(Error::Io(path.owned(), io::ErrorKind::NotFound.into()));
99        }
100
101        Ok(Self(path.owned(), vfs, marker::PhantomData))
102    }
103}
104
105#[cfg(feature = "async")]
106#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
107impl<'a, const CHECK_ON_READ: bool, T, Vfs: VfsAsync<Path = P> + 'static, P: PathType + ?Sized + 'a>
108    ReadFromAsync<'a, Vfs> for DeferredRead<'a, T, Vfs, CHECK_ON_READ>
109where
110    T: Send + ReadFromAsync<'a, Vfs> + 'static,
111{
112    type Future
113        = Pin<Box<dyn Future<Output = VfsResult<Self, Vfs>> + Send + 'a>>
114    where
115        Self: 'a;
116
117    fn read_from_async(path: P::OwnedPath, vfs: Pin<&'a Vfs>) -> Self::Future {
118        Box::pin(async move {
119            if CHECK_ON_READ && !vfs.exists(path.clone()).await? {
120                return Err(Error::Io(path, io::ErrorKind::NotFound.into()));
121            }
122
123            Ok(Self(path, vfs, marker::PhantomData))
124        })
125    }
126}
127
128impl<'a, const CHECK_ON_READ: bool, T, Vfs: vfs::Vfs<'a, Path = P>, P: PathType + ?Sized + 'a>
129    DeferredRead<'a, T, Vfs, CHECK_ON_READ>
130where
131    T: ReadFrom<'a, Vfs>,
132{
133    /// Performs the read and returns the value.
134    ///
135    /// If the value changed on disk since the [`DeferredRead`] was created, then the
136    /// new value will be read from disk and returned.
137    ///
138    /// For a cached version see [`DeferredReadOrOwn`](crate::deferred_read_or_own::DeferredReadOrOwn).
139    ///
140    /// # Examples
141    ///
142    #[cfg_attr(feature = "derive", doc = "```rust")]
143    #[cfg_attr(not(feature = "derive"), doc = "```rust,compile_fail")]
144    /// use std::path::Path;
145    /// use std::pin::Pin;
146    /// use dir_structure::traits::sync::DirStructureItem;
147    /// use dir_structure::deferred_read::DeferredRead;
148    /// use dir_structure::prelude::*;
149    ///
150    /// #[derive(dir_structure::DirStructure)]
151    /// struct Dir<'vfs, Vfs: VfsCore> {
152    ///     #[dir_structure(path = "f.txt")]
153    ///     f: DeferredRead<'vfs, String, Vfs>,
154    /// }
155    ///
156    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
157    ///     let d = Path::new("dir");
158    ///
159    ///     std::fs::create_dir_all(&d)?;
160    ///     std::fs::write(d.join("f.txt"), "Hello, world!")?;
161    ///
162    ///     let dir = Dir::read(&d)?;
163    ///     assert_eq!(dir.f.perform_read()?, "Hello, world!");
164    ///
165    ///     std::fs::write(d.join("f.txt"), "Goodbye, world!")?;
166    ///     assert_eq!(dir.f.perform_read()?, "Goodbye, world!");
167    ///
168    ///     # std::fs::remove_dir_all(&d)?;
169    ///     Ok(())
170    /// }
171    /// ```
172    pub fn perform_read(&self) -> VfsResult<T, Vfs> {
173        T::read_from(self.0.as_ref(), self.1)
174    }
175}
176
177#[cfg(feature = "async")]
178#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
179impl<'a, const CHECK_ON_READ: bool, T, Vfs: VfsAsync<Path = P> + 'a, P: PathType + ?Sized + 'a>
180    DeferredRead<'a, T, Vfs, CHECK_ON_READ>
181where
182    T: ReadFromAsync<'a, Vfs> + Send + 'static,
183{
184    /// Performs the read asynchronously and returns the value.
185    ///
186    /// If the value changed on disk since the [`DeferredRead`] was created, then the
187    /// new value will be read from disk and returned.
188    ///
189    /// For a cached version see [`DeferredReadOrOwn`](crate::deferred_read_or_own::DeferredReadOrOwn).
190    ///
191    /// Asynchronous version of [`DeferredRead::perform_read`].
192    pub async fn perform_read_async(&self) -> VfsResult<T, Vfs> {
193        T::read_from_async(self.0.clone(), self.1).await
194    }
195}
196
197impl<
198    'a,
199    't,
200    const CHECK_ON_READ: bool,
201    T,
202    P: PathType + ?Sized + 'a,
203    SelfVfs: vfs::Vfs<'a, Path = P>,
204    TargetVfs: vfs::WriteSupportingVfs<'t, Path = P>,
205> WriteTo<'t, TargetVfs> for DeferredRead<'a, T, SelfVfs, CHECK_ON_READ>
206where
207    T: ReadFrom<'a, SelfVfs> + WriteTo<'t, TargetVfs>,
208{
209    fn write_to(&self, path: &P, vfs: Pin<&'t TargetVfs>) -> Result<(), P::OwnedPath> {
210        if path == self.0.as_ref() {
211            // Optimization: We were asked to write to the same path
212            // we are supposed to read from. We can just ignore it, since
213            // the file / directory should already be in the given state.
214
215            // If `T` has trivial `ReadFrom` / `WriteTo` implementations,
216            // this should not be a problem, but if it is, a custom `DeferredRead`
217            // implementation should be written for it.
218            return Ok(());
219        }
220
221        let r = self.perform_read()?;
222        r.write_to(path, vfs)
223    }
224}
225
226#[cfg(feature = "async")]
227#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
228#[pin_project(project_replace = DeferredReadWriteFutureProj)]
229#[doc(hidden)]
230pub enum DeferredReadWriteFuture<
231    'a,
232    T,
233    P: PathType + ?Sized + 'a,
234    SelfVfs: VfsAsync<Path = P> + 'a,
235    TargetVfs: WriteSupportingVfsAsync<Path = P> + 'a,
236> where
237    T: ReadFromAsync<'a, SelfVfs> + WriteToAsync<'a, TargetVfs> + Send + 'static,
238    <T as ReadFromAsync<'a, SelfVfs>>::Future: Future<Output = VfsResult<T, SelfVfs>> + Unpin + 'a,
239    <T as WriteToAsync<'a, TargetVfs>>::Future:
240        Future<Output = VfsResult<(), TargetVfs>> + Unpin + 'a,
241{
242    Poisson,
243    SamePath,
244    Reading {
245        self_vfs: Pin<&'a SelfVfs>,
246        target_vfs: Pin<&'a TargetVfs>,
247        inner: <T as ReadFromAsync<'a, SelfVfs>>::Future,
248        path: P::OwnedPath,
249    },
250    Writing {
251        inner: <T as WriteToAsync<'a, TargetVfs>>::Future,
252    },
253}
254
255#[cfg(feature = "async")]
256#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
257impl<
258    'a,
259    T,
260    P: PathType + ?Sized + 'a,
261    SelfVfs: VfsAsync<Path = P> + 'a,
262    TargetVfs: WriteSupportingVfsAsync<Path = P> + 'a,
263> Future for DeferredReadWriteFuture<'a, T, P, SelfVfs, TargetVfs>
264where
265    T: ReadFromAsync<'a, SelfVfs> + WriteToAsync<'a, TargetVfs> + Send + 'static,
266    <T as ReadFromAsync<'a, SelfVfs>>::Future: Future<Output = VfsResult<T, SelfVfs>> + Unpin,
267    <T as WriteToAsync<'a, TargetVfs>>::Future: Future<Output = VfsResult<(), TargetVfs>> + Unpin,
268{
269    type Output = Result<(), P::OwnedPath>;
270
271    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
272        let this = self.as_mut().project_replace(Self::Poisson);
273        match this {
274            DeferredReadWriteFutureProj::SamePath => Poll::Ready(Ok(())),
275            DeferredReadWriteFutureProj::Reading {
276                mut inner,
277                self_vfs,
278                target_vfs,
279                path,
280            } => match Pin::new(&mut inner).poll(cx) {
281                Poll::Ready(Ok(v)) => {
282                    self.project_replace(Self::Writing {
283                        inner: v.write_to_async(path, target_vfs),
284                    });
285                    cx.waker().wake_by_ref();
286                    Poll::Pending
287                }
288                Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
289                Poll::Pending => {
290                    self.project_replace(Self::Reading {
291                        inner,
292                        path,
293                        self_vfs,
294                        target_vfs,
295                    });
296                    Poll::Pending
297                }
298            },
299            DeferredReadWriteFutureProj::Writing { mut inner } => {
300                match Pin::new(&mut inner).poll(cx) {
301                    Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
302                    Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
303                    Poll::Pending => {
304                        self.project_replace(Self::Writing { inner });
305                        Poll::Pending
306                    }
307                }
308            }
309            DeferredReadWriteFutureProj::Poisson => {
310                panic!(
311                    "DeferredReadWriteFuture is in an invalid state. This is a bug in the code."
312                );
313            }
314        }
315    }
316}
317
318#[cfg(feature = "async")]
319#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
320impl<
321    'f,
322    const CHECK_ON_READ: bool,
323    T,
324    P: PathType + ?Sized + 'f,
325    SelfVfs: VfsAsync<Path = P> + 'f,
326    TargetVfs: WriteSupportingVfsAsync<Path = P> + 'f,
327> WriteToAsync<'f, TargetVfs> for DeferredRead<'f, T, SelfVfs, CHECK_ON_READ>
328where
329    T: for<'a> ReadFromAsync<'a, SelfVfs> + for<'a> WriteToAsync<'a, TargetVfs> + Send + 'static,
330    for<'a> <T as ReadFromAsync<'a, SelfVfs>>::Future:
331        Future<Output = VfsResult<T, SelfVfs>> + Unpin + 'a,
332    for<'a> <T as WriteToAsync<'a, TargetVfs>>::Future:
333        Future<Output = VfsResult<(), TargetVfs>> + Unpin + 'a,
334{
335    type Future = DeferredReadWriteFuture<'f, T, P, SelfVfs, TargetVfs>;
336
337    fn write_to_async(self, path: P::OwnedPath, vfs: Pin<&'f TargetVfs>) -> Self::Future {
338        if path == self.0 {
339            // Optimization: We were asked to write to the same path
340            // we are supposed to read from. We can just ignore it, since
341            // the file / directory should already be in the given state.
342
343            // If `T` has trivial `ReadFromAsync` / `WriteToAsync` implementations,
344            // this should not be a problem, but if it is, a custom `DeferredRead`
345            // implementation should be written for it.
346            return DeferredReadWriteFuture::SamePath;
347        }
348
349        DeferredReadWriteFuture::Reading {
350            inner: T::read_from_async(self.0.clone(), self.1),
351            path,
352            self_vfs: self.1,
353            target_vfs: vfs,
354        }
355    }
356}
357
358#[cfg(feature = "async")]
359#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
360#[pin_project(project_replace = DeferredReadWriteRefFutureProj)]
361#[doc(hidden)]
362pub enum DeferredReadWriteRefFuture<
363    'f,
364    T,
365    P: PathType + ?Sized + 'f,
366    SelfVfs: VfsAsync<Path = P> + 'f,
367    TargetVfs: WriteSupportingVfsAsync<Path = P> + 'f,
368> where
369    T: for<'a> ReadFromAsync<'a, SelfVfs> + for<'a> WriteToAsync<'a, TargetVfs> + Send + 'static,
370    for<'a> <T as ReadFromAsync<'a, SelfVfs>>::Future:
371        Future<Output = VfsResult<T, SelfVfs>> + Unpin + 'a,
372    for<'a> <T as WriteToAsync<'a, TargetVfs>>::Future:
373        Future<Output = VfsResult<(), TargetVfs>> + Unpin + 'a,
374{
375    Poisson,
376    SamePath,
377    Reading {
378        self_vfs: Pin<&'f SelfVfs>,
379        target_vfs: Pin<&'f TargetVfs>,
380        inner: <T as ReadFromAsync<'f, SelfVfs>>::Future,
381        path: P::OwnedPath,
382    },
383    Writing {
384        inner: <T as WriteToAsync<'f, TargetVfs>>::Future,
385    },
386}
387
388#[cfg(feature = "async")]
389#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
390impl<
391    'f,
392    T,
393    P: PathType + ?Sized + 'f,
394    SelfVfs: VfsAsync<Path = P> + 'f,
395    TargetVfs: WriteSupportingVfsAsync<Path = P> + 'f,
396> Future for DeferredReadWriteRefFuture<'f, T, P, SelfVfs, TargetVfs>
397where
398    T: for<'a> ReadFromAsync<'a, SelfVfs> + for<'a> WriteToAsync<'a, TargetVfs> + Send + 'static,
399    for<'a> <T as ReadFromAsync<'a, SelfVfs>>::Future:
400        Future<Output = VfsResult<T, SelfVfs>> + Unpin + 'a,
401    for<'a> <T as WriteToAsync<'a, TargetVfs>>::Future:
402        Future<Output = VfsResult<(), TargetVfs>> + Unpin + 'a,
403{
404    type Output = Result<(), P::OwnedPath>;
405
406    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
407        let this = self.as_mut().project_replace(Self::Poisson);
408        match this {
409            DeferredReadWriteRefFutureProj::SamePath => Poll::Ready(Ok(())),
410            DeferredReadWriteRefFutureProj::Reading {
411                mut inner,
412                self_vfs,
413                target_vfs,
414                path,
415            } => match Pin::new(&mut inner).poll(cx) {
416                Poll::Ready(Ok(v)) => {
417                    let write_fut = v.write_to_async(path, target_vfs);
418                    self.as_mut()
419                        .project_replace(Self::Writing { inner: write_fut });
420                    cx.waker().wake_by_ref();
421                    Poll::Pending
422                }
423                Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
424                Poll::Pending => {
425                    self.project_replace(Self::Reading {
426                        inner,
427                        self_vfs,
428                        target_vfs,
429                        path,
430                    });
431                    Poll::Pending
432                }
433            },
434            DeferredReadWriteRefFutureProj::Writing { mut inner } => {
435                match Pin::new(&mut inner).poll(cx) {
436                    Poll::Ready(r) => Poll::Ready(r),
437                    Poll::Pending => {
438                        self.as_mut().project_replace(Self::Writing { inner });
439                        cx.waker().wake_by_ref();
440                        Poll::Pending
441                    }
442                }
443            }
444            DeferredReadWriteRefFutureProj::Poisson => {
445                panic!(
446                    "DeferredReadWriteRefFuture is in an invalid state. This is a bug in the code."
447                );
448            }
449        }
450    }
451}
452
453#[cfg(feature = "async")]
454#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
455impl<
456    'f,
457    const CHECK_ON_READ: bool,
458    T,
459    P: PathType + ?Sized + 'f,
460    SelfVfs: VfsAsync<Path = P> + 'f,
461    TargetVfs: WriteSupportingVfsAsync<Path = P> + 'f,
462> WriteToAsyncRef<'f, TargetVfs> for DeferredRead<'f, T, SelfVfs, CHECK_ON_READ>
463where
464    for<'a> T: ReadFromAsync<'a, SelfVfs> + WriteToAsync<'a, TargetVfs> + Send + 'a,
465    for<'a> <T as ReadFromAsync<'a, SelfVfs>>::Future:
466        Future<Output = VfsResult<T, SelfVfs>> + Unpin + 'a,
467    for<'a> <T as WriteToAsync<'a, TargetVfs>>::Future:
468        Future<Output = VfsResult<(), TargetVfs>> + Unpin + 'a,
469{
470    type Future<'a>
471        = DeferredReadWriteFuture<'a, T, P, SelfVfs, TargetVfs>
472    where
473        Self: 'a,
474        'f: 'a;
475
476    fn write_to_async_ref<'a>(
477        self: &'a Self,
478        path: P::OwnedPath,
479        vfs: Pin<&'a TargetVfs>,
480    ) -> Self::Future<'a>
481    where
482        'f: 'a,
483    {
484        if path == self.0 {
485            // Optimization: We were asked to write to the same path
486            // we are supposed to read from. We can just ignore it, since
487            // the file / directory should already be in the given state.
488
489            // If `T` has trivial `ReadFromAsync` / `WriteToAsyncRef` implementations,
490            // this should not be a problem, but if it is, a custom `DeferredRead`
491            // implementation should be written for it.
492            return DeferredReadWriteFuture::SamePath;
493        }
494
495        DeferredReadWriteFuture::Reading {
496            inner: T::read_from_async(self.0.clone(), self.1),
497            path,
498            self_vfs: self.1,
499            target_vfs: vfs,
500        }
501    }
502}
503
504#[cfg(feature = "resolve-path")]
505#[cfg_attr(docsrs, doc(cfg(feature = "resolve-path")))]
506impl<const CHECK_ON_READ: bool, const NAME: [char; HAS_FIELD_MAX_LEN], T, Vfs: VfsCore>
507    HasField<NAME> for DeferredRead<'_, T, Vfs, CHECK_ON_READ>
508where
509    T: HasField<NAME>,
510{
511    type Inner = <T as HasField<NAME>>::Inner;
512
513    fn resolve_path<P: OwnedPathType>(p: P) -> P {
514        T::resolve_path(p)
515    }
516}
517
518#[cfg(feature = "resolve-path")]
519#[cfg_attr(docsrs, doc(cfg(feature = "resolve-path")))]
520impl<const CHECK_ON_READ: bool, T, Vfs: VfsCore> DynamicHasField
521    for DeferredRead<'_, T, Vfs, CHECK_ON_READ>
522where
523    T: DynamicHasField,
524{
525    type Inner = <T as DynamicHasField>::Inner;
526
527    fn resolve_path<P: OwnedPathType>(p: P, name: &str) -> P {
528        T::resolve_path(p, name)
529    }
530}