fyrox_core/
io.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use std::fmt::{Display, Formatter};
22use std::{io::Error, path::Path};
23
24#[derive(Debug)]
25pub enum FileError {
26    Io(std::io::Error),
27    Custom(String),
28}
29
30impl std::error::Error for FileError {}
31
32impl Display for FileError {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        match self {
35            FileError::Io(err) => {
36                write!(f, "Io error: {err}")
37            }
38            FileError::Custom(err) => {
39                write!(f, "{err}")
40            }
41        }
42    }
43}
44
45impl From<std::io::Error> for FileError {
46    fn from(e: Error) -> Self {
47        Self::Io(e)
48    }
49}
50
51#[cfg(target_os = "android")]
52pub static ANDROID_APP: once_cell::sync::OnceCell<android_activity::AndroidApp> =
53    once_cell::sync::OnceCell::new();
54
55#[cfg(target_arch = "wasm32")]
56impl From<wasm_bindgen::JsValue> for FileError {
57    fn from(value: wasm_bindgen::JsValue) -> Self {
58        let string = match js_sys::JSON::stringify(&value) {
59            Ok(string) => String::from(string),
60            Err(_) => format!("{:?}", value),
61        };
62        Self::Custom(string)
63    }
64}
65
66pub async fn load_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, FileError> {
67    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
68    {
69        use std::fs::File;
70        use std::io::Read;
71
72        let mut file = File::open(path)?;
73        let mut buffer = Vec::new();
74        file.read_to_end(&mut buffer)?;
75        Ok(buffer)
76    }
77
78    #[cfg(target_os = "android")]
79    {
80        let asset_manager = ANDROID_APP
81            .get()
82            .ok_or_else(|| FileError::Custom("ANDROID_APP is not set".to_string()))?
83            .asset_manager();
84        let mut opened_asset = asset_manager
85            .open(&std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap())
86            .ok_or_else(|| FileError::Custom(format!("File {:?} not found!", path.as_ref())))?;
87        let bytes = opened_asset.buffer()?;
88        Ok(bytes.to_vec())
89    }
90
91    #[cfg(target_arch = "wasm32")]
92    {
93        use js_sys::Uint8Array;
94        use wasm_bindgen::JsCast;
95        use wasm_bindgen_futures::JsFuture;
96
97        match web_sys::window() {
98            Some(window) => {
99                let resp_value =
100                    JsFuture::from(window.fetch_with_str(path.as_ref().to_str().unwrap())).await?;
101
102                let resp: web_sys::Response = resp_value.dyn_into().unwrap();
103                let data = JsFuture::from(resp.array_buffer().unwrap()).await?;
104                let bytes = Uint8Array::new(&data).to_vec();
105                Ok(bytes)
106            }
107            None => Err(FileError::Custom("Window not found!".to_owned())),
108        }
109    }
110}
111
112pub async fn exists<P: AsRef<Path>>(path: P) -> bool {
113    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
114    {
115        path.as_ref().exists()
116    }
117
118    #[cfg(target_os = "android")]
119    {
120        ANDROID_APP
121            .get()
122            .map(|v| {
123                v.asset_manager()
124                    .open(&std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap())
125                    .is_some()
126            })
127            .unwrap_or_default()
128    }
129
130    #[cfg(target_arch = "wasm32")]
131    {
132        use wasm_bindgen::JsCast;
133        use wasm_bindgen_futures::JsFuture;
134
135        match web_sys::window() {
136            Some(window) => {
137                if let Ok(resp_value) =
138                    JsFuture::from(window.fetch_with_str(path.as_ref().to_str().unwrap())).await
139                {
140                    let resp: web_sys::Response = resp_value.dyn_into().unwrap();
141
142                    resp.status() == 200
143                } else {
144                    false
145                }
146            }
147            None => false,
148        }
149    }
150}
151
152pub async fn is_dir<P: AsRef<Path>>(#[allow(unused)] path: P) -> bool {
153    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
154    {
155        path.as_ref().is_dir()
156    }
157
158    #[cfg(target_os = "android")]
159    {
160        ANDROID_APP
161            .get()
162            .map(|v| {
163                v.asset_manager()
164                    .open_dir(&std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap())
165                    .is_some()
166            })
167            .unwrap_or_default()
168    }
169
170    // TODO: Is directory checking possible on wasm?
171    #[cfg(target_arch = "wasm32")]
172    {
173        false
174    }
175}
176
177pub async fn is_file<P: AsRef<Path>>(path: P) -> bool {
178    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
179    {
180        path.as_ref().is_file()
181    }
182
183    // On android and wasm the default exists logic works for files
184    #[cfg(any(target_os = "android", target_arch = "wasm32"))]
185    {
186        exists(path).await
187    }
188}