1use wasm_bindgen::throw_str;
2
3pub mod callbacks {
4 use crate::{
5 blob::Blob,
6 file_reader::{FileReadError, ReadyState},
7 };
8 use gloo_events::EventListener;
9 use std::{cell::RefCell, rc::Rc};
10 use wasm_bindgen::{prelude::*, throw_str, JsCast, UnwrapThrowExt};
11
12 #[derive(Debug)]
15 pub struct FileReader {
16 reader: web_sys::FileReader,
17 _load_listener: EventListener,
18 _error_listener: EventListener,
19 }
20
21 impl std::ops::Drop for FileReader {
22 fn drop(&mut self) {
23 if !ReadyState::from(self.reader.ready_state()).is_done() {
24 self.reader.abort();
25 }
26 }
27 }
28
29 pub fn read_as_text<F>(blob: &Blob, callback: F) -> FileReader
34 where
35 F: FnOnce(Result<String, FileReadError>) + 'static,
36 {
37 read(
38 blob,
39 callback,
40 |value| value.as_string().unwrap_throw(),
41 |reader, blob| reader.read_as_text(blob).unwrap_throw(),
42 )
43 }
44
45 pub fn read_as_data_url<F>(blob: &Blob, callback: F) -> FileReader
51 where
52 F: FnOnce(Result<String, FileReadError>) + 'static,
53 {
54 read(
55 blob,
56 callback,
57 |value| value.as_string().unwrap_throw(),
58 |reader, blob| reader.read_as_data_url(blob).unwrap_throw(),
59 )
60 }
61
62 pub fn read_as_array_buffer<F>(blob: &Blob, callback: F) -> FileReader
67 where
68 F: FnOnce(Result<js_sys::ArrayBuffer, FileReadError>) + 'static,
69 {
70 read(
71 blob,
72 callback,
73 |value| value.dyn_into::<js_sys::ArrayBuffer>().unwrap_throw(),
74 |reader, blob| reader.read_as_array_buffer(blob).unwrap_throw(),
75 )
76 }
77
78 pub fn read_as_bytes<F>(blob: &Blob, callback: F) -> FileReader
83 where
84 F: FnOnce(Result<Vec<u8>, FileReadError>) + 'static,
85 {
86 read_as_array_buffer(blob, move |result| {
87 callback(result.map(|buffer| js_sys::Uint8Array::new(&buffer).to_vec()));
88 })
89 }
90
91 fn read<T, CF, EF, RF>(blob: &Blob, callback: CF, extract_fn: EF, read_fn: RF) -> FileReader
97 where
98 CF: FnOnce(Result<T, FileReadError>) + 'static,
99 EF: Fn(JsValue) -> T + 'static,
100 RF: Fn(&web_sys::FileReader, &web_sys::Blob) + 'static,
101 {
102 let load_callback: Rc<RefCell<Option<CF>>> = Rc::new(RefCell::new(Some(callback)));
106 let error_callback = load_callback.clone();
107
108 let reader = web_sys::FileReader::new().unwrap_throw();
109
110 let load_reader = reader.clone();
111 let error_reader = reader.clone();
112 let load_listener = EventListener::new(&reader, "load", move |_event| {
115 let result = extract_fn(load_reader.result().unwrap_throw());
116 let callback = load_callback.borrow_mut().take().unwrap_throw();
117 callback(Ok(result));
118 });
119
120 let error_listener = EventListener::new(&reader, "error", move |_event| {
121 let exception = error_reader.error().unwrap_throw();
122 let error = match exception.name().as_str() {
123 "NotFoundError" => FileReadError::NotFound(exception.message()),
124 "NotReadableError" => FileReadError::NotReadable(exception.message()),
125 "SecurityError" => FileReadError::Security(exception.message()),
126 _ => throw_str("unrecognised error type"),
129 };
130 let callback = error_callback.borrow_mut().take().unwrap_throw();
131 callback(Err(error));
132 });
133
134 read_fn(&reader, blob.as_ref());
135
136 FileReader {
137 reader,
138 _load_listener: load_listener,
139 _error_listener: error_listener,
140 }
141 }
142}
143
144#[cfg(feature = "futures")]
145pub mod futures {
146 use crate::{Blob, FileReadError};
147 use std::future::Future;
148 use wasm_bindgen::UnwrapThrowExt;
149
150 pub fn read_as_text(blob: &Blob) -> impl Future<Output = Result<String, FileReadError>> {
155 let (sender, receiver) = futures_channel::oneshot::channel();
156 let reader = super::callbacks::read_as_text(blob, |result| {
157 sender.send(result).unwrap_throw();
158 });
159
160 async move {
161 let output = receiver.await.unwrap_throw();
162 drop(reader);
163 output
164 }
165 }
166
167 pub fn read_as_data_url(blob: &Blob) -> impl Future<Output = Result<String, FileReadError>> {
172 let (sender, receiver) = futures_channel::oneshot::channel();
173 let reader = super::callbacks::read_as_data_url(blob, |result| {
174 sender.send(result).unwrap_throw();
175 });
176
177 async move {
178 let output = receiver.await.unwrap_throw();
179 drop(reader);
180 output
181 }
182 }
183
184 pub fn read_as_array_buffer(
190 blob: &Blob,
191 ) -> impl Future<Output = Result<js_sys::ArrayBuffer, FileReadError>> {
192 let (sender, receiver) = futures_channel::oneshot::channel();
193 let reader = super::callbacks::read_as_array_buffer(blob, |result| {
194 sender.send(result).unwrap_throw();
195 });
196
197 async move {
198 let output = receiver.await.unwrap_throw();
199 drop(reader);
200 output
201 }
202 }
203
204 pub fn read_as_bytes(blob: &Blob) -> impl Future<Output = Result<Vec<u8>, FileReadError>> {
210 let (sender, receiver) = futures_channel::oneshot::channel();
211 let reader = super::callbacks::read_as_bytes(blob, |result| {
212 sender.send(result).unwrap_throw();
213 });
214
215 async move {
216 let output = receiver.await.unwrap_throw();
217 drop(reader);
218 output
219 }
220 }
221}
222
223enum ReadyState {
224 Empty,
225 Loading,
226 Done,
227}
228
229impl ReadyState {
230 fn is_done(&self) -> bool {
231 matches!(self, ReadyState::Done)
232 }
233}
234
235impl From<u16> for ReadyState {
236 fn from(val: u16) -> Self {
237 match val {
238 0 => ReadyState::Empty,
239 1 => ReadyState::Loading,
240 2 => ReadyState::Done,
241 _ => throw_str("got invalid value for FileReader.readyState"),
242 }
243 }
244}
245
246#[derive(Debug)]
247pub enum FileReadError {
248 AbortedEarly,
249 NotFound(String),
250 NotReadable(String),
251 Security(String),
252}
253
254impl std::fmt::Display for FileReadError {
255 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
256 match self {
257 FileReadError::AbortedEarly => write!(f, "FileReader aborted early"),
258 FileReadError::NotFound(msg) => write!(f, "FileReader cannot find blob: {msg}"),
259 FileReadError::NotReadable(msg) => {
260 write!(f, "FileReader cannot read contents of blob: {msg}")
261 }
262 FileReadError::Security(msg) => {
263 write!(f, "FileReader encountered a security exception: {msg}")
264 }
265 }
266 }
267}
268
269impl std::error::Error for FileReadError {}