fyrox_resource/
io.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Provides an interface for IO operations that a resource loader will use, this facilliates
22//! things such as loading assets within archive files
23
24use fyrox_core::io::FileLoadError;
25use std::future::{ready, Future};
26use std::iter::empty;
27use std::pin::Pin;
28use std::{
29    fmt::Debug,
30    io::{Cursor, Read, Seek},
31    path::{Path, PathBuf},
32};
33
34/// Trait for files readers ensuring they implement the required traits
35pub trait FileReader: Debug + Send + Read + Seek + 'static {}
36
37impl<F> FileReader for F where F: Debug + Send + Read + Seek + 'static {}
38
39/// Interface wrapping IO operations for doing this like loading files
40/// for resources
41pub trait ResourceIo: Send + Sync + 'static {
42    /// Attempts to load the file at the provided path returning
43    /// the entire byte contents of the file or an error
44    fn load_file<'a>(
45        &'a self,
46        path: &'a Path,
47    ) -> ResourceIoFuture<'a, Result<Vec<u8>, FileLoadError>>;
48
49    /// Attempts to move a file at the given `source` path to the given `dest` path.
50    fn move_file<'a>(
51        &'a self,
52        source: &'a Path,
53        dest: &'a Path,
54    ) -> ResourceIoFuture<'a, Result<(), FileLoadError>>;
55
56    /// Tries to convert the path to its canonical form (normalize it in other terms). This method
57    /// should guarantee correct behaviour for relative paths. Symlinks aren't mandatory to
58    /// follow.
59    fn canonicalize_path<'a>(
60        &'a self,
61        path: &'a Path,
62    ) -> ResourceIoFuture<'a, Result<PathBuf, FileLoadError>> {
63        Box::pin(ready(Ok(path.to_owned())))
64    }
65
66    /// Provides an iterator over the paths present in the provided
67    /// path, this should only provide paths immediately within the directory
68    ///
69    /// Default implementation is no-op returning an empty iterator
70    fn read_directory<'a>(
71        &'a self,
72        #[allow(unused)] path: &'a Path,
73    ) -> ResourceIoFuture<'a, Result<Box<dyn Iterator<Item = PathBuf> + Send>, FileLoadError>> {
74        let iter: Box<dyn Iterator<Item = PathBuf> + Send> = Box::new(empty());
75        Box::pin(ready(Ok(iter)))
76    }
77
78    /// Provides an iterator over the paths present in the provided
79    /// path directory this implementation should walk the directory paths
80    ///
81    /// Default implementation is no-op returning an empty iterator
82    fn walk_directory<'a>(
83        &'a self,
84        #[allow(unused)] path: &'a Path,
85    ) -> ResourceIoFuture<'a, Result<Box<dyn Iterator<Item = PathBuf> + Send>, FileLoadError>> {
86        let iter: Box<dyn Iterator<Item = PathBuf> + Send> = Box::new(empty());
87        Box::pin(ready(Ok(iter)))
88    }
89
90    /// Attempts to open a file reader to the proivded path for
91    /// reading its bytes
92    ///
93    /// Default implementation loads the entire file contents from `load_file`
94    /// then uses a cursor as the reader
95    fn file_reader<'a>(
96        &'a self,
97        path: &'a Path,
98    ) -> ResourceIoFuture<'a, Result<Box<dyn FileReader>, FileLoadError>> {
99        Box::pin(async move {
100            let bytes = self.load_file(path).await?;
101            let read: Box<dyn FileReader> = Box::new(Cursor::new(bytes));
102            Ok(read)
103        })
104    }
105
106    /// Used to check whether a path exists
107    fn exists<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool>;
108
109    /// Used to check whether a path is a file
110    fn is_file<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool>;
111
112    /// Used to check whether a path is a dir
113    fn is_dir<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool>;
114}
115
116/// Standard resource IO provider that uses the file system to
117/// load the file bytes
118#[derive(Default)]
119pub struct FsResourceIo;
120
121/// Future for resource io loading
122#[cfg(target_arch = "wasm32")]
123pub type ResourceIoFuture<'a, V> = Pin<Box<dyn Future<Output = V> + 'a>>;
124/// Future for resource io loading
125#[cfg(not(target_arch = "wasm32"))]
126pub type ResourceIoFuture<'a, V> = Pin<Box<dyn Future<Output = V> + Send + 'a>>;
127
128/// Iterator of paths
129#[cfg(target_arch = "wasm32")]
130pub type PathIter = Box<dyn Iterator<Item = PathBuf>>;
131/// Iterator of paths
132#[cfg(not(target_arch = "wasm32"))]
133pub type PathIter = Box<dyn Iterator<Item = PathBuf> + Send>;
134
135impl ResourceIo for FsResourceIo {
136    fn load_file<'a>(
137        &'a self,
138        path: &'a Path,
139    ) -> ResourceIoFuture<'a, Result<Vec<u8>, FileLoadError>> {
140        Box::pin(fyrox_core::io::load_file(path))
141    }
142
143    fn move_file<'a>(
144        &'a self,
145        source: &'a Path,
146        dest: &'a Path,
147    ) -> ResourceIoFuture<'a, Result<(), FileLoadError>> {
148        Box::pin(async move {
149            std::fs::rename(source, dest)?;
150            Ok(())
151        })
152    }
153
154    fn canonicalize_path<'a>(
155        &'a self,
156        path: &'a Path,
157    ) -> ResourceIoFuture<'a, Result<PathBuf, FileLoadError>> {
158        Box::pin(async move { Ok(std::fs::canonicalize(path)?) })
159    }
160
161    /// wasm should fallback to the default no-op impl as im not sure if they
162    /// can directly read a directory
163    ///
164    /// Note: Android directory reading should be possible just I have not created
165    /// an implementation for this yet
166    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
167    fn read_directory<'a>(
168        &'a self,
169        #[allow(unused)] path: &'a Path,
170    ) -> ResourceIoFuture<'a, Result<PathIter, FileLoadError>> {
171        Box::pin(async move {
172            let iter = std::fs::read_dir(path)?.flatten().map(|entry| entry.path());
173            let iter: PathIter = Box::new(iter);
174            Ok(iter)
175        })
176    }
177
178    /// Android and wasm should fallback to the default no-op impl as they cant be
179    /// walked with WalkDir
180    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
181    fn walk_directory<'a>(
182        &'a self,
183        path: &'a Path,
184    ) -> ResourceIoFuture<'a, Result<PathIter, FileLoadError>> {
185        Box::pin(async move {
186            use walkdir::WalkDir;
187
188            let iter = WalkDir::new(path)
189                .into_iter()
190                .flatten()
191                .map(|value| value.into_path());
192
193            let iter: PathIter = Box::new(iter);
194
195            Ok(iter)
196        })
197    }
198
199    /// Only use file reader when not targetting android or wasm
200    ///
201    /// Note: Might be possible to use the Android Asset struct for reading as
202    /// long as its Send + Sync + 'static (It already implements Debug + Read + Seek)
203    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
204    fn file_reader<'a>(
205        &'a self,
206        path: &'a Path,
207    ) -> ResourceIoFuture<'a, Result<Box<dyn FileReader>, FileLoadError>> {
208        Box::pin(async move {
209            let file = match std::fs::File::open(path) {
210                Ok(file) => file,
211                Err(e) => return Err(FileLoadError::Io(e)),
212            };
213
214            let read: Box<dyn FileReader> = Box::new(std::io::BufReader::new(file));
215            Ok(read)
216        })
217    }
218
219    fn exists<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool> {
220        Box::pin(fyrox_core::io::exists(path))
221    }
222
223    fn is_file<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool> {
224        Box::pin(fyrox_core::io::is_file(path))
225    }
226
227    fn is_dir<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool> {
228        Box::pin(fyrox_core::io::is_dir(path))
229    }
230}