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