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 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#[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 #[inline]
86 pub const fn debounce_millis(millis: u64) -> Self {
87 Self::Debounce(Duration::from_millis(millis))
88 }
89
90 #[inline]
92 pub const fn debounce_secs(secs: u64) -> Self {
93 Self::Debounce(Duration::from_secs(secs))
94 }
95
96 #[inline]
98 pub const fn throttle_millis(millis: u64) -> Self {
99 Self::Throttle(Duration::from_millis(millis))
100 }
101
102 #[inline]
104 pub const fn throttle_secs(secs: u64) -> Self {
105 Self::Throttle(Duration::from_secs(secs))
106 }
107
108 #[inline]
110 pub const fn is_debounce(&self) -> bool {
111 matches!(self, Self::Debounce(_))
112 }
113
114 #[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}