bevy_asset/io/file/
file_asset.rs1use 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#[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 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 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}