tauri_store/store/
save.rs

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