1#![recursion_limit = "512"]
9mod config;
33mod download;
34mod error;
35mod js;
36mod plan;
37mod state;
38mod upload;
39mod ws;
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)]
66 pub fn new(opts: JsValue) -> LibfwClient {
67 let config = ClientConfig::from_js(&opts);
68 LibfwClient {
69 config,
70 callbacks: Callbacks::new(),
71 control: TaskControl::new(),
72 }
73 }
74
75 pub fn set_callbacks(&self, callbacks: JsValue) {
77 self.callbacks.set(callbacks);
78 }
79
80 pub fn download_folder(&self, base_url: &str, token: &str, dir_path: &str) -> js_sys::Promise {
84 let base_url = base_url.to_string();
85 let token = token.to_string();
86 let dir_path = dir_path.to_string();
87 let config = self.config.clone();
88 let callbacks = self.callbacks.clone();
89 let control = self.control.clone();
90
91 wasm_bindgen_futures::future_to_promise(async move {
92 control.reset();
93 control.begin(TaskState::Downloading);
94 match download::download_folder(
95 &base_url,
96 &token,
97 &dir_path,
98 &callbacks,
99 &control,
100 &config,
101 )
102 .await
103 {
104 Ok(total) => {
105 control.complete();
106 Ok(JsValue::from_f64(total as f64))
107 }
108 Err(e) => {
109 control.fail();
110 Err(e.to_js())
111 }
112 }
113 })
114 }
115
116 pub fn download_file(&self, base_url: &str, token: &str, file_path: &str) -> js_sys::Promise {
120 let base_url = base_url.to_string();
121 let token = token.to_string();
122 let file_path = file_path.to_string();
123 let config = self.config.clone();
124 let callbacks = self.callbacks.clone();
125 let control = self.control.clone();
126
127 wasm_bindgen_futures::future_to_promise(async move {
128 control.reset();
129 control.begin(TaskState::Downloading);
130 match download::download_single(
131 &base_url,
132 &token,
133 &file_path,
134 &callbacks,
135 &control,
136 &config,
137 )
138 .await
139 {
140 Ok(total) => {
141 control.complete();
142 Ok(JsValue::from_f64(total as f64))
143 }
144 Err(e) => {
145 control.fail();
146 Err(e.to_js())
147 }
148 }
149 })
150 }
151
152 pub fn upload(&self, base_url: &str, token: &str) -> js_sys::Promise {
156 let base_url = base_url.to_string();
157 let token = token.to_string();
158 let config = self.config.clone();
159 let callbacks = self.callbacks.clone();
160 let control = self.control.clone();
161
162 wasm_bindgen_futures::future_to_promise(async move {
163 control.reset();
164 control.begin(TaskState::Uploading);
165 match upload::upload(&base_url, &token, &callbacks, &control, &config).await {
166 Ok(total) => {
167 control.complete();
168 Ok(JsValue::from_f64(total as f64))
169 }
170 Err(e) => {
171 control.fail();
172 Err(e.to_js())
173 }
174 }
175 })
176 }
177
178 pub fn pause(&self) {
180 self.control.pause();
181 }
182
183 pub fn resume(&self) {
185 self.control.resume();
186 }
187
188 pub fn cancel(&self) {
190 self.control.cancel();
191 }
192
193 pub fn state(&self) -> String {
196 self.control.state().as_str().to_string()
197 }
198
199 pub fn progress(&self) -> f64 {
201 self.control.progress()
202 }
203
204 pub fn done_bytes(&self) -> f64 {
206 self.control.done_bytes() as f64
207 }
208
209 pub fn total_bytes(&self) -> f64 {
211 self.control.total_bytes() as f64
212 }
213
214 pub fn has_callbacks(&self) -> bool {
216 self.callbacks.is_set()
217 }
218}
219
220#[wasm_bindgen]
222pub fn js_option_string(obj: &JsValue, key: &str) -> Option<String> {
223 Reflect::get(obj, &JsValue::from_str(key))
224 .ok()
225 .and_then(|v| v.as_string())
226}
227
228#[cfg(test)]
229mod tests {
230 #[test]
231 #[cfg(target_arch = "wasm32")]
232 fn engine_default_state_is_idle() {
233 let engine = super::LibfwClient::new(wasm_bindgen::JsValue::NULL);
234 assert_eq!(engine.state(), "idle");
235 assert!(!engine.has_callbacks());
236 }
237
238 #[test]
239 #[cfg(target_arch = "wasm32")]
240 fn engine_options_parse() {
241 let opts = js_sys::Object::new();
242 js_sys::Reflect::set(&opts, &wasm_bindgen::JsValue::from_str("concurrency"), &wasm_bindgen::JsValue::from_f64(2.0))
243 .unwrap();
244 js_sys::Reflect::set(&opts, &wasm_bindgen::JsValue::from_str("compress"), &wasm_bindgen::JsValue::FALSE).unwrap();
245 let engine = super::LibfwClient::new(opts.into());
246 assert_eq!(engine.config.concurrency, 2);
247 assert!(!engine.config.compress);
248 }
249
250 #[test]
251 #[cfg(target_arch = "wasm32")]
252 fn state_transitions_via_public_api() {
253 use crate::state::TaskState;
254 let engine = super::LibfwClient::new(wasm_bindgen::JsValue::NULL);
255 engine.control.begin(TaskState::Downloading);
256 engine.pause();
257 assert_eq!(engine.state(), "paused");
258 engine.resume();
259 assert_eq!(engine.state(), "downloading");
260 engine.cancel();
261 assert_eq!(engine.state(), "failed");
262 }
263}