#![recursion_limit = "512"]
mod config;
mod download;
mod error;
mod http;
mod js;
mod plan;
mod state;
mod upload;
pub use config::{backoff_ms, ClientConfig};
pub use error::LibfwError;
pub use plan::FileEntry;
use js_sys::Reflect;
use wasm_bindgen::prelude::*;
use crate::js::Callbacks;
use crate::state::{TaskControl, TaskState};
#[wasm_bindgen]
pub struct LibfwClient {
config: ClientConfig,
callbacks: Callbacks,
control: TaskControl,
}
#[wasm_bindgen]
impl LibfwClient {
#[wasm_bindgen(constructor)]
pub fn new(opts: JsValue) -> LibfwClient {
let config = ClientConfig::from_js(&opts);
LibfwClient {
config,
callbacks: Callbacks::new(),
control: TaskControl::new(),
}
}
pub fn set_callbacks(&self, callbacks: JsValue) {
self.callbacks.set(callbacks);
}
pub fn download_folder(&self, base_url: &str, token: &str, dir_path: &str) -> js_sys::Promise {
let base_url = base_url.to_string();
let token = token.to_string();
let dir_path = dir_path.to_string();
let config = self.config.clone();
let callbacks = self.callbacks.clone();
let control = self.control.clone();
wasm_bindgen_futures::future_to_promise(async move {
control.reset();
control.begin(TaskState::Downloading);
match download::download_folder(
&base_url,
&token,
&dir_path,
&callbacks,
&control,
&config,
)
.await
{
Ok(total) => {
control.complete();
Ok(JsValue::from_f64(total as f64))
}
Err(e) => {
control.fail();
Err(e.to_js())
}
}
})
}
pub fn upload(&self, base_url: &str, token: &str) -> js_sys::Promise {
let base_url = base_url.to_string();
let token = token.to_string();
let config = self.config.clone();
let callbacks = self.callbacks.clone();
let control = self.control.clone();
wasm_bindgen_futures::future_to_promise(async move {
control.reset();
control.begin(TaskState::Uploading);
match upload::upload(&base_url, &token, &callbacks, &control, &config).await {
Ok(total) => {
control.complete();
Ok(JsValue::from_f64(total as f64))
}
Err(e) => {
control.fail();
Err(e.to_js())
}
}
})
}
pub fn pause(&self) {
self.control.pause();
}
pub fn resume(&self) {
self.control.resume();
}
pub fn cancel(&self) {
self.control.cancel();
}
pub fn state(&self) -> String {
self.control.state().as_str().to_string()
}
pub fn progress(&self) -> f64 {
self.control.progress()
}
pub fn done_bytes(&self) -> f64 {
self.control.done_bytes() as f64
}
pub fn total_bytes(&self) -> f64 {
self.control.total_bytes() as f64
}
pub fn has_callbacks(&self) -> bool {
self.callbacks.is_set()
}
}
#[wasm_bindgen]
pub fn js_option_string(obj: &JsValue, key: &str) -> Option<String> {
Reflect::get(obj, &JsValue::from_str(key))
.ok()
.and_then(|v| v.as_string())
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(target_arch = "wasm32")]
fn engine_default_state_is_idle() {
let engine = super::LibfwClient::new(wasm_bindgen::JsValue::NULL);
assert_eq!(engine.state(), "idle");
assert!(!engine.has_callbacks());
}
#[test]
#[cfg(target_arch = "wasm32")]
fn engine_options_parse() {
let opts = js_sys::Object::new();
js_sys::Reflect::set(&opts, &wasm_bindgen::JsValue::from_str("concurrency"), &wasm_bindgen::JsValue::from_f64(2.0))
.unwrap();
js_sys::Reflect::set(&opts, &wasm_bindgen::JsValue::from_str("compress"), &wasm_bindgen::JsValue::FALSE).unwrap();
let engine = super::LibfwClient::new(opts.into());
assert_eq!(engine.config.concurrency, 2);
assert!(!engine.config.compress);
}
#[test]
#[cfg(target_arch = "wasm32")]
fn state_transitions_via_public_api() {
use crate::state::TaskState;
let engine = super::LibfwClient::new(wasm_bindgen::JsValue::NULL);
engine.control.begin(TaskState::Downloading);
engine.pause();
assert_eq!(engine.state(), "paused");
engine.resume();
assert_eq!(engine.state(), "downloading");
engine.cancel();
assert_eq!(engine.state(), "failed");
}
}