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}