1mod autosave;
2mod builder;
3mod path;
4
5use crate::error::Result;
6use crate::event::{emit, STORE_UNLOAD_EVENT};
7use crate::meta::Meta;
8use crate::store::{SaveStrategy, Store, StoreId, StoreResource, StoreState, WatcherId};
9use autosave::Autosave;
10use dashmap::DashMap;
11use serde::de::DeserializeOwned;
12use serde_json::Value as Json;
13use std::collections::HashSet;
14use std::fmt;
15use std::path::PathBuf;
16use std::sync::{Arc, Mutex, OnceLock};
17use std::time::Duration;
18use tauri::{AppHandle, Resource, ResourceId, Runtime};
19
20pub use builder::StoreCollectionBuilder;
21
22#[cfg(feature = "unstable-migration")]
23use crate::migration::Migrator;
24
25pub(crate) static RESOURCE_ID: OnceLock<ResourceId> = OnceLock::new();
26
27pub type OnLoadFn<R> = dyn Fn(&Store<R>) -> Result<()> + Send + Sync;
29
30pub struct StoreCollection<R: Runtime> {
33 pub(crate) app: AppHandle<R>,
34 pub(crate) name: Box<str>,
35 pub(crate) path: Mutex<PathBuf>,
36 pub(crate) stores: DashMap<StoreId, ResourceId>,
37 pub(crate) on_load: Option<Box<OnLoadFn<R>>>,
38 pub(crate) autosave: Mutex<Autosave>,
39 pub(crate) default_save_strategy: SaveStrategy,
40 pub(crate) save_denylist: Option<HashSet<StoreId>>,
41 pub(crate) sync_denylist: Option<HashSet<StoreId>>,
42 pub(crate) pretty: bool,
43
44 #[cfg(feature = "unstable-migration")]
45 pub(crate) migrator: Mutex<Migrator>,
46}
47
48impl<R: Runtime> StoreCollection<R> {
49 pub fn builder() -> StoreCollectionBuilder<R> {
51 StoreCollectionBuilder::new()
52 }
53
54 pub(crate) fn get_resource(&self, id: impl AsRef<str>) -> Result<Arc<StoreResource<R>>> {
55 let id = StoreId::from(id.as_ref());
56 let rid = match self.rid(&id) {
57 Some(rid) => rid,
58 None => self.load_store(id)?,
59 };
60
61 StoreResource::get(&self.app, rid)
62 }
63
64 fn load_store(&self, id: StoreId) -> Result<ResourceId> {
65 let (rid, resource) = Store::load(&self.app, &id)?;
66 if let Some(on_load) = &self.on_load {
67 resource.locked(|store| on_load(store))?;
68 }
69
70 self.stores.insert(id, rid);
71 Ok(rid)
72 }
73
74 fn rid(&self, store_id: &StoreId) -> Option<ResourceId> {
76 self.stores.get(store_id).map(|it| *it.value())
77 }
78
79 fn rids(&self) -> Vec<ResourceId> {
81 self.stores.iter().map(|it| *it.value()).collect()
82 }
83
84 pub fn ids(&self) -> Vec<StoreId> {
86 self
87 .stores
88 .iter()
89 .map(|it| it.key().clone())
90 .collect()
91 }
92
93 pub fn with_store<F, T>(&self, store_id: impl AsRef<str>, f: F) -> Result<T>
95 where
96 F: FnOnce(&mut Store<R>) -> T,
97 {
98 Ok(self.get_resource(store_id)?.locked(f))
99 }
100
101 pub fn state(&self, store_id: impl AsRef<str>) -> Result<StoreState> {
103 self
104 .get_resource(store_id)?
105 .locked(|store| Ok(store.state().clone()))
106 }
107
108 pub fn try_state<T>(&self, store_id: impl AsRef<str>) -> Result<T>
110 where
111 T: DeserializeOwned,
112 {
113 self
114 .get_resource(store_id)?
115 .locked(|store| store.try_state())
116 }
117
118 pub fn try_state_or<T>(&self, store_id: impl AsRef<str>, default: T) -> Result<T>
122 where
123 T: DeserializeOwned,
124 {
125 self
126 .get_resource(store_id)?
127 .locked(move |store| Ok(store.try_state_or(default)))
128 }
129
130 pub fn try_state_or_default<T>(&self, store_id: impl AsRef<str>) -> Result<T>
134 where
135 T: DeserializeOwned + Default,
136 {
137 self
138 .get_resource(store_id)?
139 .locked(|store| Ok(store.try_state_or_default()))
140 }
141
142 pub fn try_state_or_else<T>(&self, store_id: impl AsRef<str>, f: impl FnOnce() -> T) -> Result<T>
146 where
147 T: DeserializeOwned,
148 {
149 self
150 .get_resource(store_id)?
151 .locked(|store| Ok(store.try_state_or_else(f)))
152 }
153
154 pub fn get(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Option<Json> {
156 self
157 .get_resource(store_id)
158 .ok()?
159 .locked(|store| store.get(key).cloned())
160 }
161
162 pub fn try_get<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Result<T>
164 where
165 T: DeserializeOwned,
166 {
167 self
168 .get_resource(store_id)?
169 .locked(|store| store.try_get(key))
170 }
171
172 pub fn try_get_or<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>, default: T) -> T
176 where
177 T: DeserializeOwned,
178 {
179 self.try_get(store_id, key).unwrap_or(default)
180 }
181
182 pub fn try_get_or_default<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
186 where
187 T: Default + DeserializeOwned,
188 {
189 self.try_get(store_id, key).unwrap_or_default()
190 }
191
192 pub fn try_get_or_else<T>(
196 &self,
197 store_id: impl AsRef<str>,
198 key: impl AsRef<str>,
199 f: impl FnOnce() -> T,
200 ) -> T
201 where
202 T: DeserializeOwned,
203 {
204 self
205 .try_get(store_id, key)
206 .unwrap_or_else(|_| f())
207 }
208
209 pub fn set<K, V>(&self, store_id: impl AsRef<str>, key: K, value: V) -> Result<()>
211 where
212 K: AsRef<str>,
213 V: Into<Json>,
214 {
215 self
216 .get_resource(store_id)?
217 .locked(|store| store.set(key, value))
218 }
219
220 pub fn patch<S>(&self, store_id: impl AsRef<str>, state: S) -> Result<()>
222 where
223 S: Into<StoreState>,
224 {
225 self
226 .get_resource(store_id)?
227 .locked(|store| store.patch(state))
228 }
229
230 pub fn save(&self, store_id: impl AsRef<str>) -> Result<()> {
232 self
233 .get_resource(store_id)?
234 .locked(|store| store.save())
235 }
236
237 pub fn save_now(&self, store_id: impl AsRef<str>) -> Result<()> {
239 self.get_resource(store_id)?.locked(|store| {
240 store.abort_pending_save();
241 store.save_now()
242 })
243 }
244
245 pub fn save_some(&self, ids: &[impl AsRef<str>]) -> Result<()> {
247 ids.iter().try_for_each(|id| self.save(id))
248 }
249
250 pub fn save_some_now(&self, ids: &[impl AsRef<str>]) -> Result<()> {
252 ids.iter().try_for_each(|id| self.save_now(id))
253 }
254
255 pub fn save_all(&self) -> Result<()> {
257 self
261 .rids()
262 .into_iter()
263 .try_for_each(|rid| StoreResource::save(&self.app, rid))
264 }
265
266 pub fn save_all_now(&self) -> Result<()> {
268 self
269 .rids()
270 .into_iter()
271 .try_for_each(|rid| StoreResource::save_now(&self.app, rid))
272 }
273
274 #[inline]
277 pub fn default_save_strategy(&self) -> SaveStrategy {
278 self.default_save_strategy
279 }
280
281 pub fn set_autosave(&self, duration: Duration) {
283 if let Ok(mut autosave) = self.autosave.lock() {
284 autosave.set_duration(duration);
285 autosave.start(&self.app);
286 }
287 }
288
289 pub fn clear_autosave(&self) {
291 if let Ok(mut autosave) = self.autosave.lock() {
292 autosave.stop();
293 }
294 }
295
296 pub fn watch<F>(&self, store_id: impl AsRef<str>, f: F) -> Result<WatcherId>
298 where
299 F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
300 {
301 self
302 .get_resource(store_id)?
303 .locked(|store| Ok(store.watch(f)))
304 }
305
306 pub fn unwatch(
308 &self,
309 store_id: impl AsRef<str>,
310 watcher_id: impl Into<WatcherId>,
311 ) -> Result<bool> {
312 self
313 .get_resource(store_id)?
314 .locked(|store| Ok(store.unwatch(watcher_id)))
315 }
316
317 #[doc(hidden)]
319 pub fn unload_store(&self, id: &StoreId) -> Result<()> {
320 if let Some((_, rid)) = self.stores.remove(id) {
321 let resource = StoreResource::take(&self.app, rid)?;
325 resource.locked(|store| store.save_now())?;
326
327 emit(&self.app, STORE_UNLOAD_EVENT, id, None::<&str>)?;
328 }
329
330 Ok(())
331 }
332
333 #[doc(hidden)]
335 pub fn on_exit(&self) -> Result<()> {
336 self.clear_autosave();
337
338 for rid in self.rids() {
339 if let Ok(resource) = StoreResource::take(&self.app, rid) {
340 resource.locked(|store| {
341 store.abort_pending_save();
342 if store.save_on_exit {
343 let _ = store.save_now();
344 }
345 });
346 }
347 }
348
349 Meta::write(self)?;
350
351 Ok(())
352 }
353}
354
355impl<R: Runtime> Resource for StoreCollection<R> {}
356
357impl<R: Runtime> fmt::Debug for StoreCollection<R> {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 f.debug_struct("StoreCollection")
360 .field("default_save_strategy", &self.default_save_strategy)
361 .field("pretty", &self.pretty)
362 .finish_non_exhaustive()
363 }
364}