1#![warn(missing_docs)]
5
6use anyhow::anyhow;
7use futures::prelude::*;
8use serde::de::DeserializeOwned;
9#[cfg(not(target_arch = "wasm32"))]
10use std::pin::Pin;
11#[cfg(target_arch = "wasm32")]
12use wasm_bindgen::prelude::*;
13#[cfg(target_arch = "wasm32")]
14use wasm_bindgen_futures::JsFuture;
15
16pub async fn load(path: impl AsRef<std::path::Path>) -> anyhow::Result<impl AsyncBufRead> {
20 let path = path.as_ref();
21 #[cfg(target_arch = "wasm32")]
22 {
23 let fetch: JsFuture = web_sys::window()
24 .expect("window unavailable")
25 .fetch_with_str(path.to_str().expect("path is not a valid str"))
26 .into();
27 let response: web_sys::Response = match fetch.await {
28 Ok(response) => response.unchecked_into(),
29 Err(e) => anyhow::bail!("{e:?}"),
30 };
31 let status = http::StatusCode::from_u16(response.status())?;
32 if !status.is_success() {
33 anyhow::bail!("Http status: {status}");
34 }
35 let body = response.body().expect("response without body?");
36 Ok(futures::io::BufReader::new(read_stream(body)))
37 }
38 #[cfg(target_os = "android")]
39 if batbox_android::file_mode() == batbox_android::FileMode::Assets {
40 let app = batbox_android::app();
42 let asset_manager = app.asset_manager();
43 let path = path.to_str().expect("Path expected to be a utf-8 str");
44 let path = path.strip_prefix("./").unwrap();
45 let path = std::ffi::CString::new(path).expect("Paths should not have null bytes");
46 let mut asset = asset_manager
47 .open(path.as_c_str())
48 .ok_or(anyhow!("Asset not found"))?;
49
50 struct ReadAsAsync<T>(Box<T>);
51
52 impl<T: std::io::Read> AsyncRead for ReadAsAsync<T> {
53 fn poll_read(
54 mut self: Pin<&mut Self>,
55 _: &mut std::task::Context<'_>,
56 buf: &mut [u8],
57 ) -> std::task::Poll<std::io::Result<usize>> {
58 std::task::Poll::Ready(std::io::Read::read(&mut self.0, buf))
59 }
60
61 fn poll_read_vectored(
62 mut self: Pin<&mut Self>,
63 _: &mut std::task::Context<'_>,
64 bufs: &mut [std::io::IoSliceMut<'_>],
65 ) -> std::task::Poll<std::io::Result<usize>> {
66 std::task::Poll::Ready(std::io::Read::read_vectored(&mut self.0, bufs))
67 }
68 }
69
70 impl<T: std::io::BufRead> AsyncBufRead for ReadAsAsync<T> {
71 fn poll_fill_buf(
72 mut self: Pin<&mut Self>,
73 _: &mut std::task::Context<'_>,
74 ) -> std::task::Poll<std::io::Result<&[u8]>> {
75 std::task::Poll::Ready(std::io::BufRead::fill_buf(&mut self.get_mut().0))
76 }
77
78 fn consume(mut self: Pin<&mut Self>, amt: usize) {
79 std::io::BufRead::consume(&mut self.0, amt)
80 }
81 }
82
83 return Ok(
84 Box::pin(ReadAsAsync(Box::new(std::io::BufReader::new(asset))))
85 as Pin<Box<dyn AsyncBufRead>>,
86 );
87 }
88 #[cfg(not(target_arch = "wasm32"))]
89 match path
90 .to_str()
91 .and_then(|path| url::Url::parse(path).ok())
92 .filter(|url| matches!(url.scheme(), "http" | "https"))
93 {
94 Some(url) => {
95 log::debug!("{:?}", url.scheme());
96 let request = reqwest::get(url);
97 let request = async_compat::Compat::new(request); let response = request.await?;
99 let status = response.status();
100 if !status.is_success() {
101 anyhow::bail!("Http status: {status}");
102 }
103 let reader = response
104 .bytes_stream()
105 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
106 .into_async_read();
107 let reader = futures::io::BufReader::new(reader);
108 Ok(Box::pin(reader) as Pin<Box<dyn AsyncBufRead>>)
109 }
110 None => {
111 let file = async_std::fs::File::open(path).await?;
112 let reader = futures::io::BufReader::new(file);
113 Ok(Box::pin(reader) as Pin<Box<dyn AsyncBufRead>>)
114 }
115 }
116}
117
118#[cfg(target_arch = "wasm32")]
120pub fn read_stream(stream: web_sys::ReadableStream) -> impl AsyncRead {
121 let stream = wasm_streams::ReadableStream::from_raw(stream.unchecked_into());
122
123 fn js_to_string(js_value: &JsValue) -> Option<String> {
124 js_value.as_string().or_else(|| {
125 js_sys::Object::try_from(js_value)
126 .map(|js_object| js_object.to_string().as_string().unwrap_throw())
127 })
128 }
129 fn js_to_io_error(js_value: JsValue) -> std::io::Error {
130 let message = js_to_string(&js_value).unwrap_or_else(|| "Unknown error".to_string());
131 std::io::Error::new(std::io::ErrorKind::Other, message)
132 }
133
134 stream
137 .into_stream()
138 .map(|result| match result {
139 Ok(chunk) => Ok(chunk.unchecked_into::<js_sys::Uint8Array>().to_vec()),
140 Err(e) => Err(js_to_io_error(e)),
141 })
142 .into_async_read()
143}
144
145pub async fn load_bytes(path: impl AsRef<std::path::Path>) -> anyhow::Result<Vec<u8>> {
147 let mut buf = Vec::new();
148 load(path).await?.read_to_end(&mut buf).await?;
149 Ok(buf)
150}
151
152pub async fn load_string(path: impl AsRef<std::path::Path>) -> anyhow::Result<String> {
154 let mut buf = String::new();
155 load(path).await?.read_to_string(&mut buf).await?;
156 Ok(buf)
157}
158
159pub async fn load_detect<T: DeserializeOwned>(
166 path: impl AsRef<std::path::Path>,
167) -> anyhow::Result<T> {
168 let path = path.as_ref();
169 let ext = path
170 .extension()
171 .ok_or(anyhow!("Expected to have extension"))?;
172 let ext = ext.to_str().ok_or(anyhow!("Extension is not valid str"))?;
173 let data = load_bytes(path).await?;
174 let value = match ext {
175 "json" => serde_json::from_reader(data.as_slice())?,
176 "toml" => toml::from_str(std::str::from_utf8(&data)?)?,
177 "ron" => ron::de::from_bytes(&data)?,
178 _ => anyhow::bail!("{ext:?} is unsupported"),
179 };
180 Ok(value)
181}
182
183pub async fn load_json<T: DeserializeOwned>(
185 path: impl AsRef<std::path::Path>,
186) -> anyhow::Result<T> {
187 let json: String = load_string(path).await?;
188 let value = serde_json::from_str(&json)?;
189 Ok(value)
190}