1use fyrox_core::io::FileError;
25use std::ffi::OsStr;
26use std::{
27 fmt::Debug,
28 fs::File,
29 future::{ready, Future},
30 io::{BufReader, Cursor, Read, Seek, Write},
31 iter::empty,
32 path::{Path, PathBuf},
33 pin::Pin,
34};
35
36pub trait FileReader: Debug + Send + Sync + Read + Seek + 'static {
38 fn byte_len(&self) -> Option<u64>;
40}
41
42impl FileReader for File {
43 fn byte_len(&self) -> Option<u64> {
44 match self.metadata() {
45 Ok(metadata) => Some(metadata.len()),
46 _ => None,
47 }
48 }
49}
50
51impl<T> FileReader for Cursor<T>
52where
53 T: Debug + Send + Sync + std::convert::AsRef<[u8]> + 'static,
54{
55 fn byte_len(&self) -> Option<u64> {
56 let inner = self.get_ref();
57 Some(inner.as_ref().len().try_into().unwrap())
58 }
59}
60impl FileReader for BufReader<File> {
61 fn byte_len(&self) -> Option<u64> {
62 self.get_ref().byte_len()
63 }
64}
65
66pub trait ResourceIo: Send + Sync + 'static {
69 fn load_file<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, Result<Vec<u8>, FileError>>;
72
73 fn write_file<'a>(
75 &'a self,
76 path: &'a Path,
77 data: Vec<u8>,
78 ) -> ResourceIoFuture<'a, Result<(), FileError>>;
79
80 fn write_file_sync(&self, path: &Path, data: &[u8]) -> Result<(), FileError>;
83
84 fn move_file<'a>(
86 &'a self,
87 source: &'a Path,
88 dest: &'a Path,
89 ) -> ResourceIoFuture<'a, Result<(), FileError>>;
90
91 fn delete_file<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, Result<(), FileError>>;
93
94 fn delete_file_sync(&self, path: &Path) -> Result<(), FileError>;
96
97 fn copy_file<'a>(
99 &'a self,
100 source: &'a Path,
101 dest: &'a Path,
102 ) -> ResourceIoFuture<'a, Result<(), FileError>>;
103
104 fn canonicalize_path<'a>(
108 &'a self,
109 path: &'a Path,
110 ) -> ResourceIoFuture<'a, Result<PathBuf, FileError>> {
111 Box::pin(ready(Ok(path.to_owned())))
112 }
113
114 fn read_directory<'a>(
119 &'a self,
120 #[allow(unused)] path: &'a Path,
121 ) -> ResourceIoFuture<'a, Result<Box<dyn Iterator<Item = PathBuf> + Send>, FileError>> {
122 let iter: Box<dyn Iterator<Item = PathBuf> + Send> = Box::new(empty());
123 Box::pin(ready(Ok(iter)))
124 }
125
126 fn walk_directory<'a>(
131 &'a self,
132 #[allow(unused)] path: &'a Path,
133 #[allow(unused)] max_depth: usize,
134 ) -> ResourceIoFuture<'a, Result<Box<dyn Iterator<Item = PathBuf> + Send>, FileError>> {
135 let iter: Box<dyn Iterator<Item = PathBuf> + Send> = Box::new(empty());
136 Box::pin(ready(Ok(iter)))
137 }
138
139 fn file_reader<'a>(
145 &'a self,
146 path: &'a Path,
147 ) -> ResourceIoFuture<'a, Result<Box<dyn FileReader>, FileError>> {
148 Box::pin(async move {
149 let bytes = self.load_file(path).await?;
150 let read: Box<dyn FileReader> = Box::new(Cursor::new(bytes));
151 Ok(read)
152 })
153 }
154
155 fn is_valid_file_name(&self, name: &OsStr) -> bool;
157
158 fn exists<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool>;
160
161 fn is_file<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool>;
163
164 fn is_dir<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool>;
166}
167
168#[derive(Default)]
171pub struct FsResourceIo;
172
173#[cfg(target_arch = "wasm32")]
175pub type ResourceIoFuture<'a, V> = Pin<Box<dyn Future<Output = V> + 'a>>;
176#[cfg(not(target_arch = "wasm32"))]
178pub type ResourceIoFuture<'a, V> = Pin<Box<dyn Future<Output = V> + Send + 'a>>;
179
180#[cfg(target_arch = "wasm32")]
182pub type PathIter = Box<dyn Iterator<Item = PathBuf>>;
183#[cfg(not(target_arch = "wasm32"))]
185pub type PathIter = Box<dyn Iterator<Item = PathBuf> + Send>;
186
187impl ResourceIo for FsResourceIo {
188 fn load_file<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, Result<Vec<u8>, FileError>> {
189 Box::pin(fyrox_core::io::load_file(path))
190 }
191
192 fn write_file<'a>(
193 &'a self,
194 path: &'a Path,
195 data: Vec<u8>,
196 ) -> ResourceIoFuture<'a, Result<(), FileError>> {
197 Box::pin(async move {
198 let mut file = File::create(path)?;
199 file.write_all(&data)?;
200 Ok(())
201 })
202 }
203
204 fn write_file_sync(&self, path: &Path, data: &[u8]) -> Result<(), FileError> {
205 let mut file = File::create(path)?;
206 file.write_all(data)?;
207 Ok(())
208 }
209
210 fn move_file<'a>(
211 &'a self,
212 source: &'a Path,
213 dest: &'a Path,
214 ) -> ResourceIoFuture<'a, Result<(), FileError>> {
215 Box::pin(async move {
216 std::fs::rename(source, dest)?;
217 Ok(())
218 })
219 }
220
221 fn delete_file<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, Result<(), FileError>> {
222 Box::pin(async move {
223 std::fs::remove_file(path)?;
224 Ok(())
225 })
226 }
227
228 fn delete_file_sync(&self, path: &Path) -> Result<(), FileError> {
229 std::fs::remove_file(path)?;
230 Ok(())
231 }
232
233 fn copy_file<'a>(
234 &'a self,
235 source: &'a Path,
236 dest: &'a Path,
237 ) -> ResourceIoFuture<'a, Result<(), FileError>> {
238 Box::pin(async move {
239 std::fs::copy(source, dest)?;
240 Ok(())
241 })
242 }
243
244 fn canonicalize_path<'a>(
245 &'a self,
246 path: &'a Path,
247 ) -> ResourceIoFuture<'a, Result<PathBuf, FileError>> {
248 Box::pin(async move { Ok(std::fs::canonicalize(path)?) })
249 }
250
251 #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
257 fn read_directory<'a>(
258 &'a self,
259 #[allow(unused)] path: &'a Path,
260 ) -> ResourceIoFuture<'a, Result<PathIter, FileError>> {
261 Box::pin(async move {
262 let iter = std::fs::read_dir(path)?.flatten().map(|entry| entry.path());
263 let iter: PathIter = Box::new(iter);
264 Ok(iter)
265 })
266 }
267
268 #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
271 fn walk_directory<'a>(
272 &'a self,
273 path: &'a Path,
274 max_depth: usize,
275 ) -> ResourceIoFuture<'a, Result<PathIter, FileError>> {
276 Box::pin(async move {
277 use walkdir::WalkDir;
278
279 let iter = WalkDir::new(path)
280 .max_depth(max_depth)
281 .into_iter()
282 .flatten()
283 .map(|value| value.into_path());
284
285 let iter: PathIter = Box::new(iter);
286
287 Ok(iter)
288 })
289 }
290
291 #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
296 fn file_reader<'a>(
297 &'a self,
298 path: &'a Path,
299 ) -> ResourceIoFuture<'a, Result<Box<dyn FileReader>, FileError>> {
300 Box::pin(async move {
301 let file = match std::fs::File::open(path) {
302 Ok(file) => file,
303 Err(e) => return Err(FileError::Io(e)),
304 };
305
306 let read: Box<dyn FileReader> = Box::new(std::io::BufReader::new(file));
307 Ok(read)
308 })
309 }
310
311 fn is_valid_file_name(&self, name: &OsStr) -> bool {
312 for &byte in name.as_encoded_bytes() {
313 #[cfg(windows)]
314 {
315 if matches!(
316 byte,
317 b'<' | b'>' | b':' | b'"' | b'/' | b'\\' | b'|' | b'?' | b'*'
318 ) {
319 return false;
320 }
321
322 if byte < 32 {
324 return false;
325 }
326 }
327
328 #[cfg(not(windows))]
329 {
330 if matches!(byte, b'0' | b'/') {
331 return false;
332 }
333 }
334 }
335
336 true
337 }
338
339 fn exists<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool> {
340 Box::pin(fyrox_core::io::exists(path))
341 }
342
343 fn is_file<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool> {
344 Box::pin(fyrox_core::io::is_file(path))
345 }
346
347 fn is_dir<'a>(&'a self, path: &'a Path) -> ResourceIoFuture<'a, bool> {
348 Box::pin(fyrox_core::io::is_dir(path))
349 }
350}