tauri_plugin_android_fs/api/
progress_notification_guard.rs1use sync_async::sync_async;
2use crate::*;
3use super::*;
4
5
6#[sync_async]
7pub struct ProgressNotificationGuard<R: tauri::Runtime> {
8 #[cfg(target_os = "android")]
9 inner: Inner<R>,
10
11 #[cfg(not(target_os = "android"))]
12 inner: std::marker::PhantomData<fn() -> R>,
13}
14
15#[cfg(target_os = "android")]
16#[sync_async(
17 use(if_sync) impls::SyncImpls as Impls;
18 use(if_async) impls::AsyncImpls as Impls;
19)]
20impl<R: tauri::Runtime> ProgressNotificationGuard<R> {
21
22 #[maybe_async]
23 pub(crate) fn start_new_notification(
24 icon: ProgressNotificationIcon,
25 title: Option<String>,
26 text: Option<String>,
27 sub_text: Option<String>,
28 progress: Option<u64>,
29 progress_max: Option<u64>,
30 handle: tauri::plugin::PluginHandle<R>,
31 ) -> Result<Self> {
32
33 let impls = Impls { handle: &handle };
34
35 let (n_progress, n_progress_max) = normalize_progress_and_max(progress, progress_max);
36 let id = impls
37 .start_progress_notification(
38 icon,
39 title.as_deref(),
40 text.as_deref(),
41 sub_text.as_deref(),
42 n_progress,
43 n_progress_max
44 )
45 .await?;
46
47 Ok(Self {
48 inner: Inner {
49 current_state: std::sync::Mutex::new(CurrentState {
50 title: title.clone(),
51 text,
52 sub_text,
53 progress,
54 progress_max
55 }),
56 drop_behavior: std::sync::Mutex::new(DropBehavior {
57 title: None,
58 text: None,
59 sub_text: None,
60 error: true,
61 }),
62 update_throttler: Throttler::with_delay(std::time::Duration::from_millis(1100)),
63 id,
64 icon,
65 handle,
66 is_finished: false,
67 }
68 })
69 }
70
71 #[always_sync]
72 fn impls(&self) -> Impls<'_, R> {
73 Impls { handle: &self.inner.handle }
74 }
75}
76
77#[sync_async(
78 use(if_async) api_async::{AndroidFs, Utils};
79 use(if_sync) api_sync::{AndroidFs, Utils};
80)]
81impl<R: tauri::Runtime> ProgressNotificationGuard<R> {
82
83 #[always_sync]
84 pub fn into_sync(self) -> SyncProgressNotificationGuard<R> {
85 SyncProgressNotificationGuard { inner: self.inner }
86 }
87
88 #[always_sync]
89 pub fn into_async(self) -> AsyncProgressNotificationGuard<R> {
90 AsyncProgressNotificationGuard { inner: self.inner }
91 }
92
93 #[always_sync]
94 pub fn title(&self) -> Option<String> {
95 #[cfg(not(target_os = "android"))] {
96 None
97 }
98 #[cfg(target_os = "android")] {
99 self.inner.lock_current_state().title.clone()
100 }
101 }
102
103 #[always_sync]
104 pub fn text(&self) -> Option<String> {
105 #[cfg(not(target_os = "android"))] {
106 None
107 }
108 #[cfg(target_os = "android")] {
109 self.inner.lock_current_state().text.clone()
110 }
111 }
112
113 #[always_sync]
114 pub fn sub_text(&self) -> Option<String> {
115 #[cfg(not(target_os = "android"))] {
116 None
117 }
118 #[cfg(target_os = "android")] {
119 self.inner.lock_current_state().sub_text.clone()
120 }
121 }
122
123 #[always_sync]
124 pub fn progress(&self) -> Option<u64> {
125 #[cfg(not(target_os = "android"))] {
126 None
127 }
128 #[cfg(target_os = "android")] {
129 self.inner.lock_current_state().progress
130 }
131 }
132
133 #[always_sync]
134 pub fn progress_max(&self) -> Option<u64> {
135 #[cfg(not(target_os = "android"))] {
136 None
137 }
138 #[cfg(target_os = "android")] {
139 self.inner.lock_current_state().progress_max
140 }
141 }
142
143 #[maybe_async]
144 pub fn update_progress_by(&self, addend: u64) -> Result<()> {
145 #[cfg(not(target_os = "android"))] {
146 Ok(())
147 }
148 #[cfg(target_os = "android")] {
149 {
150 let mut state = self.inner.lock_current_state();
151 state.progress = Some(state.progress.unwrap_or(0).saturating_add(addend));
152 }
153
154 self.update_notification().await?;
155 Ok(())
156 }
157 }
158
159 #[maybe_async]
160 pub fn update_progress(&self, progress: Option<u64>) -> Result<()> {
161 #[cfg(not(target_os = "android"))] {
162 Ok(())
163 }
164 #[cfg(target_os = "android")] {
165 {
166 let mut state = self.inner.lock_current_state();
167 state.progress = progress;
168 }
169
170 self.update_notification().await?;
171 Ok(())
172 }
173 }
174
175 #[maybe_async]
176 pub fn update_progress_max(&self, progress_max: Option<u64>) -> Result<()> {
177 #[cfg(not(target_os = "android"))] {
178 Ok(())
179 }
180 #[cfg(target_os = "android")] {
181 {
182 let mut state = self.inner.lock_current_state();
183 state.progress_max = progress_max;
184 }
185
186 self.update_notification().await?;
187 Ok(())
188 }
189 }
190
191 #[maybe_async]
192 pub fn update_title(&self, title: Option<&str>) -> Result<()> {
193 #[cfg(not(target_os = "android"))] {
194 Ok(())
195 }
196 #[cfg(target_os = "android")] {
197 {
198 let mut state = self.inner.lock_current_state();
199 state.title = title.map(|s| s.to_string());
200 }
201
202 self.update_notification().await?;
203 Ok(())
204 }
205 }
206
207 #[maybe_async]
208 pub fn update_text(&self, text: Option<&str>) -> Result<()> {
209 #[cfg(not(target_os = "android"))] {
210 Ok(())
211 }
212 #[cfg(target_os = "android")] {
213 {
214 let mut state = self.inner.lock_current_state();
215 state.text = text.map(|s| s.to_string());
216 }
217
218 self.update_notification().await?;
219 Ok(())
220 }
221 }
222
223 #[maybe_async]
224 pub fn update_sub_text(&self, sub_text: Option<&str>) -> Result<()> {
225 #[cfg(not(target_os = "android"))] {
226 Ok(())
227 }
228 #[cfg(target_os = "android")] {
229 {
230 let mut state = self.inner.lock_current_state();
231 state.sub_text = sub_text.map(|s| s.to_string());
232 };
233
234 self.update_notification().await?;
235 Ok(())
236 }
237 }
238
239 #[maybe_async]
240 pub fn update(
241 &self,
242 title: Option<&str>,
243 text: Option<&str>,
244 sub_text: Option<&str>,
245 progress: Option<u64>,
246 progress_max: Option<u64>,
247 ) -> Result<()> {
248
249 #[cfg(not(target_os = "android"))] {
250 Ok(())
251 }
252 #[cfg(target_os = "android")] {
253 {
254 let mut state = self.inner.lock_current_state();
255 *state = CurrentState {
256 title: title.map(|s| s.to_string()),
257 text: text.map(|s| s.to_string()),
258 sub_text: sub_text.map(|s| s.to_string()),
259 progress,
260 progress_max
261 };
262 }
263
264 self.update_notification().await?;
265 Ok(())
266 }
267 }
268
269 #[always_sync]
270 pub fn set_drop_behavior_to_complete(
271 &self,
272 title: Option<&str>,
273 text: Option<&str>,
274 sub_text: Option<&str>,
275 ) {
276
277 #[cfg(target_os = "android")] {
278 let title = title.map(|s| s.to_string());
279 let text = text.map(|s| s.to_string());
280 let sub_text = sub_text.map(|s| s.to_string());
281
282 *self.inner.lock_drop_behavior() = DropBehavior {
283 title: Some(Box::new(move || title)),
284 text: Some(Box::new(move || text)),
285 sub_text: Some(Box::new(move || sub_text)),
286 error: false,
287 };
288 }
289 }
290
291 #[always_sync]
292 pub fn set_drop_behavior_to_complete_with(
293 &self,
294 title: impl 'static + Send + FnOnce() -> Option<String>,
295 text: impl 'static + Send + FnOnce() -> Option<String>,
296 sub_text: impl 'static + Send + FnOnce() -> Option<String>,
297 ) {
298
299 #[cfg(target_os = "android")] {
300 *self.inner.lock_drop_behavior() = DropBehavior {
301 title: Some(Box::new(title)),
302 text: Some(Box::new(text)),
303 sub_text: Some(Box::new(sub_text)),
304 error: false,
305 };
306 }
307 }
308
309 #[always_sync]
310 pub fn set_drop_behavior_to_fail(
311 &self,
312 title: Option<&str>,
313 text: Option<&str>,
314 sub_text: Option<&str>
315 ) {
316
317 #[cfg(target_os = "android")] {
318 let title = title.map(|s| s.to_string());
319 let text = text.map(|s| s.to_string());
320 let sub_text = sub_text.map(|s| s.to_string());
321
322 *self.inner.lock_drop_behavior() = DropBehavior {
323 title: Some(Box::new(move || title)),
324 text: Some(Box::new(move || text)),
325 sub_text: Some(Box::new(move || sub_text)),
326 error: true,
327 };
328 }
329 }
330
331 #[always_sync]
332 pub fn set_drop_behavior_to_fail_with(
333 &self,
334 title: impl 'static + Send + FnOnce() -> Option<String>,
335 text: impl 'static + Send + FnOnce() -> Option<String>,
336 sub_text: impl 'static + Send + FnOnce() -> Option<String>,
337 ) {
338
339 #[cfg(target_os = "android")] {
340 *self.inner.lock_drop_behavior() = DropBehavior {
341 title: Some(Box::new(title)),
342 text: Some(Box::new(text)),
343 sub_text: Some(Box::new(sub_text)),
344 error: true,
345 };
346 }
347 }
348
349 #[maybe_async]
350 pub fn complete(
351 self,
352 title: Option<&str>,
353 text: Option<&str>,
354 sub_text: Option<&str>,
355 ) -> Result<()> {
356
357 #[cfg(not(target_os = "android"))] {
358 Ok(())
359 }
360 #[cfg(target_os = "android")] {
361 self.finish_notification(title, text, sub_text, false).await
362 }
363 }
364
365 #[maybe_async]
366 pub fn fail(
367 self,
368 title: Option<&str>,
369 text: Option<&str>,
370 sub_text: Option<&str>,
371 ) -> Result<()> {
372
373 #[cfg(not(target_os = "android"))] {
374 Ok(())
375 }
376 #[cfg(target_os = "android")] {
377 self.finish_notification(title, text, sub_text, true).await
378 }
379 }
380
381
382 #[cfg(target_os = "android")]
383 #[maybe_async]
384 fn finish_notification(
385 mut self,
386 title: Option<&str>,
387 text: Option<&str>,
388 sub_text: Option<&str>,
389 error: bool
390 ) -> Result<()> {
391
392 self.impls().finish_progress_notification(
393 self.inner.id,
394 self.inner.icon,
395 title,
396 text,
397 sub_text,
398 error
399 ).await?;
400
401 self.inner.is_finished = true;
402 Ok(())
403 }
404
405 #[cfg(target_os = "android")]
406 #[maybe_async]
407 fn update_notification(&self) -> Result<()> {
408 if self.inner.update_throttler.check_and_mark() {
409 let state = self.inner.lock_current_state().clone();
410 let (progress, progress_max) = normalize_progress_and_max(state.progress, state.progress_max);
411
412 self.impls().update_progress_notification(
413 self.inner.id,
414 self.inner.icon,
415 state.title.as_deref(),
416 state.text.as_deref(),
417 state.sub_text.as_deref(),
418 progress,
419 progress_max,
420 ).await?;
421 }
422
423 Ok(())
424 }
425}
426
427
428#[cfg(target_os = "android")]
429struct Inner<R: tauri::Runtime> {
430 id: i32,
431 icon: ProgressNotificationIcon,
432 is_finished: bool,
433 drop_behavior: std::sync::Mutex<DropBehavior>,
434 current_state: std::sync::Mutex<CurrentState>,
435 update_throttler: Throttler,
436 handle: tauri::plugin::PluginHandle<R>,
437}
438
439#[cfg(target_os = "android")]
440struct DropBehavior {
441 title: Option<Box<dyn Send + 'static + FnOnce() -> Option<String>>>,
442 text: Option<Box<dyn Send + 'static + FnOnce() -> Option<String>>>,
443 sub_text: Option<Box<dyn Send + 'static + FnOnce() -> Option<String>>>,
444 error: bool,
445}
446
447#[cfg(target_os = "android")]
448#[derive(Clone)]
449struct CurrentState {
450 title: Option<String>,
451 text: Option<String>,
452 sub_text: Option<String>,
453 progress: Option<u64>,
454 progress_max: Option<u64>,
455}
456
457#[cfg(target_os = "android")]
458impl<R: tauri::Runtime> Inner<R> {
459
460 fn lock_drop_behavior<'a>(&'a self) -> std::sync::MutexGuard<'a, DropBehavior> {
461 self.drop_behavior.lock().unwrap_or_else(|e| e.into_inner())
462 }
463
464 fn lock_current_state<'a>(&'a self) -> std::sync::MutexGuard<'a, CurrentState> {
465 self.current_state.lock().unwrap_or_else(|e| e.into_inner())
466 }
467}
468
469#[cfg(target_os = "android")]
470impl<R: tauri::Runtime> Drop for Inner<R> {
471
472 fn drop(&mut self) {
473 if self.is_finished {
474 return
475 }
476
477 let handle = self.handle.clone();
478 let id = self.id;
479 let icon = self.icon;
480 let (error, title, text, sub_text) = {
481 let mut d = self.lock_drop_behavior();
482 (d.error, d.title.take(), d.text.take(), d.sub_text.take())
483 };
484
485 tauri::async_runtime::spawn(async move {
486 let impls = impls::AsyncImpls { handle: &handle };
487 impls.finish_progress_notification(
488 id,
489 icon,
490 title.and_then(|f| f()).as_deref(),
491 text.and_then(|f| f()).as_deref(),
492 sub_text.and_then(|f| f()).as_deref(),
493 error
494 ).await.ok();
495 });
496 }
497}
498
499#[cfg(target_os = "android")]
500struct Throttler {
501 next: std::sync::Mutex<std::time::Instant>,
502 interval: std::time::Duration,
503}
504
505#[cfg(target_os = "android")]
506impl Throttler {
507
508 pub fn with_delay(interval: std::time::Duration) -> Self {
509 Self {
510 next: std::sync::Mutex::new(std::time::Instant::now() + interval),
511 interval,
512 }
513 }
514
515 pub fn check_and_mark(&self) -> bool {
516 let mut next = self.next.lock().unwrap_or_else(|e| e.into_inner());
517 let now = std::time::Instant::now();
518
519 if now < *next {
520 return false
521 }
522
523 *next = now + self.interval;
524 true
525 }
526}
527
528#[cfg(target_os = "android")]
529fn normalize_progress_and_max(
530 progress: Option<u64>,
531 progress_max: Option<u64>,
532) -> (Option<i32>, Option<i32>) {
533
534 let Some((progress, progress_max)) = Option::zip(progress, progress_max) else {
535 return (None, None)
536 };
537
538 const PROGRESS_MAX: i32 = 100_000;
539
540 if progress_max == 0 {
541 return (Some(0), Some(0));
542 }
543
544 let ratio = progress as f64 / progress_max as f64;
545 let scaled_progress = (ratio * PROGRESS_MAX as f64) as i32;
546
547 (Some(i32::min(scaled_progress, PROGRESS_MAX)), Some(PROGRESS_MAX))
548}