Path: blob/main/crates/bevy_asset/src/io/file/file_asset.rs
9492 views
use crate::io::{1get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream,2Reader, ReaderNotSeekableError, SeekableReader, Writer,3};4use async_fs::{read_dir, File};5#[cfg(not(target_os = "windows"))]6use async_io::Timer;7#[cfg(not(target_os = "windows"))]8use async_lock::{Semaphore, SemaphoreGuard};9use futures_lite::StreamExt;1011use alloc::{borrow::ToOwned, boxed::Box};12#[cfg(target_os = "windows")]13use core::marker::PhantomData;14#[cfg(not(target_os = "windows"))]15use core::time::Duration;16#[cfg(not(target_os = "windows"))]17use futures_util::{future, pin_mut};18use std::path::Path;1920use super::{FileAssetReader, FileAssetWriter};2122impl Reader for File {23fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {24Ok(self)25}26}2728// Set to OS default limit / 229// macos & ios: 25630// linux & android: 102431#[cfg(any(target_os = "macos", target_os = "ios"))]32static OPEN_FILE_LIMITER: Semaphore = Semaphore::new(128);33#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows")))]34static OPEN_FILE_LIMITER: Semaphore = Semaphore::new(512);3536#[cfg(not(target_os = "windows"))]37async fn maybe_get_semaphore<'a>() -> Option<SemaphoreGuard<'a>> {38let guard_future = OPEN_FILE_LIMITER.acquire();39let timeout_future = Timer::after(Duration::from_millis(500));40pin_mut!(guard_future);41pin_mut!(timeout_future);4243match future::select(guard_future, timeout_future).await {44future::Either::Left((guard, _)) => Some(guard),45future::Either::Right((_, _)) => None,46}47}4849struct GuardedFile<'a> {50file: File,51#[cfg(not(target_os = "windows"))]52_guard: Option<SemaphoreGuard<'a>>,53#[cfg(target_os = "windows")]54_lifetime: PhantomData<&'a ()>,55}5657impl<'a> futures_io::AsyncRead for GuardedFile<'a> {58fn poll_read(59mut self: core::pin::Pin<&mut Self>,60cx: &mut core::task::Context<'_>,61buf: &mut [u8],62) -> core::task::Poll<std::io::Result<usize>> {63core::pin::Pin::new(&mut self.file).poll_read(cx, buf)64}65}6667impl<'a> Reader for GuardedFile<'a> {68fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {69self.file.seekable()70}71}7273impl AssetReader for FileAssetReader {74async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {75#[cfg(not(target_os = "windows"))]76let _guard = maybe_get_semaphore().await;7778let full_path = self.root_path.join(path);79File::open(&full_path)80.await81.map_err(|e| {82if e.kind() == std::io::ErrorKind::NotFound {83AssetReaderError::NotFound(full_path)84} else {85e.into()86}87})88.map(|file| GuardedFile {89file,90#[cfg(not(target_os = "windows"))]91_guard,92#[cfg(target_os = "windows")]93_lifetime: PhantomData,94})95}9697async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {98#[cfg(not(target_os = "windows"))]99let _guard = maybe_get_semaphore().await;100101let meta_path = get_meta_path(path);102let full_path = self.root_path.join(meta_path);103File::open(&full_path)104.await105.map_err(|e| {106if e.kind() == std::io::ErrorKind::NotFound {107AssetReaderError::NotFound(full_path)108} else {109e.into()110}111})112.map(|file| GuardedFile {113file,114#[cfg(not(target_os = "windows"))]115_guard,116#[cfg(target_os = "windows")]117_lifetime: PhantomData,118})119}120121async fn read_directory<'a>(122&'a self,123path: &'a Path,124) -> Result<Box<PathStream>, AssetReaderError> {125let full_path = self.root_path.join(path);126match read_dir(&full_path).await {127Ok(read_dir) => {128let root_path = self.root_path.clone();129let mapped_stream = read_dir.filter_map(move |f| {130f.ok().and_then(|dir_entry| {131let path = dir_entry.path();132// filter out meta files as they are not considered assets133if let Some(ext) = path.extension().and_then(|e| e.to_str())134&& ext.eq_ignore_ascii_case("meta")135{136return None;137}138// filter out hidden files. they are not listed by default but are directly targetable139if path140.file_name()141.and_then(|file_name| file_name.to_str())142.map(|file_name| file_name.starts_with('.'))143.unwrap_or_default()144{145return None;146}147let relative_path = path.strip_prefix(&root_path).unwrap();148Some(relative_path.to_owned())149})150});151let read_dir: Box<PathStream> = Box::new(mapped_stream);152Ok(read_dir)153}154Err(e) => {155if e.kind() == std::io::ErrorKind::NotFound {156Err(AssetReaderError::NotFound(full_path))157} else {158Err(e.into())159}160}161}162}163164async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {165let full_path = self.root_path.join(path);166let metadata = full_path167.metadata()168.map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;169Ok(metadata.file_type().is_dir())170}171}172173impl AssetWriter for FileAssetWriter {174async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {175let full_path = self.root_path.join(path);176if let Some(parent) = full_path.parent() {177async_fs::create_dir_all(parent).await?;178}179let file = File::create(&full_path).await?;180let writer: Box<Writer> = Box::new(file);181Ok(writer)182}183184async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {185let meta_path = get_meta_path(path);186let full_path = self.root_path.join(meta_path);187if let Some(parent) = full_path.parent() {188async_fs::create_dir_all(parent).await?;189}190let file = File::create(&full_path).await?;191let writer: Box<Writer> = Box::new(file);192Ok(writer)193}194195async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {196let full_path = self.root_path.join(path);197async_fs::remove_file(full_path).await?;198Ok(())199}200201async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {202let meta_path = get_meta_path(path);203let full_path = self.root_path.join(meta_path);204async_fs::remove_file(full_path).await?;205Ok(())206}207208async fn rename<'a>(209&'a self,210old_path: &'a Path,211new_path: &'a Path,212) -> Result<(), AssetWriterError> {213let full_old_path = self.root_path.join(old_path);214let full_new_path = self.root_path.join(new_path);215if let Some(parent) = full_new_path.parent() {216async_fs::create_dir_all(parent).await?;217}218async_fs::rename(full_old_path, full_new_path).await?;219Ok(())220}221222async fn rename_meta<'a>(223&'a self,224old_path: &'a Path,225new_path: &'a Path,226) -> Result<(), AssetWriterError> {227let old_meta_path = get_meta_path(old_path);228let new_meta_path = get_meta_path(new_path);229let full_old_path = self.root_path.join(old_meta_path);230let full_new_path = self.root_path.join(new_meta_path);231if let Some(parent) = full_new_path.parent() {232async_fs::create_dir_all(parent).await?;233}234async_fs::rename(full_old_path, full_new_path).await?;235Ok(())236}237238async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {239let full_path = self.root_path.join(path);240async_fs::create_dir_all(full_path).await?;241Ok(())242}243244async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {245let full_path = self.root_path.join(path);246async_fs::remove_dir_all(full_path).await?;247Ok(())248}249250async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {251let full_path = self.root_path.join(path);252async_fs::remove_dir(full_path).await?;253Ok(())254}255256async fn remove_assets_in_directory<'a>(257&'a self,258path: &'a Path,259) -> Result<(), AssetWriterError> {260let full_path = self.root_path.join(path);261async_fs::remove_dir_all(&full_path).await?;262async_fs::create_dir_all(&full_path).await?;263Ok(())264}265}266267268