bevy_asset/io/file/
file_asset.rs

1use crate::io::{
2    get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, AsyncSeekForward,
3    PathStream, Reader, Writer,
4};
5use async_fs::{read_dir, File};
6use futures_io::AsyncSeek;
7use futures_lite::StreamExt;
8
9use alloc::{borrow::ToOwned, boxed::Box};
10use core::{pin::Pin, task, task::Poll};
11use std::path::Path;
12
13use super::{FileAssetReader, FileAssetWriter};
14
15impl AsyncSeekForward for File {
16    fn poll_seek_forward(
17        mut self: Pin<&mut Self>,
18        cx: &mut task::Context<'_>,
19        offset: u64,
20    ) -> Poll<futures_io::Result<u64>> {
21        let offset: Result<i64, _> = offset.try_into();
22
23        if let Ok(offset) = offset {
24            Pin::new(&mut self).poll_seek(cx, futures_io::SeekFrom::Current(offset))
25        } else {
26            Poll::Ready(Err(std::io::Error::new(
27                std::io::ErrorKind::InvalidInput,
28                "seek position is out of range",
29            )))
30        }
31    }
32}
33
34impl Reader for File {}
35
36impl AssetReader for FileAssetReader {
37    async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
38        let full_path = self.root_path.join(path);
39        File::open(&full_path).await.map_err(|e| {
40            if e.kind() == std::io::ErrorKind::NotFound {
41                AssetReaderError::NotFound(full_path)
42            } else {
43                e.into()
44            }
45        })
46    }
47
48    async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
49        let meta_path = get_meta_path(path);
50        let full_path = self.root_path.join(meta_path);
51        File::open(&full_path).await.map_err(|e| {
52            if e.kind() == std::io::ErrorKind::NotFound {
53                AssetReaderError::NotFound(full_path)
54            } else {
55                e.into()
56            }
57        })
58    }
59
60    async fn read_directory<'a>(
61        &'a self,
62        path: &'a Path,
63    ) -> Result<Box<PathStream>, AssetReaderError> {
64        let full_path = self.root_path.join(path);
65        match read_dir(&full_path).await {
66            Ok(read_dir) => {
67                let root_path = self.root_path.clone();
68                let mapped_stream = read_dir.filter_map(move |f| {
69                    f.ok().and_then(|dir_entry| {
70                        let path = dir_entry.path();
71                        // filter out meta files as they are not considered assets
72                        if let Some(ext) = path.extension().and_then(|e| e.to_str())
73                            && ext.eq_ignore_ascii_case("meta")
74                        {
75                            return None;
76                        }
77                        // filter out hidden files. they are not listed by default but are directly targetable
78                        if path
79                            .file_name()
80                            .and_then(|file_name| file_name.to_str())
81                            .map(|file_name| file_name.starts_with('.'))
82                            .unwrap_or_default()
83                        {
84                            return None;
85                        }
86                        let relative_path = path.strip_prefix(&root_path).unwrap();
87                        Some(relative_path.to_owned())
88                    })
89                });
90                let read_dir: Box<PathStream> = Box::new(mapped_stream);
91                Ok(read_dir)
92            }
93            Err(e) => {
94                if e.kind() == std::io::ErrorKind::NotFound {
95                    Err(AssetReaderError::NotFound(full_path))
96                } else {
97                    Err(e.into())
98                }
99            }
100        }
101    }
102
103    async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
104        let full_path = self.root_path.join(path);
105        let metadata = full_path
106            .metadata()
107            .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
108        Ok(metadata.file_type().is_dir())
109    }
110}
111
112impl AssetWriter for FileAssetWriter {
113    async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
114        let full_path = self.root_path.join(path);
115        if let Some(parent) = full_path.parent() {
116            async_fs::create_dir_all(parent).await?;
117        }
118        let file = File::create(&full_path).await?;
119        let writer: Box<Writer> = Box::new(file);
120        Ok(writer)
121    }
122
123    async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
124        let meta_path = get_meta_path(path);
125        let full_path = self.root_path.join(meta_path);
126        if let Some(parent) = full_path.parent() {
127            async_fs::create_dir_all(parent).await?;
128        }
129        let file = File::create(&full_path).await?;
130        let writer: Box<Writer> = Box::new(file);
131        Ok(writer)
132    }
133
134    async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
135        let full_path = self.root_path.join(path);
136        async_fs::remove_file(full_path).await?;
137        Ok(())
138    }
139
140    async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
141        let meta_path = get_meta_path(path);
142        let full_path = self.root_path.join(meta_path);
143        async_fs::remove_file(full_path).await?;
144        Ok(())
145    }
146
147    async fn rename<'a>(
148        &'a self,
149        old_path: &'a Path,
150        new_path: &'a Path,
151    ) -> Result<(), AssetWriterError> {
152        let full_old_path = self.root_path.join(old_path);
153        let full_new_path = self.root_path.join(new_path);
154        if let Some(parent) = full_new_path.parent() {
155            async_fs::create_dir_all(parent).await?;
156        }
157        async_fs::rename(full_old_path, full_new_path).await?;
158        Ok(())
159    }
160
161    async fn rename_meta<'a>(
162        &'a self,
163        old_path: &'a Path,
164        new_path: &'a Path,
165    ) -> Result<(), AssetWriterError> {
166        let old_meta_path = get_meta_path(old_path);
167        let new_meta_path = get_meta_path(new_path);
168        let full_old_path = self.root_path.join(old_meta_path);
169        let full_new_path = self.root_path.join(new_meta_path);
170        if let Some(parent) = full_new_path.parent() {
171            async_fs::create_dir_all(parent).await?;
172        }
173        async_fs::rename(full_old_path, full_new_path).await?;
174        Ok(())
175    }
176
177    async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
178        let full_path = self.root_path.join(path);
179        async_fs::create_dir_all(full_path).await?;
180        Ok(())
181    }
182
183    async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
184        let full_path = self.root_path.join(path);
185        async_fs::remove_dir_all(full_path).await?;
186        Ok(())
187    }
188
189    async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
190        let full_path = self.root_path.join(path);
191        async_fs::remove_dir(full_path).await?;
192        Ok(())
193    }
194
195    async fn remove_assets_in_directory<'a>(
196        &'a self,
197        path: &'a Path,
198    ) -> Result<(), AssetWriterError> {
199        let full_path = self.root_path.join(path);
200        async_fs::remove_dir_all(&full_path).await?;
201        async_fs::create_dir_all(&full_path).await?;
202        Ok(())
203    }
204}