bevy_asset/io/file/
sync_file_asset.rs

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