1#![recursion_limit = "512"]
9mod config;
33mod download;
34mod error;
35mod http;
36mod js;
37mod plan;
38mod state;
39mod upload;
40
41pub use config::{backoff_ms, ClientConfig};
42pub use error::LibfwError;
43pub use plan::FileEntry;
44
45use js_sys::Reflect;
46use wasm_bindgen::prelude::*;
47
48use crate::js::Callbacks;
49use crate::state::{TaskControl, TaskState};
50
51#[wasm_bindgen]
53pub struct LibfwClient {
54 config: ClientConfig,
55 callbacks: Callbacks,
56 control: TaskControl,
57}
58
59#[wasm_bindgen]
60impl LibfwClient {
61 #[wasm_bindgen(constructor)]
65 pub fn new(opts: JsValue) -> LibfwClient {
66 let config = ClientConfig::from_js(&opts);
67 LibfwClient {
68 config,
69 callbacks: Callbacks::new(),
70 control: TaskControl::new(),
71 }
72 }
73
74 pub fn set_callbacks(&self, callbacks: JsValue) {
76 self.callbacks.set(callbacks);
77 }
78
79 pub fn download_folder(&self, base_url: &str, token: &str, dir_path: &str) -> js_sys::Promise {
83 let base_url = base_url.to_string();
84 let token = token.to_string();
85 let dir_path = dir_path.to_string();
86 let config = self.config.clone();
87 let callbacks = self.callbacks.clone();
88 let control = self.control.clone();
89
90 wasm_bindgen_futures::future_to_promise(async move {
91 control.reset();
92 control.begin(TaskState::Downloading);
93 match download::download_folder(
94 &base_url,
95 &token,
96 &dir_path,
97 &callbacks,
98 &control,
99 &config,
100 )
101 .await
102 {
103 Ok(total) => {
104 control.complete();
105 Ok(JsValue::from_f64(total as f64))
106 }
107 Err(e) => {
108 control.fail();
109 Err(e.to_js())
110 }
111 }
112 })
113 }
114
115 pub fn upload(&self, base_url: &str, token: &str) -> js_sys::Promise {
119 let base_url = base_url.to_string();
120 let token = token.to_string();
121 let config = self.config.clone();
122 let callbacks = self.callbacks.clone();
123 let control = self.control.clone();
124
125 wasm_bindgen_futures::future_to_promise(async move {
126 control.reset();
127 control.begin(TaskState::Uploading);
128 match upload::upload(&base_url, &token, &callbacks, &control, &config).await {
129 Ok(total) => {
130 control.complete();
131 Ok(JsValue::from_f64(total as f64))
132 }
133 Err(e) => {
134 control.fail();
135 Err(e.to_js())
136 }
137 }
138 })
139 }
140
141 pub fn pause(&self) {
143 self.control.pause();
144 }
145
146 pub fn resume(&self) {
148 self.control.resume();
149 }
150
151 pub fn cancel(&self) {
153 self.control.cancel();
154 }
155
156 pub fn state(&self) -> String {
159 self.control.state().as_str().to_string()
160 }
161
162 pub fn progress(&self) -> f64 {
164 self.control.progress()
165 }
166
167 pub fn done_bytes(&self) -> f64 {
169 self.control.done_bytes() as f64
170 }
171
172 pub fn total_bytes(&self) -> f64 {
174 self.control.total_bytes() as f64
175 }
176
177 pub fn has_callbacks(&self) -> bool {
179 self.callbacks.is_set()
180 }
181}
182
183#[wasm_bindgen]
185pub fn js_option_string(obj: &JsValue, key: &str) -> Option<String> {
186 Reflect::get(obj, &JsValue::from_str(key))
187 .ok()
188 .and_then(|v| v.as_string())
189}
190
191#[cfg(test)]
192mod tests {
193 #[test]
194 #[cfg(target_arch = "wasm32")]
195 fn engine_default_state_is_idle() {
196 let engine = super::LibfwClient::new(wasm_bindgen::JsValue::NULL);
197 assert_eq!(engine.state(), "idle");
198 assert!(!engine.has_callbacks());
199 }
200
201 #[test]
202 #[cfg(target_arch = "wasm32")]
203 fn engine_options_parse() {
204 let opts = js_sys::Object::new();
205 js_sys::Reflect::set(&opts, &wasm_bindgen::JsValue::from_str("concurrency"), &wasm_bindgen::JsValue::from_f64(2.0))
206 .unwrap();
207 js_sys::Reflect::set(&opts, &wasm_bindgen::JsValue::from_str("compress"), &wasm_bindgen::JsValue::FALSE).unwrap();
208 let engine = super::LibfwClient::new(opts.into());
209 assert_eq!(engine.config.concurrency, 2);
210 assert!(!engine.config.compress);
211 }
212
213 #[test]
214 #[cfg(target_arch = "wasm32")]
215 fn state_transitions_via_public_api() {
216 use crate::state::TaskState;
217 let engine = super::LibfwClient::new(wasm_bindgen::JsValue::NULL);
218 engine.control.begin(TaskState::Downloading);
219 engine.pause();
220 assert_eq!(engine.state(), "paused");
221 engine.resume();
222 assert_eq!(engine.state(), "downloading");
223 engine.cancel();
224 assert_eq!(engine.state(), "failed");
225 }
226}