bevy_asset/io/file/
file_asset.rs

1use crate::io::{
2    get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream,
3    Reader, ReaderNotSeekableError, SeekableReader, Writer,
4};
5use async_fs::{read_dir, File};
6#[cfg(not(target_os = "windows"))]
7use async_io::Timer;
8#[cfg(not(target_os = "windows"))]
9use async_lock::{Semaphore, SemaphoreGuard};
10use futures_lite::StreamExt;
11
12use alloc::{borrow::ToOwned, boxed::Box};
13#[cfg(target_os = "windows")]
14use core::marker::PhantomData;
15#[cfg(not(target_os = "windows"))]
16use core::time::Duration;
17#[cfg(not(target_os = "windows"))]
18use futures_util::{future, pin_mut};
19use std::path::Path;
20
21use super::{FileAssetReader, FileAssetWriter};
22
23impl Reader for File {
24    fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
25        Ok(self)
26    }
27}
28
29// Set to OS default limit / 2
30// macos & ios: 256
31// linux & android: 1024
32#[cfg(any(target_os = "macos", target_os = "ios"))]
33static OPEN_FILE_LIMITER: Semaphore = Semaphore::new(128);
34#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows")))]
35static OPEN_FILE_LIMITER: Semaphore = Semaphore::new(512);
36
37#[cfg(not(target_os = "windows"))]
38async fn maybe_get_semaphore<'a>() -> Option<SemaphoreGuard<'a>> {
39    let guard_future = OPEN_FILE_LIMITER.acquire();
40    let timeout_future = Timer::after(Duration::from_millis(500));
41    pin_mut!(guard_future);
42    pin_mut!(timeout_future);
43
44    match future::select(guard_future, timeout_future).await {
45        future::Either::Left((guard, _)) => Some(guard),
46        future::Either::Right((_, _)) => None,
47    }
48}
49
50struct GuardedFile<'a> {
51    file: File,
52    #[cfg(not(target_os = "windows"))]
53    _guard: Option<SemaphoreGuard<'a>>,
54    #[cfg(target_os = "windows")]
55    _lifetime: PhantomData<&'a ()>,
56}
57
58impl<'a> futures_io::AsyncRead for GuardedFile<'a> {
59    fn poll_read(
60        mut self: core::pin::Pin<&mut Self>,
61        cx: &mut core::task::Context<'_>,
62        buf: &mut [u8],
63    ) -> core::task::Poll<std::io::Result<usize>> {
64        core::pin::Pin::new(&mut self.file).poll_read(cx, buf)
65    }
66}
67
68impl<'a> Reader for GuardedFile<'a> {
69    fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
70        self.file.seekable()
71    }
72}
73
74impl AssetReader for FileAssetReader {
75    async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
76        #[cfg(not(target_os = "windows"))]
77        let _guard = maybe_get_semaphore().await;
78
79        let full_path = self.root_path.join(path);
80        File::open(&full_path)
81            .await
82            .map_err(|e| {
83                if e.kind() == std::io::ErrorKind::NotFound {
84                    AssetReaderError::NotFound(full_path)
85                } else {
86                    e.into()
87                }
88            })
89            .map(|file| GuardedFile {
90                file,
91                #[cfg(not(target_os = "windows"))]
92                _guard,
93                #[cfg(target_os = "windows")]
94                _lifetime: PhantomData::default(),
95            })
96    }
97
98    async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
99        #[cfg(not(target_os = "windows"))]
100        let _guard = maybe_get_semaphore().await;
101
102        let meta_path = get_meta_path(path);
103        let full_path = self.root_path.join(meta_path);
104        File::open(&full_path)
105            .await
106            .map_err(|e| {
107                if e.kind() == std::io::ErrorKind::NotFound {
108                    AssetReaderError::NotFound(full_path)
109                } else {
110                    e.into()
111                }
112            })
113            .map(|file| GuardedFile {
114                file,
115                #[cfg(not(target_os = "windows"))]
116                _guard,
117                #[cfg(target_os = "windows")]
118                _lifetime: PhantomData::default(),
119            })
120    }
121
122    async fn read_directory<'a>(
123        &'a self,
124        path: &'a Path,
125    ) -> Result<Box<PathStream>, AssetReaderError> {
126        let full_path = self.root_path.join(path);
127        match read_dir(&full_path).await {
128            Ok(read_dir) => {
129                let root_path = self.root_path.clone();
130                let mapped_stream = read_dir.filter_map(move |f| {
131                    f.ok().and_then(|dir_entry| {
132                        let path = dir_entry.path();
133                        // filter out meta files as they are not considered assets
134                        if let Some(ext) = path.extension().and_then(|e| e.to_str())
135                            && ext.eq_ignore_ascii_case("meta")
136                        {
137                            return None;
138                        }
139                        // filter out hidden files. they are not listed by default but are directly targetable
140                        if path
141                            .file_name()
142                            .and_then(|file_name| file_name.to_str())
143                            .map(|file_name| file_name.starts_with('.'))
144                            .unwrap_or_default()
145                        {
146                            return None;
147                        }
148                        let relative_path = path.strip_prefix(&root_path).unwrap();
149                        Some(relative_path.to_owned())
150                    })
151                });
152                let read_dir: Box<PathStream> = Box::new(mapped_stream);
153                Ok(read_dir)
154            }
155            Err(e) => {
156                if e.kind() == std::io::ErrorKind::NotFound {
157                    Err(AssetReaderError::NotFound(full_path))
158                } else {
159                    Err(e.into())
160                }
161            }
162        }
163    }
164
165    async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
166        let full_path = self.root_path.join(path);
167        let metadata = full_path
168            .metadata()
169            .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
170        Ok(metadata.file_type().is_dir())
171    }
172}
173
174impl AssetWriter for FileAssetWriter {
175    async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
176        let full_path = self.root_path.join(path);
177        if let Some(parent) = full_path.parent() {
178            async_fs::create_dir_all(parent).await?;
179        }
180        let file = File::create(&full_path).await?;
181        let writer: Box<Writer> = Box::new(file);
182        Ok(writer)
183    }
184
185    async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
186        let meta_path = get_meta_path(path);
187        let full_path = self.root_path.join(meta_path);
188        if let Some(parent) = full_path.parent() {
189            async_fs::create_dir_all(parent).await?;
190        }
191        let file = File::create(&full_path).await?;
192        let writer: Box<Writer> = Box::new(file);
193        Ok(writer)
194    }
195
196    async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
197        let full_path = self.root_path.join(path);
198        async_fs::remove_file(full_path).await?;
199        Ok(())
200    }
201
202    async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
203        let meta_path = get_meta_path(path);
204        let full_path = self.root_path.join(meta_path);
205        async_fs::remove_file(full_path).await?;
206        Ok(())
207    }
208
209    async fn rename<'a>(
210        &'a self,
211        old_path: &'a Path,
212        new_path: &'a Path,
213    ) -> Result<(), AssetWriterError> {
214        let full_old_path = self.root_path.join(old_path);
215        let full_new_path = self.root_path.join(new_path);
216        if let Some(parent) = full_new_path.parent() {
217            async_fs::create_dir_all(parent).await?;
218        }
219        async_fs::rename(full_old_path, full_new_path).await?;
220        Ok(())
221    }
222
223    async fn rename_meta<'a>(
224        &'a self,
225        old_path: &'a Path,
226        new_path: &'a Path,
227    ) -> Result<(), AssetWriterError> {
228        let old_meta_path = get_meta_path(old_path);
229        let new_meta_path = get_meta_path(new_path);
230        let full_old_path = self.root_path.join(old_meta_path);
231        let full_new_path = self.root_path.join(new_meta_path);
232        if let Some(parent) = full_new_path.parent() {
233            async_fs::create_dir_all(parent).await?;
234        }
235        async_fs::rename(full_old_path, full_new_path).await?;
236        Ok(())
237    }
238
239    async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
240        let full_path = self.root_path.join(path);
241        async_fs::create_dir_all(full_path).await?;
242        Ok(())
243    }
244
245    async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
246        let full_path = self.root_path.join(path);
247        async_fs::remove_dir_all(full_path).await?;
248        Ok(())
249    }
250
251    async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
252        let full_path = self.root_path.join(path);
253        async_fs::remove_dir(full_path).await?;
254        Ok(())
255    }
256
257    async fn remove_assets_in_directory<'a>(
258        &'a self,
259        path: &'a Path,
260    ) -> Result<(), AssetWriterError> {
261        let full_path = self.root_path.join(path);
262        async_fs::remove_dir_all(&full_path).await?;
263        async_fs::create_dir_all(&full_path).await?;
264        Ok(())
265    }
266}