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>(duration: Duration, id: StoreId) -> SaveHandle<R> {
30  SaveHandle(Box::new(Debounce::new(duration, save_handle(id))))
31}
32
33pub(super) fn throttle<R: Runtime>(duration: Duration, id: StoreId) -> 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  pub const fn debounce_millis(millis: u64) -> Self {
73    Self::Debounce(Duration::from_millis(millis))
74  }
75
76  /// Returns [`Debounce`](SaveStrategy::Debounce) with the given duration, in seconds.
77  pub const fn debounce_secs(secs: u64) -> Self {
78    Self::Debounce(Duration::from_secs(secs))
79  }
80
81  /// Returns [`Throttle`](SaveStrategy::Throttle) with the given duration, in milliseconds.
82  pub const fn throttle_millis(millis: u64) -> Self {
83    Self::Throttle(Duration::from_millis(millis))
84  }
85
86  /// Returns [`Throttle`](SaveStrategy::Throttle) with the given duration, in seconds.
87  pub const fn throttle_secs(secs: u64) -> Self {
88    Self::Throttle(Duration::from_secs(secs))
89  }
90
91  /// Whether the strategy is [`Debounce`](SaveStrategy::Debounce).
92  pub const fn is_debounce(&self) -> bool {
93    matches!(self, Self::Debounce(_))
94  }
95
96  /// Whether the strategy is [`Throttle`](SaveStrategy::Throttle).
97  pub const fn is_throttle(&self) -> bool {
98    matches!(self, Self::Throttle(_))
99  }
100}
101
102impl fmt::Display for SaveStrategy {
103  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104    match self {
105      Self::Immediate => write!(f, "{}", Self::IMMEDIATE),
106      Self::Debounce(_) => write!(f, "{}", Self::DEBOUNCE),
107      Self::Throttle(_) => write!(f, "{}", Self::THROTTLE),
108    }
109  }
110}
111
112impl Serialize for SaveStrategy {
113  fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
114  where
115    S: Serializer,
116  {
117    let interval = match self {
118      Self::Immediate => 0,
119      Self::Debounce(duration) | Self::Throttle(duration) => duration.as_millis(),
120    };
121
122    let mut tuple = serializer.serialize_tuple(2)?;
123    tuple.serialize_element(&self.to_string())?;
124    tuple.serialize_element(&interval.to_string())?;
125    tuple.end()
126  }
127}
128
129impl<'de> Deserialize<'de> for SaveStrategy {
130  fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
131  where
132    D: Deserializer<'de>,
133  {
134    let err = || {
135      use serde::de::Error;
136      D::Error::custom("invalid save strategy")
137    };
138
139    let value = Json::deserialize(deserializer)?;
140    if let Json::Array(mut array) = value {
141      if array.len() != 2 {
142        return Err(err());
143      }
144
145      let strategy = array
146        .remove(0)
147        .as_str()
148        .map(ToOwned::to_owned)
149        .ok_or_else(err)?;
150
151      let duration = array
152        .remove(0)
153        .as_str()
154        .map(str::parse)
155        .ok_or_else(err)?
156        .map(Duration::from_millis)
157        .map_err(|_| err())?;
158
159      if duration.is_zero() {
160        return Ok(Self::Immediate);
161      }
162
163      match strategy.as_str() {
164        Self::DEBOUNCE => Ok(Self::Debounce(duration)),
165        Self::THROTTLE => Ok(Self::Throttle(duration)),
166        Self::IMMEDIATE => Ok(Self::Immediate),
167        _ => Err(err()),
168      }
169    } else {
170      Err(err())
171    }
172  }
173}