tauri_store/store/
save.rs1use 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#[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 #[inline]
73 pub const fn debounce_millis(millis: u64) -> Self {
74 Self::Debounce(Duration::from_millis(millis))
75 }
76
77 #[inline]
79 pub const fn debounce_secs(secs: u64) -> Self {
80 Self::Debounce(Duration::from_secs(secs))
81 }
82
83 #[inline]
85 pub const fn throttle_millis(millis: u64) -> Self {
86 Self::Throttle(Duration::from_millis(millis))
87 }
88
89 #[inline]
91 pub const fn throttle_secs(secs: u64) -> Self {
92 Self::Throttle(Duration::from_secs(secs))
93 }
94
95 #[inline]
97 pub const fn is_debounce(&self) -> bool {
98 matches!(self, Self::Debounce(_))
99 }
100
101 #[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}