use js_sys::{Array, Promise, Uint8Array};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use super::archive::WasmArchive;
use super::writer::{WasmWriteOptions, WasmWriter};
#[wasm_bindgen(js_name = "openArchiveAsync")]
pub fn open_archive_async(data: Uint8Array, password: Option<String>) -> Promise {
future_to_promise(async move {
yield_to_event_loop().await;
let archive = if let Some(pwd) = password {
#[cfg(feature = "aes")]
{
WasmArchive::open_with_password(data, &pwd)?
}
#[cfg(not(feature = "aes"))]
{
let _ = pwd;
return Err(JsValue::from_str("AES encryption support not compiled"));
}
} else {
WasmArchive::new(data)?
};
Ok(JsValue::from(archive))
})
}
#[wasm_bindgen(js_name = "extractEntryAsync")]
pub fn extract_entry_async(archive: &mut WasmArchive, name: String) -> Promise {
let result = archive.extract_entry(&name);
future_to_promise(async move {
yield_to_event_loop().await;
match result {
Ok(data) => Ok(data.into()),
Err(e) => Err(e),
}
})
}
#[wasm_bindgen(js_name = "extractAllAsync")]
pub fn extract_all_async(archive: &mut WasmArchive) -> Promise {
let result = archive.extract_all();
future_to_promise(async move {
yield_to_event_loop().await;
result.map(JsValue::from)
})
}
#[wasm_bindgen(js_name = "testArchiveAsync")]
pub fn test_archive_async(archive: &mut WasmArchive) -> Promise {
let result = archive.test();
future_to_promise(async move {
yield_to_event_loop().await;
result.map(JsValue::from)
})
}
#[wasm_bindgen(js_name = "createArchiveAsync")]
pub fn create_archive_async(entries: Array, options: Option<WasmWriteOptions>) -> Promise {
future_to_promise(async move {
yield_to_event_loop().await;
let mut writer = WasmWriter::new(options);
for i in 0..entries.length() {
yield_to_event_loop().await;
let entry = entries.get(i);
let name = js_sys::Reflect::get(&entry, &"name".into())?
.as_string()
.ok_or_else(|| JsValue::from_str("Entry missing 'name' property"))?;
let data_val = js_sys::Reflect::get(&entry, &"data".into())?;
let data: Uint8Array = data_val
.dyn_into()
.map_err(|_| JsValue::from_str("Entry 'data' must be Uint8Array"))?;
writer.add_file(&name, data)?;
}
let result = writer.finish()?;
Ok(result.into())
})
}
#[wasm_bindgen(js_name = "processEntriesAsync")]
pub fn process_entries_async(archive: &WasmArchive, callback: &js_sys::Function) -> Promise {
let entries = match archive.get_entries() {
Ok(e) => e,
Err(e) => return Promise::reject(&e),
};
let callback = callback.clone();
future_to_promise(async move {
for i in 0..entries.length() {
let entry = entries.get(i);
let result = callback.call1(&JsValue::NULL, &entry)?;
if let Ok(promise) = result.dyn_into::<Promise>() {
wasm_bindgen_futures::JsFuture::from(promise).await?;
}
if i % 10 == 0 {
yield_to_event_loop().await;
}
}
Ok(JsValue::UNDEFINED)
})
}
#[wasm_bindgen(js_name = "batchExtractAsync")]
pub fn batch_extract_async(
archive: &mut WasmArchive,
entry_names: Array,
on_progress: Option<js_sys::Function>,
) -> Promise {
let total = entry_names.length();
let names: Vec<String> = (0..total)
.filter_map(|i| entry_names.get(i).as_string())
.collect();
let mut results: Vec<(String, Result<Uint8Array, JsValue>)> = Vec::new();
for name in &names {
let result = archive.extract_entry(name);
results.push((name.clone(), result));
}
future_to_promise(async move {
let map = js_sys::Map::new();
let total = results.len() as u32;
for (i, (name, result)) in results.into_iter().enumerate() {
yield_to_event_loop().await;
match result {
Ok(data) => {
map.set(&JsValue::from_str(&name), &data);
}
Err(e) => {
web_sys::console::warn_2(
&JsValue::from_str(&format!("Failed to extract {}: ", name)),
&e,
);
}
}
if let Some(ref callback) = on_progress {
let _ = callback.call2(
&JsValue::NULL,
&JsValue::from((i + 1) as u32),
&JsValue::from(total),
);
}
}
Ok(JsValue::from(map))
})
}
async fn yield_to_event_loop() {
let promise = Promise::new(&mut |resolve, _| {
let window = web_sys::window();
if let Some(win) = window {
let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, 0);
} else {
let _ = resolve.call0(&JsValue::NULL);
}
});
let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
}
#[wasm_bindgen(js_name = "supportsAsync")]
pub fn supports_async() -> bool {
js_sys::Reflect::get(&js_sys::global(), &"Promise".into())
.map(|v| !v.is_undefined())
.unwrap_or(false)
}
#[wasm_bindgen(js_name = "delay")]
pub fn delay(ms: u32) -> Promise {
Promise::new(&mut |resolve, _| {
let window = web_sys::window();
if let Some(win) = window {
let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, ms as i32);
} else {
let _ = resolve.call0(&JsValue::NULL);
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_supports_async() {
let _ = supports_async();
}
}