Skip to main content

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
51impl From<&str> for FileError {
52    fn from(value: &str) -> Self {
53        Self::Custom(value.to_string())
54    }
55}
56
57impl From<String> for FileError {
58    fn from(value: String) -> Self {
59        Self::Custom(value)
60    }
61}
62
63#[cfg(target_os = "android")]
64pub static ANDROID_APP: once_cell::sync::OnceCell<android_activity::AndroidApp> =
65    once_cell::sync::OnceCell::new();
66
67#[cfg(target_arch = "wasm32")]
68impl From<wasm_bindgen::JsValue> for FileError {
69    fn from(value: wasm_bindgen::JsValue) -> Self {
70        let string = match js_sys::JSON::stringify(&value) {
71            Ok(string) => String::from(string),
72            Err(_) => format!("{:?}", value),
73        };
74        Self::Custom(string)
75    }
76}
77
78pub async fn load_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, FileError> {
79    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
80    {
81        use std::fs::File;
82        use std::io::Read;
83
84        let mut file = File::open(path)?;
85        let mut buffer = Vec::new();
86        file.read_to_end(&mut buffer)?;
87        Ok(buffer)
88    }
89
90    #[cfg(target_os = "android")]
91    {
92        let asset_manager = ANDROID_APP
93            .get()
94            .ok_or_else(|| FileError::Custom("ANDROID_APP is not set".to_string()))?
95            .asset_manager();
96        let mut opened_asset = asset_manager
97            .open(&std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap())
98            .ok_or_else(|| FileError::Custom(format!("File {:?} not found!", path.as_ref())))?;
99        let bytes = opened_asset.buffer()?;
100        Ok(bytes.to_vec())
101    }
102
103    #[cfg(target_arch = "wasm32")]
104    {
105        use js_sys::Uint8Array;
106        use wasm_bindgen::JsCast;
107        use wasm_bindgen_futures::JsFuture;
108
109        match web_sys::window() {
110            Some(window) => {
111                let resp_value =
112                    JsFuture::from(window.fetch_with_str(path.as_ref().to_str().unwrap())).await?;
113
114                let resp: web_sys::Response = resp_value.dyn_into().unwrap();
115                let data = JsFuture::from(resp.array_buffer().unwrap()).await?;
116                let bytes = Uint8Array::new(&data).to_vec();
117                Ok(bytes)
118            }
119            None => Err(FileError::Custom("Window not found!".to_owned())),
120        }
121    }
122}
123
124pub async fn exists<P: AsRef<Path>>(path: P) -> bool {
125    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
126    {
127        path.as_ref().exists()
128    }
129
130    #[cfg(target_os = "android")]
131    {
132        ANDROID_APP
133            .get()
134            .map(|v| {
135                v.asset_manager()
136                    .open(&std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap())
137                    .is_some()
138            })
139            .unwrap_or_default()
140    }
141
142    #[cfg(target_arch = "wasm32")]
143    {
144        use wasm_bindgen::JsCast;
145        use wasm_bindgen_futures::JsFuture;
146
147        match web_sys::window() {
148            Some(window) => {
149                if let Ok(resp_value) =
150                    JsFuture::from(window.fetch_with_str(path.as_ref().to_str().unwrap())).await
151                {
152                    let resp: web_sys::Response = resp_value.dyn_into().unwrap();
153
154                    resp.status() == 200
155                } else {
156                    false
157                }
158            }
159            None => false,
160        }
161    }
162}
163
164pub async fn is_dir<P: AsRef<Path>>(#[allow(unused)] path: P) -> bool {
165    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
166    {
167        path.as_ref().is_dir()
168    }
169
170    #[cfg(target_os = "android")]
171    {
172        ANDROID_APP
173            .get()
174            .map(|v| {
175                v.asset_manager()
176                    .open_dir(&std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap())
177                    .is_some()
178            })
179            .unwrap_or_default()
180    }
181
182    // TODO: Is directory checking possible on wasm?
183    #[cfg(target_arch = "wasm32")]
184    {
185        false
186    }
187}
188
189pub async fn is_file<P: AsRef<Path>>(path: P) -> bool {
190    #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
191    {
192        path.as_ref().is_file()
193    }
194
195    // On android and wasm the default exists logic works for files
196    #[cfg(any(target_os = "android", target_arch = "wasm32"))]
197    {
198        exists(path).await
199    }
200}