use crate::api::{PickOptions, SaveOptions};
use crate::error::{AccessError, PickerError};
use crate::token::{
FileAccessToken, PermissionStatus, ReadSeek, TokenInner, WriteSeek,
};
pub(crate) async fn pick_open_single(
options: PickOptions,
) -> Result<Option<FileAccessToken>, PickerError> {
let tokens = pick_files(options, false).await?;
Ok(tokens.into_iter().next())
}
pub(crate) async fn pick_open_multi(
options: PickOptions,
) -> Result<Vec<FileAccessToken>, PickerError> {
pick_files(options, true).await
}
pub(crate) async fn pick_save(
options: SaveOptions,
) -> Result<Option<FileAccessToken>, PickerError> {
let name = options
.suggested_name
.unwrap_or_else(|| "download".into());
Ok(Some(FileAccessToken {
inner: TokenInner::Wasm {
data: Vec::new(),
name,
mime_type: options.mime_type,
},
}))
}
pub(crate) fn open_read(inner: &TokenInner) -> Result<Box<dyn ReadSeek>, AccessError> {
match inner {
TokenInner::Wasm { data, .. } => {
Ok(Box::new(std::io::Cursor::new(data.clone())))
}
_ => Err(AccessError::Platform {
message: "non-WASM token on WASM platform".into(),
}),
}
}
pub(crate) fn open_write(inner: &TokenInner) -> Result<Box<dyn WriteSeek>, AccessError> {
match inner {
TokenInner::Wasm { data, .. } => {
Ok(Box::new(std::io::Cursor::new(data.clone())))
}
_ => Err(AccessError::Platform {
message: "non-WASM token on WASM platform".into(),
}),
}
}
pub(crate) fn check_permission(inner: &TokenInner) -> PermissionStatus {
match inner {
TokenInner::Wasm { .. } => PermissionStatus::Valid,
_ => PermissionStatus::Unknown,
}
}
async fn pick_files(
options: PickOptions,
multiple: bool,
) -> Result<Vec<FileAccessToken>, PickerError> {
use wasm_bindgen::JsCast as _;
use wasm_bindgen_futures::JsFuture;
let window = web_sys::window().ok_or_else(|| PickerError::Platform {
message: "no global window object".into(),
})?;
let document = window.document().ok_or_else(|| PickerError::Platform {
message: "no document object".into(),
})?;
let input: web_sys::HtmlInputElement = document
.create_element("input")
.map_err(|e| PickerError::Platform {
message: format!("create_element failed: {e:?}"),
})?
.dyn_into()
.map_err(|_| PickerError::Platform {
message: "element is not HtmlInputElement".into(),
})?;
input.set_type("file");
if !options.mime_types.is_empty() {
input.set_accept(&options.mime_types.join(","));
}
if multiple {
input.set_multiple(true);
}
let promise = js_sys::Promise::new(&mut |resolve, _reject| {
let resolve_clone = resolve.clone();
let cb = wasm_bindgen::closure::Closure::once_into_js(move || {
let _ = resolve_clone.call0(&wasm_bindgen::JsValue::NULL);
});
let _ = input.add_event_listener_with_callback(
"change",
cb.as_ref().unchecked_ref(),
);
});
input.click();
JsFuture::from(promise).await.map_err(|e| {
PickerError::Platform {
message: format!("file input promise rejected: {e:?}"),
}
})?;
let file_list = match input.files() {
Some(fl) => fl,
None => return Ok(vec![]),
};
let mut tokens = Vec::new();
for i in 0..file_list.length() {
let file = match file_list.get(i) {
Some(f) => f,
None => continue,
};
let name = file.name();
let mime = file.type_();
let mime_type = if mime.is_empty() { None } else { Some(mime) };
let array_buf_promise = file.array_buffer();
let array_buf = JsFuture::from(array_buf_promise).await.map_err(|e| {
PickerError::Platform {
message: format!("arrayBuffer() failed: {e:?}"),
}
})?;
let uint8 = js_sys::Uint8Array::new(&array_buf);
let data = uint8.to_vec();
tokens.push(FileAccessToken {
inner: TokenInner::Wasm {
data,
name,
mime_type,
},
});
}
Ok(tokens)
}