1mod autosave;
2mod builder;
3mod marker;
4mod path;
5
6use crate::error::Result;
7use crate::event::{emit, STORE_UNLOAD_EVENT};
8use crate::meta::Meta;
9use crate::store::{SaveStrategy, Store, StoreId, StoreResource, StoreState, WatcherId};
10use autosave::Autosave;
11use dashmap::{DashMap, DashSet};
12use serde::de::DeserializeOwned;
13use serde_json::Value as Json;
14use std::fmt;
15use std::marker::PhantomData;
16use std::path::PathBuf;
17use std::sync::{Arc, Mutex};
18use std::time::Duration;
19use tauri::{AppHandle, Resource, ResourceId, Runtime};
20
21pub use builder::StoreCollectionBuilder;
22pub use marker::{CollectionMarker, DefaultMarker};
23
24#[cfg(feature = "unstable-migration")]
25use crate::migration::Migrator;
26
27pub type OnLoadFn<R, C> = dyn Fn(&Store<R, C>) -> Result<()> + Send + Sync;
29
30pub struct StoreCollection<R, C>
33where
34 R: Runtime,
35 C: CollectionMarker,
36{
37 pub(crate) app: AppHandle<R>,
38 pub(crate) name: Box<str>,
39 pub(crate) path: Mutex<PathBuf>,
40 pub(crate) stores: DashMap<StoreId, ResourceId>,
41 pub(crate) on_load: Option<Box<OnLoadFn<R, C>>>,
42 pub(crate) autosave: Mutex<Autosave>,
43 pub(crate) default_save_strategy: SaveStrategy,
44 pub(crate) save_denylist: DashSet<StoreId>,
45 pub(crate) sync_denylist: DashSet<StoreId>,
46 pub(crate) pretty: bool,
47 phantom: PhantomData<C>,
48
49 #[cfg(feature = "unstable-migration")]
50 pub(crate) migrator: Mutex<Migrator>,
51}
52
53impl<R, C> StoreCollection<R, C>
54where
55 R: Runtime,
56 C: CollectionMarker,
57{
58 pub fn builder() -> StoreCollectionBuilder<R, C> {
60 StoreCollectionBuilder::<R, C>::new()
61 }
62
63 pub(crate) fn get_resource(&self, id: impl AsRef<str>) -> Result<Arc<StoreResource<R, C>>> {
64 let id = StoreId::from(id.as_ref());
65 let rid = match self.rid(&id) {
66 Some(rid) => rid,
67 None => self.load_store(id)?,
68 };
69
70 StoreResource::get(&self.app, rid)
71 }
72
73 fn load_store(&self, id: StoreId) -> Result<ResourceId> {
74 let (rid, resource) = Store::load(&self.app, &id)?;
75 if let Some(on_load) = &self.on_load {
76 resource.locked(|store| on_load(store))?;
77 }
78
79 self.stores.insert(id, rid);
80 Ok(rid)
81 }
82
83 fn rid(&self, store_id: &StoreId) -> Option<ResourceId> {
85 self.stores.get(store_id).map(|it| *it.value())
86 }
87
88 fn rids(&self) -> Vec<ResourceId> {
90 self.stores.iter().map(|it| *it.value()).collect()
91 }
92
93 pub fn ids(&self) -> Vec<StoreId> {
95 self
96 .stores
97 .iter()
98 .map(|it| it.key().clone())
99 .collect()
100 }
101
102 pub fn with_store<F, T>(&self, store_id: impl AsRef<str>, f: F) -> Result<T>
104 where
105 F: FnOnce(&mut Store<R, C>) -> T,
106 {
107 Ok(self.get_resource(store_id)?.locked(f))
108 }
109
110 pub fn state(&self, store_id: impl AsRef<str>) -> Result<StoreState> {
112 self
113 .get_resource(store_id)?
114 .locked(|store| Ok(store.state().clone()))
115 }
116
117 pub fn try_state<T>(&self, store_id: impl AsRef<str>) -> Result<T>
119 where
120 T: DeserializeOwned,
121 {
122 self
123 .get_resource(store_id)?
124 .locked(|store| store.try_state())
125 }
126
127 pub fn try_state_or<T>(&self, store_id: impl AsRef<str>, default: T) -> Result<T>
131 where
132 T: DeserializeOwned,
133 {
134 self
135 .get_resource(store_id)?
136 .locked(move |store| Ok(store.try_state_or(default)))
137 }
138
139 pub fn try_state_or_default<T>(&self, store_id: impl AsRef<str>) -> Result<T>
143 where
144 T: DeserializeOwned + Default,
145 {
146 self
147 .get_resource(store_id)?
148 .locked(|store| Ok(store.try_state_or_default()))
149 }
150
151 pub fn try_state_or_else<T>(&self, store_id: impl AsRef<str>, f: impl FnOnce() -> T) -> Result<T>
155 where
156 T: DeserializeOwned,
157 {
158 self
159 .get_resource(store_id)?
160 .locked(|store| Ok(store.try_state_or_else(f)))
161 }
162
163 pub fn get(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Option<Json> {
165 self
166 .get_resource(store_id)
167 .ok()?
168 .locked(|store| store.get(key).cloned())
169 }
170
171 pub fn try_get<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Result<T>
173 where
174 T: DeserializeOwned,
175 {
176 self
177 .get_resource(store_id)?
178 .locked(|store| store.try_get(key))
179 }
180
181 pub fn try_get_or<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>, default: T) -> T
185 where
186 T: DeserializeOwned,
187 {
188 self.try_get(store_id, key).unwrap_or(default)
189 }
190
191 pub fn try_get_or_default<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
195 where
196 T: Default + DeserializeOwned,
197 {
198 self.try_get(store_id, key).unwrap_or_default()
199 }
200
201 pub fn try_get_or_else<T>(
205 &self,
206 store_id: impl AsRef<str>,
207 key: impl AsRef<str>,
208 f: impl FnOnce() -> T,
209 ) -> T
210 where
211 T: DeserializeOwned,
212 {
213 self
214 .try_get(store_id, key)
215 .unwrap_or_else(|_| f())
216 }
217
218 pub fn set<K, V>(&self, store_id: impl AsRef<str>, key: K, value: V) -> Result<()>
220 where
221 K: AsRef<str>,
222 V: Into<Json>,
223 {
224 self
225 .get_resource(store_id)?
226 .locked(|store| store.set(key, value))
227 }
228
229 pub fn patch<S>(&self, store_id: impl AsRef<str>, state: S) -> Result<()>
231 where
232 S: Into<StoreState>,
233 {
234 self
235 .get_resource(store_id)?
236 .locked(|store| store.patch(state))
237 }
238
239 pub fn save(&self, store_id: impl AsRef<str>) -> Result<()> {
241 self
242 .get_resource(store_id)?
243 .locked(|store| store.save())
244 }
245
246 pub fn save_now(&self, store_id: impl AsRef<str>) -> Result<()> {
248 self.get_resource(store_id)?.locked(|store| {
249 store.abort_pending_save();
250 store.save_now()
251 })
252 }
253
254 pub fn save_some(&self, ids: &[impl AsRef<str>]) -> Result<()> {
256 ids.iter().try_for_each(|id| self.save(id))
257 }
258
259 pub fn save_some_now(&self, ids: &[impl AsRef<str>]) -> Result<()> {
261 ids.iter().try_for_each(|id| self.save_now(id))
262 }
263
264 pub fn save_all(&self) -> Result<()> {
266 self
270 .rids()
271 .into_iter()
272 .try_for_each(|rid| StoreResource::<R, C>::save(&self.app, rid))
273 }
274
275 pub fn save_all_now(&self) -> Result<()> {
277 self
278 .rids()
279 .into_iter()
280 .try_for_each(|rid| StoreResource::<R, C>::save_now(&self.app, rid))
281 }
282
283 #[inline]
286 pub fn default_save_strategy(&self) -> SaveStrategy {
287 self.default_save_strategy
288 }
289
290 pub fn set_autosave(&self, duration: Duration) {
292 if let Ok(mut autosave) = self.autosave.lock() {
293 autosave.set_duration(duration);
294 autosave.start::<R, C>(&self.app);
295 }
296 }
297
298 pub fn clear_autosave(&self) {
300 if let Ok(mut autosave) = self.autosave.lock() {
301 autosave.stop();
302 }
303 }
304
305 pub fn watch<F>(&self, store_id: impl AsRef<str>, f: F) -> Result<WatcherId>
307 where
308 F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
309 {
310 self
311 .get_resource(store_id)?
312 .locked(|store| Ok(store.watch(f)))
313 }
314
315 pub fn unwatch(
317 &self,
318 store_id: impl AsRef<str>,
319 watcher_id: impl Into<WatcherId>,
320 ) -> Result<bool> {
321 self
322 .get_resource(store_id)?
323 .locked(|store| Ok(store.unwatch(watcher_id)))
324 }
325
326 pub fn allow_save(&self, id: impl AsRef<str>) {
328 let id = StoreId::from(id.as_ref());
329 self.save_denylist.remove(&id);
330 }
331
332 pub fn deny_save(&self, id: impl AsRef<str>) {
334 let id = StoreId::from(id.as_ref());
335 self.save_denylist.insert(id);
336 }
337
338 pub fn allow_sync(&self, id: impl AsRef<str>) {
340 let id = StoreId::from(id.as_ref());
341 self.sync_denylist.remove(&id);
342 }
343
344 pub fn deny_sync(&self, id: impl AsRef<str>) {
346 let id = StoreId::from(id.as_ref());
347 self.sync_denylist.insert(id);
348 }
349
350 #[doc(hidden)]
352 pub fn unload_store(&self, id: &StoreId) -> Result<()> {
353 if let Some((_, rid)) = self.stores.remove(id) {
354 let resource = StoreResource::<R, C>::take(&self.app, rid)?;
358 resource.locked(|store| store.save_now())?;
359
360 emit(&self.app, STORE_UNLOAD_EVENT, id, None::<&str>)?;
361 }
362
363 Ok(())
364 }
365
366 #[doc(hidden)]
368 pub fn on_exit(&self) -> Result<()> {
369 self.clear_autosave();
370
371 for rid in self.rids() {
372 if let Ok(resource) = StoreResource::<R, C>::take(&self.app, rid) {
373 resource.locked(|store| {
374 store.abort_pending_save();
375 if store.save_on_exit {
376 let _ = store.save_now();
377 }
378 });
379 }
380 }
381
382 Meta::write(self)?;
383
384 Ok(())
385 }
386}
387
388impl<R, C> Resource for StoreCollection<R, C>
389where
390 R: Runtime,
391 C: CollectionMarker,
392{
393}
394
395impl<R, C> fmt::Debug for StoreCollection<R, C>
396where
397 R: Runtime,
398 C: CollectionMarker,
399{
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 f.debug_struct("StoreCollection")
402 .field("default_save_strategy", &self.default_save_strategy)
403 .field("pretty", &self.pretty)
404 .finish_non_exhaustive()
405 }
406}