tauri_store/store/
save.rs

1use super::StoreId;
2use crate::manager::ManagerExt;
3use futures::future::BoxFuture;
4use serde::ser::SerializeTuple;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_json::Value as Json;
7use std::fmt;
8use std::result::Result as StdResult;
9use std::time::Duration;
10use tauri::async_runtime::spawn_blocking;
11use tauri::{AppHandle, Runtime};
12use tauri_store_utils::{Debounce, RemoteCallable, Throttle};
13
14type RemoteSaveHandle<R> = Box<dyn RemoteCallable<AppHandle<R>> + Send + Sync>;
15type SaveHandleFn<R> = Box<dyn Fn(AppHandle<R>) -> BoxFuture<'static, ()> + Send + Sync + 'static>;
16
17pub(super) struct SaveHandle<R: Runtime>(RemoteSaveHandle<R>);
18
19impl<R: Runtime> SaveHandle<R> {
20  pub fn call(&self, app: &AppHandle<R>) {
21    self.0.call(app);
22  }
23
24  pub fn abort(&self) {
25    self.0.abort();
26  }
27}
28
29pub(super) fn debounce<R: Runtime>(id: StoreId, duration: Duration) -> SaveHandle<R> {
30  SaveHandle(Box::new(Debounce::new(duration, save_handle(id))))
31}
32
33pub(super) fn throttle<R: Runtime>(id: StoreId, duration: Duration) -> SaveHandle<R> {
34  SaveHandle(Box::new(Throttle::new(duration, save_handle(id))))
35}
36
37fn save_handle<R: Runtime>(id: StoreId) -> SaveHandleFn<R> {
38  Box::new(move |app| {
39    let id = id.clone();
40    Box::pin(async move {
41      let task = spawn_blocking(move || {
42        app
43          .store_collection()
44          .get_resource(&id)?
45          .locked(|store| store.save_now())
46      });
47
48      let _ = task.await;
49    })
50  })
51}
52
53/// The strategy to use when saving a store.
54///
55/// For a detailed explanation of the differences between debouncing and throttling,
56/// take a look at [this article](https://kettanaito.com/blog/debounce-vs-throttle).
57#[non_exhaustive]
58#[derive(Clone, Copy, Debug, Default)]
59pub enum SaveStrategy {
60  #[default]
61  Immediate,
62  Debounce(Duration),
63  Throttle(Duration),
64}
65
66impl SaveStrategy {
67  const IMMEDIATE: &'static str = "immediate";
68  const DEBOUNCE: &'static str = "debounce";
69  const THROTTLE: &'static str = "throttle";
70
71  /// Returns [`Debounce`](SaveStrategy::Debounce) with the given duration, in milliseconds.
72  #[inline]
73  pub const fn debounce_millis(millis: u64) -> Self {
74    Self::Debounce(Duration::from_millis(millis))
75  }
76
77  /// Returns [`Debounce`](SaveStrategy::Debounce) with the given duration, in seconds.
78  #[inline]
79  pub const fn debounce_secs(secs: u64) -> Self {
80    Self::Debounce(Duration::from_secs(secs))
81  }
82
83  /// Returns [`Throttle`](SaveStrategy::Throttle) with the given duration, in milliseconds.
84  #[inline]
85  pub const fn throttle_millis(millis: u64) -> Self {
86    Self::Throttle(Duration::from_millis(millis))
87  }
88
89  /// Returns [`Throttle`](SaveStrategy::Throttle) with the given duration, in seconds.
90  #[inline]
91  pub const fn throttle_secs(secs: u64) -> Self {
92    Self::Throttle(Duration::from_secs(secs))
93  }
94
95  /// Whether the strategy is [`Debounce`](SaveStrategy::Debounce).
96  #[inline]
97  pub const fn is_debounce(&self) -> bool {
98    matches!(self, Self::Debounce(_))
99  }
100
101  /// Whether the strategy is [`Throttle`](SaveStrategy::Throttle).
102  #[inline]
103  pub const fn is_throttle(&self) -> bool {
104    matches!(self, Self::Throttle(_))
105  }
106}
107
108impl fmt::Display for SaveStrategy {
109  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110    match self {
111      Self::Immediate => write!(f, "{}", Self::IMMEDIATE),
112      Self::Debounce(_) => write!(f, "{}", Self::DEBOUNCE),
113      Self::Throttle(_) => write!(f, "{}", Self::THROTTLE),
114    }
115  }
116}
117
118impl Serialize for SaveStrategy {
119  fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
120  where
121    S: Serializer,
122  {
123    let interval = match self {
124      Self::Immediate => 0,
125      Self::Debounce(duration) | Self::Throttle(duration) => duration.as_millis(),
126    };
127
128    let mut tuple = serializer.serialize_tuple(2)?;
129    tuple.serialize_element(&self.to_string())?;
130    tuple.serialize_element(&interval.to_string())?;
131    tuple.end()
132  }
133}
134
135impl<'de> Deserialize<'de> for SaveStrategy {
136  fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
137  where
138    D: Deserializer<'de>,
139  {
140    let err = || {
141      use serde::de::Error;
142      D::Error::custom("invalid save strategy")
143    };
144
145    let value = Json::deserialize(deserializer)?;
146    if let Json::Array(mut array) = value {
147      if array.len() != 2 {
148        return Err(err());
149      }
150
151      let strategy = array
152        .remove(0)
153        .as_str()
154        .map(ToOwned::to_owned)
155        .ok_or_else(err)?;
156
157      let duration = array
158        .remove(0)
159        .as_str()
160        .map(str::parse)
161        .ok_or_else(err)?
162        .map(Duration::from_millis)
163        .map_err(|_| err())?;
164
165      if duration.is_zero() {
166        return Ok(Self::Immediate);
167      }
168
169      match strategy.as_str() {
170        Self::DEBOUNCE => Ok(Self::Debounce(duration)),
171        Self::THROTTLE => Ok(Self::Throttle(duration)),
172        Self::IMMEDIATE => Ok(Self::Immediate),
173        _ => Err(err()),
174      }
175    } else {
176      Err(err())
177    }
178  }
179}