tauri_store/store/
save.rs1use 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;
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
30impl<R: Runtime> Drop for SaveHandle<R> {
31 fn drop(&mut self) {
32 self.abort();
33 }
34}
35
36pub(super) fn debounce<R, C>(id: StoreId, duration: Duration) -> SaveHandle<R>
37where
38 R: Runtime,
39 C: CollectionMarker,
40{
41 SaveHandle(Box::new(Debounce::new(duration, save_handle::<R, C>(id))))
42}
43
44pub(super) fn throttle<R, C>(id: StoreId, duration: Duration) -> SaveHandle<R>
45where
46 R: Runtime,
47 C: CollectionMarker,
48{
49 SaveHandle(Box::new(Throttle::new(duration, save_handle::<R, C>(id))))
50}
51
52fn save_handle<R, C>(id: StoreId) -> SaveHandleFn<R>
53where
54 R: Runtime,
55 C: CollectionMarker,
56{
57 Box::new(move |app| {
58 let id = id.clone();
59 Box::pin(async move {
60 let task = spawn_blocking(move || {
61 app
62 .store_collection_with_marker::<C>()
63 .get_resource(&id)?
64 .locked(|store| store.save_now())
65 });
66
67 let _ = task.await;
68 })
69 })
70}
71
72#[non_exhaustive]
77#[derive(Clone, Copy, Debug, Default)]
78pub enum SaveStrategy {
79 #[default]
80 Immediate,
81 Debounce(Duration),
82 Throttle(Duration),
83}
84
85impl SaveStrategy {
86 const IMMEDIATE: &'static str = "immediate";
87 const DEBOUNCE: &'static str = "debounce";
88 const THROTTLE: &'static str = "throttle";
89
90 #[inline]
92 pub const fn debounce_millis(millis: u64) -> Self {
93 Self::Debounce(Duration::from_millis(millis))
94 }
95
96 #[inline]
98 pub const fn debounce_secs(secs: u64) -> Self {
99 Self::Debounce(Duration::from_secs(secs))
100 }
101
102 #[inline]
104 pub const fn throttle_millis(millis: u64) -> Self {
105 Self::Throttle(Duration::from_millis(millis))
106 }
107
108 #[inline]
110 pub const fn throttle_secs(secs: u64) -> Self {
111 Self::Throttle(Duration::from_secs(secs))
112 }
113
114 #[inline]
116 pub const fn is_debounce(&self) -> bool {
117 matches!(self, Self::Debounce(_))
118 }
119
120 #[inline]
122 pub const fn is_throttle(&self) -> bool {
123 matches!(self, Self::Throttle(_))
124 }
125}
126
127impl fmt::Display for SaveStrategy {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 match self {
130 Self::Immediate => write!(f, "{}", Self::IMMEDIATE),
131 Self::Debounce(_) => write!(f, "{}", Self::DEBOUNCE),
132 Self::Throttle(_) => write!(f, "{}", Self::THROTTLE),
133 }
134 }
135}
136
137impl Serialize for SaveStrategy {
138 fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
139 where
140 S: Serializer,
141 {
142 let interval = match self {
143 Self::Immediate => 0,
144 Self::Debounce(duration) | Self::Throttle(duration) => duration.as_millis(),
145 };
146
147 let mut tuple = serializer.serialize_tuple(2)?;
148 tuple.serialize_element(&self.to_string())?;
149 tuple.serialize_element(&interval.to_string())?;
150 tuple.end()
151 }
152}
153
154impl<'de> Deserialize<'de> for SaveStrategy {
155 fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
156 where
157 D: Deserializer<'de>,
158 {
159 let err = || {
160 use serde::de::Error;
161 D::Error::custom("invalid save strategy")
162 };
163
164 let value = Value::deserialize(deserializer)?;
165 if let Value::Array(mut array) = value {
166 if array.len() != 2 {
167 return Err(err());
168 }
169
170 let strategy = array
171 .remove(0)
172 .as_str()
173 .map(ToOwned::to_owned)
174 .ok_or_else(err)?;
175
176 let duration = array
177 .remove(0)
178 .as_str()
179 .map(str::parse)
180 .ok_or_else(err)?
181 .map(Duration::from_millis)
182 .map_err(|_| err())?;
183
184 if duration.is_zero() {
185 return Ok(Self::Immediate);
186 }
187
188 match strategy.as_str() {
189 Self::DEBOUNCE => Ok(Self::Debounce(duration)),
190 Self::THROTTLE => Ok(Self::Throttle(duration)),
191 Self::IMMEDIATE => Ok(Self::Immediate),
192 _ => Err(err()),
193 }
194 } else {
195 Err(err())
196 }
197 }
198}