dir_structure/traits/
async_vfs.rs

1//! Asynchronous virtual file system traits.
2//!
3//! These traits are similar to the ones in the [`vfs`](super::vfs) module, but they use
4//! asynchronous I/O operations, and return `Future`s resolving to `Result`s instead of
5//! directly returning `Result`s, in line with Rust's async programming model.
6//!
7//! These traits require the `async` feature to be enabled.
8//!
9//! They are designed to be used with async runtimes like Tokio or async-std.
10//!
11//! You should refer to the documentation of the [`vfs`](super::vfs) module for
12//! more details on the individual methods, as the async versions have the same semantics,
13//! just with async I/O.
14//!
15//! An important difference between the [`VfsAsync`] traits and their synchronous counterparts
16//! is that the methods take _owned_ paths instead of references. This is because
17//! the async methods typically need to move the path into the future, and
18//! references would not be valid for the entire duration of the future.
19//!
20//! # Tool specific extensions of the async VFS traits
21//!
22//! Similarly to the synchronous VFS traits, there are also tool-specific extensions
23//! of the async VFS traits, which are required to use certain tools with a specific
24//! [`VfsAsync`] implementation.
25//!
26//! We list them here for convenience, but you should refer to the documentation of the
27//! individual tools for more details.
28//!
29//! ## [`AtomicDir<T>`](crate::atomic_dir::AtomicDir)
30//!
31//! To use the [`AtomicDir<T>`](crate::atomic_dir::AtomicDir) wrapper type with your async VFS implementation,
32//! the VFS type itself must implement the [`VfsSupportsTemporaryDirectories`](crate::atomic_dir::VfsSupportsTemporaryDirectories) trait.
33//! See its documentation for more details.
34//!
35//! ## [Images](crate::image)
36//!
37//! To allow reading and writing image files using the types and traits from the
38//! [`image`](crate::image) module, the following traits need to be implemented for your
39//! async VFS type `YourVfsType`:
40//!
41//! - `impl<T: ImgFormat> ReadImageFromAsync<T> for YourVfsType`                                  to satisfy the bound `T: ReadFromAsync<'vfs, YourVfsType>`
42//! - `impl<'a> WriteImageToAsync<'a> for YourVfsType`                                            to satisfy the bound `T: WriteToAsync<'a, YourVfsType>`
43//! - `impl<'a> WriteImageToAsyncRef<'a, YourVfsType> for YourVfsType`                            to satisfy the bound `T: WriteToAsyncRef<'a, YourVfsType>`
44//!
45//! These impls are required because the image encoding and decoding operations are CPU-bound and blocking,
46//! and thus cannot be implemented in a generic way for all async VFS implementations. They need to be implemented
47//! specifically for each async VFS type.
48//!
49//! You can see the implementations for the [`TokioFsVfs`](crate::vfs::tokio_fs_vfs::TokioFsVfs) VFS, which use
50//! [`tokio::task::spawn_blocking`] to offload the blocking operations to a separate thread pool.
51//!
52//! Your async VFS implementation might use a different async runtime, and thus might need to use a
53//! different method to offload blocking operations, but that is the ideal approach to take.
54//!
55//! You can of course not implement these impls, but then you will not be able to use the image
56//! encoding and decoding operations with your async VFS implementation.
57
58use std::io;
59use std::pin::Pin;
60use std::task::Context;
61use std::task::Poll;
62
63use futures::AsyncWrite;
64use futures::Stream;
65use futures::io::AsyncRead;
66use futures::io::AsyncSeek;
67use pin_project::pin_project;
68
69use crate::error::Error;
70use crate::error::Result;
71use crate::error::VfsResult;
72use crate::prelude::*;
73use crate::traits::vfs::DirEntryInfo;
74use crate::traits::vfs::OwnedPathType;
75use crate::traits::vfs::PathType;
76use crate::traits::vfs::VfsCore;
77
78/// An asynchronous virtual file system.
79///
80/// This is the asynchronous counterpart to the [`Vfs`] trait in the [`vfs`](super::vfs) module.
81///
82/// Writing operations are provided by the [`WriteSupportingVfsAsync` trait](self::WriteSupportingVfsAsync).
83pub trait VfsAsync: VfsCore + Send + Sync + Unpin {
84    /// The type of the file returned by the [`open_read` method](VfsAsync::open_read).
85    type RFile: AsyncRead + Send + Unpin;
86    /// The future returned by the [`open_read` method](VfsAsync::open_read).
87    type OpenReadFuture: Future<Output = VfsResult<Self::RFile, Self>> + Send + Unpin;
88
89    /// Opens a file for reading, at the specified path.
90    fn open_read(
91        self: Pin<&Self>,
92        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
93    ) -> Self::OpenReadFuture;
94
95    /// The future returned by the [`read` method](VfsAsync::read).
96    type ReadFuture<'a>: Future<Output = VfsResult<Vec<u8>, Self>> + Send + Unpin + 'a
97    where
98        Self: 'a;
99
100    /// Reads the contents of a file, at the specified path.
101    fn read<'a>(
102        self: Pin<&'a Self>,
103        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
104    ) -> Self::ReadFuture<'a>;
105
106    /// The future returned by the [`read_string` method](VfsAsync::read_string).
107    type ReadStringFuture<'a>: Future<Output = VfsResult<String, Self>> + Send + Unpin + 'a
108    where
109        Self: 'a;
110
111    /// Reads the contents of a file, at the specified path, and returns it as a string.
112    fn read_string<'a>(
113        self: Pin<&'a Self>,
114        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
115    ) -> Self::ReadStringFuture<'a>;
116
117    /// The future returned by the [`exists` method](VfsAsync::exists).
118    type ExistsFuture<'a>: Future<Output = VfsResult<bool, Self>> + Send + 'a
119    where
120        Self: 'a;
121
122    /// Checks if a file exists at the specified path.
123    fn exists<'a>(
124        self: Pin<&'a Self>,
125        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
126    ) -> Self::ExistsFuture<'a>;
127
128    /// The future type returned by the [`is_dir` method](VfsAsync::is_dir).
129    type IsDirFuture<'a>: Future<Output = VfsResult<bool, Self>> + Send + 'a
130    where
131        Self: 'a;
132
133    /// Checks if a directory exists at the specified path.
134    fn is_dir<'a>(
135        self: Pin<&'a Self>,
136        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
137    ) -> Self::IsDirFuture<'a>;
138
139    /// The stream type returned by the [`DirWalkFuture`](VfsAsync::DirWalkFuture).
140    type DirWalk<'a>: Stream<Item = VfsResult<DirEntryInfo<<Self as VfsCore>::Path>, Self>>
141        + Send
142        + 'a
143    where
144        Self: 'a;
145
146    /// The future type returned by the [`walk_dir` method](VfsAsync::walk_dir).
147    type DirWalkFuture<'a>: Future<Output = VfsResult<Self::DirWalk<'a>, Self>> + Send + 'a
148    where
149        Self: 'a;
150
151    /// Walks a directory at the given path, returning a stream of directory entries.
152    fn walk_dir<'a>(
153        self: Pin<&'a Self>,
154        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
155    ) -> Self::DirWalkFuture<'a>;
156}
157
158/// Marks that the [`RFile`](VfsAsync::RFile) type of this [`VfsAsync`] also implements
159/// [`AsyncSeek`], allowing it to be used in contexts that require seeking, such as image decoding.
160///
161/// This trait is automatically implemented for any [`VfsAsync`] whose [`RFile`](VfsAsync::RFile) implements
162/// [`AsyncSeek`].
163pub trait VfsAsyncWithSeekRead: VfsAsync
164where
165    Self::RFile: AsyncSeek + Send + Unpin,
166{
167}
168
169impl<T: VfsAsync> VfsAsyncWithSeekRead for T where T::RFile: AsyncSeek + Send + Unpin {}
170
171/// Extension trait for [`VfsAsync`] that provides additional convenience methods.
172pub trait VfsAsyncExt: VfsAsync {
173    /// Reads a file / directory at the specified path, and parses it into the specified type using its
174    /// [`ReadFromAsync`] implementation.
175    ///
176    /// This method takes `self` as a pinned reference, to ensure that the [`VfsAsync`] value
177    /// is not moved while the read operation is in progress.
178    fn read_typed_async_pinned<'a, T: ReadFromAsync<'a, Self>>(
179        self: Pin<&'a Self>,
180        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
181    ) -> T::Future {
182        T::read_from_async(path.into(), self)
183    }
184
185    /// Reads a file / directory at the specified path, and parses it into the specified type using its
186    /// [`ReadFromAsync`] implementation.
187    ///
188    /// This method takes `self` as a regular reference, and pins it internally.
189    fn read_typed_async<'a, T: ReadFromAsync<'a, Self>>(
190        &'a self,
191        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
192    ) -> T::Future {
193        Pin::new(self).read_typed_async_pinned::<T>(path)
194    }
195}
196
197// Blanket impl.
198impl<V: VfsAsync + ?Sized> VfsAsyncExt for V {}
199
200/// A virtual file system that supports writing operations.
201pub trait WriteSupportingVfsAsync: VfsAsync {
202    /// The type of the file returned by the [`open_write` method](WriteSupportingVfsAsync::open_write).
203    type WFile: AsyncWrite + Send + Unpin;
204
205    /// The future type returned by the [`open_write` method](WriteSupportingVfsAsync::open_write).
206    type OpenWriteFuture: Future<Output = VfsResult<Self::WFile, Self>> + Send + Unpin;
207
208    /// Opens a file for writing, at the specified path.
209    fn open_write(
210        self: Pin<&Self>,
211        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
212    ) -> Self::OpenWriteFuture;
213
214    /// The future type returned by the [`write` method](WriteSupportingVfsAsync::write).
215    type WriteFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + Unpin + 'a
216    where
217        Self: 'a;
218
219    /// Writes the contents of a file, at the specified path.
220    fn write<'d, 'a: 'd>(
221        self: Pin<&'a Self>,
222        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
223        data: &'d [u8],
224    ) -> Self::WriteFuture<'d>;
225
226    /// The future type returned by the [`remove_dir_all` method](WriteSupportingVfsAsync::remove_dir_all).
227    type RemoveDirAllFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + 'a
228    where
229        Self: 'a;
230
231    /// Removes a directory and all its contents.
232    fn remove_dir_all<'a>(
233        self: Pin<&'a Self>,
234        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
235    ) -> Self::RemoveDirAllFuture<'a>;
236
237    /// The future type returned by the [`create_dir` method](WriteSupportingVfsAsync::create_dir).
238    type CreateDirFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + 'a
239    where
240        Self: 'a;
241    /// Creates a new directory at the specified path.
242    fn create_dir<'a>(
243        self: Pin<&'a Self>,
244        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
245    ) -> Self::CreateDirFuture<'a>;
246
247    /// The future type returned by the [`create_dir_all` method](WriteSupportingVfsAsync::create_dir_all).
248    type CreateDirAllFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + 'a
249    where
250        Self: 'a;
251    /// Creates a new directory and all its parent directories at the specified path.
252    fn create_dir_all<'a>(
253        self: Pin<&'a Self>,
254        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
255    ) -> Self::CreateDirAllFuture<'a>;
256
257    /// The future type returned by the [`create_parent_dir` method](WriteSupportingVfsAsync::create_parent_dir).
258    type CreateParentDirFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + 'a
259    where
260        Self: 'a;
261    /// Creates a new parent directory at the specified path.
262    fn create_parent_dir<'a>(
263        self: Pin<&'a Self>,
264        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
265    ) -> Self::CreateParentDirFuture<'a>;
266}
267
268/// Marks that the [`WFile`](WriteSupportingVfsAsync::WFile) type of this [`WriteSupportingVfsAsync`] also implements
269/// [`AsyncSeek`], allowing it to be used in
270/// contexts that require seeking.
271pub trait VfsAsyncWithSeekWrite: WriteSupportingVfsAsync
272where
273    Self::WFile: AsyncSeek + Send + Unpin,
274{
275}
276
277impl<T: WriteSupportingVfsAsync> VfsAsyncWithSeekWrite for T where T::WFile: AsyncSeek + Send + Unpin
278{}
279
280/// Extension trait for [`WriteSupportingVfsAsync`] that provides additional convenience methods.
281pub trait WriteSupportingVfsAsyncExt: WriteSupportingVfsAsync {
282    /// Writes a file / directory at the specified path, using the specified data type's
283    /// [`WriteToAsync`] implementation.
284    ///
285    /// This method takes `self` as a pinned reference, to ensure that the `VfsAsync` implementation
286    /// is not moved while the write operation is in progress.
287    fn write_typed_async_ref_pinned<'r, 'a: 'r, T: WriteToAsyncRef<'a, Self>>(
288        self: Pin<&'r Self>,
289        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
290        value: &'r T,
291    ) -> T::Future<'r> {
292        T::write_to_async_ref(value, path.into(), self)
293    }
294
295    /// Writes a file / directory at the specified path, using the specified data type's
296    /// [`WriteToAsync`] implementation.
297    ///
298    /// This method takes `self` as a regular reference, and pins it internally.
299    fn write_typed_async_ref<'r, 'a: 'r, T: WriteToAsyncRef<'a, Self>>(
300        &'r self,
301        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
302        data: &'r T,
303    ) -> T::Future<'r>
304    where
305        Self: Unpin,
306    {
307        Pin::new(self).write_typed_async_ref_pinned(path, data)
308    }
309
310    /// Writes a file / directory at the specified path, using the specified data type's
311    /// [`WriteToAsync`] implementation.
312    ///
313    /// This method takes `self` as a pinned reference, to ensure that the `VfsAsync` implementation
314    /// is not moved while the write operation is in progress.
315    fn write_typed_async_pinned<'a, T: WriteToAsync<'a, Self>>(
316        self: Pin<&'a Self>,
317        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
318        value: T,
319    ) -> T::Future {
320        value.write_to_async(path.into(), self)
321    }
322
323    /// Writes a file / directory at the specified path, using the specified data type's
324    /// [`WriteToAsync`] implementation.
325    ///
326    /// This method takes `self` as a regular reference, and pins it internally.
327    fn write_typed_async<'a, T: WriteToAsync<'a, Self>>(
328        &'a self,
329        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
330        value: T,
331    ) -> T::Future
332    where
333        Self: Unpin,
334    {
335        Pin::new(self).write_typed_async_pinned(path, value)
336    }
337}
338
339// Blanket impl.
340impl<V: WriteSupportingVfsAsync + ?Sized> WriteSupportingVfsAsyncExt for V {}
341
342#[pin_project(project_replace = CreateParentDirDefaultFutureProjOwn)]
343#[doc(hidden)]
344pub enum CreateParentDirDefaultFuture<'a, Vfs: WriteSupportingVfsAsync + 'a>
345where
346    for<'f> Vfs::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin,
347    for<'f> Vfs::CreateDirAllFuture<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin,
348{
349    Poison,
350    Start {
351        vfs: Pin<&'a Vfs>,
352        path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
353    },
354    ExistsFuture {
355        vfs: Pin<&'a Vfs>,
356        path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
357        exists_future: Vfs::ExistsFuture<'a>,
358    },
359    CreateDirAllFuture {
360        vfs: Pin<&'a Vfs>,
361        create_dir_all_future: Vfs::CreateDirAllFuture<'a>,
362    },
363}
364
365impl<'a, Vfs: WriteSupportingVfsAsync + 'a> Future for CreateParentDirDefaultFuture<'a, Vfs>
366where
367    for<'f> Vfs::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin,
368    for<'f> Vfs::CreateDirAllFuture<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin,
369{
370    type Output = VfsResult<(), Vfs>;
371
372    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
373        let this = self.as_mut().project_replace(Self::Poison);
374        match this {
375            CreateParentDirDefaultFutureProjOwn::Start { vfs, path } => {
376                self.project_replace(Self::ExistsFuture {
377                    exists_future: vfs.exists(path.clone()),
378                    vfs,
379                    path,
380                });
381                cx.waker().wake_by_ref();
382                Poll::Pending
383            }
384            CreateParentDirDefaultFutureProjOwn::ExistsFuture {
385                vfs,
386                path,
387                mut exists_future,
388            } => match Pin::new(&mut exists_future).poll(cx) {
389                Poll::Ready(Ok(true)) => Poll::Ready(Ok(())),
390                Poll::Ready(Ok(false)) => {
391                    self.project_replace(Self::CreateDirAllFuture {
392                        create_dir_all_future: vfs.create_dir_all(path),
393                        vfs,
394                    });
395                    cx.waker().wake_by_ref();
396                    Poll::Pending
397                }
398                Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
399                Poll::Pending => {
400                    self.project_replace(Self::ExistsFuture {
401                        vfs,
402                        path,
403                        exists_future,
404                    });
405                    Poll::Pending
406                }
407            },
408            CreateParentDirDefaultFutureProjOwn::CreateDirAllFuture {
409                vfs,
410                mut create_dir_all_future,
411            } => match Pin::new(&mut create_dir_all_future).poll(cx) {
412                Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
413                Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
414                Poll::Pending => {
415                    self.project_replace(Self::CreateDirAllFuture {
416                        vfs,
417                        create_dir_all_future,
418                    });
419                    Poll::Pending
420                }
421            },
422            CreateParentDirDefaultFutureProjOwn::Poison => {
423                panic!("CreateParentDirDefaultFuture polled after completion")
424            }
425        }
426    }
427}
428
429#[pin_project(project = IoErrorWrapperFutureProj)]
430#[doc(hidden)]
431pub struct IoErrorWrapperFuture<T, F: Future<Output = io::Result<T>>, P: OwnedPathType> {
432    #[pin]
433    future: F,
434    path: P,
435}
436
437impl<T, F, P> IoErrorWrapperFuture<T, F, P>
438where
439    F: Future<Output = io::Result<T>>,
440    P: OwnedPathType,
441{
442    pub fn new(path: P, future: F) -> Self {
443        Self { future, path }
444    }
445}
446
447impl<T, F, P> Future for IoErrorWrapperFuture<T, F, P>
448where
449    F: Future<Output = io::Result<T>>,
450    P: OwnedPathType + Clone,
451{
452    type Output = Result<T, P>;
453
454    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
455        let this = self.as_mut().project();
456        match this.future.poll(cx) {
457            Poll::Ready(Ok(value)) => Poll::Ready(Ok(value)),
458            Poll::Ready(Err(e)) => {
459                let path = self.path.clone();
460                Poll::Ready(Err(Error::Io(path, e)))
461            }
462            Poll::Pending => Poll::Pending,
463        }
464    }
465}