1use 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 #[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 #[cfg(any(target_os = "android", target_arch = "wasm32"))]
185 {
186 exists(path).await
187 }
188}