1mod autosave;
2mod builder;
3mod handle;
4mod marker;
5mod table;
6
7use crate::error::Result;
8use crate::event::{emit, STORE_UNLOAD_EVENT};
9use crate::migration::Migrator;
10use crate::store::{SaveStrategy, Store, StoreId, StoreResource, StoreState, WatcherId};
11use autosave::Autosave;
12use dashmap::{DashMap, DashSet};
13use serde::de::DeserializeOwned;
14use serde_json::Value;
15use std::fmt;
16use std::marker::PhantomData;
17use std::path::Path;
18use std::sync::{Arc, Mutex};
19use std::time::Duration;
20use table::{MarshalerTable, PathTable};
21use tauri::{AppHandle, Resource, ResourceId, Runtime};
22
23pub use builder::StoreCollectionBuilder;
24pub use handle::Handle;
25pub use marker::{CollectionMarker, DefaultMarker};
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) handle: Handle<R>,
38 pub(crate) name: Box<str>,
39 pub(crate) stores: DashMap<StoreId, ResourceId>,
40 pub(crate) path_table: PathTable,
41 pub(crate) marshaler_table: MarshalerTable,
42 pub(crate) on_load: Option<Box<OnLoadFn<R, C>>>,
43 pub(crate) autosave: Mutex<Autosave>,
44 pub(crate) default_save_strategy: SaveStrategy,
45 pub(crate) save_denylist: DashSet<StoreId>,
46 pub(crate) sync_denylist: DashSet<StoreId>,
47 pub(crate) migrator: Mutex<Migrator>,
48 pub(crate) debug_stores: bool,
49 phantom: PhantomData<C>,
50}
51
52impl<R, C> StoreCollection<R, C>
53where
54 R: Runtime,
55 C: CollectionMarker,
56{
57 pub fn builder() -> StoreCollectionBuilder<R, C> {
59 StoreCollectionBuilder::<R, C>::new()
60 }
61
62 pub(crate) fn get_resource(&self, id: impl AsRef<str>) -> Result<Arc<StoreResource<R, C>>> {
63 let id = StoreId::from(id.as_ref());
64 let rid = match self.rid(&id) {
65 Some(rid) => rid,
66 None => self.load_store(id)?,
67 };
68
69 StoreResource::get(self.handle.app(), rid)
70 }
71
72 fn load_store(&self, id: StoreId) -> Result<ResourceId> {
73 let (rid, resource) = Store::load(self.handle.app(), &id)?;
74 if let Some(on_load) = &self.on_load {
75 resource.locked(|store| on_load(store))?;
76 }
77
78 self.stores.insert(id, rid);
79 Ok(rid)
80 }
81
82 fn rid(&self, store_id: &StoreId) -> Option<ResourceId> {
84 self.stores.get(store_id).map(|it| *it.value())
85 }
86
87 fn rids(&self) -> Vec<ResourceId> {
89 self.stores.iter().map(|it| *it.value()).collect()
90 }
91
92 pub fn app_handle(&self) -> &AppHandle<R> {
94 self.handle.app()
95 }
96
97 pub fn ids(&self) -> Vec<StoreId> {
99 self
100 .stores
101 .iter()
102 .map(|it| it.key().clone())
103 .collect()
104 }
105
106 #[inline]
108 pub fn name(&self) -> &str {
109 &self.name
110 }
111
112 #[inline]
114 pub fn path(&self) -> &Path {
115 &self.path_table.default
116 }
117
118 pub fn path_of(&self, store_id: impl AsRef<str>) -> &Path {
120 let store_id = StoreId::from(store_id.as_ref());
121 self.path_table.get(&store_id)
122 }
123
124 pub fn with_store<F, T>(&self, store_id: impl AsRef<str>, f: F) -> Result<T>
126 where
127 F: FnOnce(&mut Store<R, C>) -> T,
128 {
129 Ok(self.get_resource(store_id)?.locked(f))
130 }
131
132 pub fn raw_state(&self, store_id: impl AsRef<str>) -> Result<StoreState> {
134 self
135 .get_resource(store_id)?
136 .locked(|store| Ok(store.raw_state().clone()))
137 }
138
139 pub fn state<T>(&self, store_id: impl AsRef<str>) -> Result<T>
141 where
142 T: DeserializeOwned,
143 {
144 self
145 .get_resource(store_id)?
146 .locked(|store| store.state())
147 }
148
149 pub fn state_or<T>(&self, store_id: impl AsRef<str>, default: T) -> Result<T>
153 where
154 T: DeserializeOwned,
155 {
156 self
157 .get_resource(store_id)?
158 .locked(move |store| Ok(store.state_or(default)))
159 }
160
161 pub fn state_or_default<T>(&self, store_id: impl AsRef<str>) -> Result<T>
165 where
166 T: DeserializeOwned + Default,
167 {
168 self
169 .get_resource(store_id)?
170 .locked(|store| Ok(store.state_or_default()))
171 }
172
173 pub fn state_or_else<T>(&self, store_id: impl AsRef<str>, f: impl FnOnce() -> T) -> Result<T>
177 where
178 T: DeserializeOwned,
179 {
180 self
181 .get_resource(store_id)?
182 .locked(|store| Ok(store.state_or_else(f)))
183 }
184
185 pub fn get_raw(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Option<Value> {
187 self
188 .get_resource(store_id)
189 .ok()?
190 .locked(|store| store.get_raw(key).cloned())
191 }
192
193 pub unsafe fn get_raw_unchecked(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Value {
199 unsafe { self.get_raw(store_id, key).unwrap_unchecked() }
200 }
201
202 pub fn get<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Result<T>
204 where
205 T: DeserializeOwned,
206 {
207 self
208 .get_resource(store_id)?
209 .locked(|store| store.get(key))
210 }
211
212 pub fn get_or<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>, default: T) -> T
216 where
217 T: DeserializeOwned,
218 {
219 self.get(store_id, key).unwrap_or(default)
220 }
221
222 pub fn get_or_default<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
226 where
227 T: Default + DeserializeOwned,
228 {
229 self.get(store_id, key).unwrap_or_default()
230 }
231
232 pub fn get_or_else<T>(
236 &self,
237 store_id: impl AsRef<str>,
238 key: impl AsRef<str>,
239 f: impl FnOnce() -> T,
240 ) -> T
241 where
242 T: DeserializeOwned,
243 {
244 self.get(store_id, key).unwrap_or_else(|_| f())
245 }
246
247 pub unsafe fn get_unchecked<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
254 where
255 T: DeserializeOwned,
256 {
257 unsafe { self.get(store_id, key).unwrap_unchecked() }
258 }
259
260 pub fn set<K, V>(&self, store_id: impl AsRef<str>, key: K, value: V) -> Result<()>
262 where
263 K: AsRef<str>,
264 V: Into<Value>,
265 {
266 self
267 .get_resource(store_id)?
268 .locked(|store| store.set(key, value))
269 }
270
271 pub fn patch<S>(&self, store_id: impl AsRef<str>, state: S) -> Result<()>
273 where
274 S: Into<StoreState>,
275 {
276 self
277 .get_resource(store_id)?
278 .locked(|store| store.patch(state))
279 }
280
281 pub fn save(&self, store_id: impl AsRef<str>) -> Result<()> {
283 self
284 .get_resource(store_id)?
285 .locked(|store| store.save())
286 }
287
288 pub fn save_now(&self, store_id: impl AsRef<str>) -> Result<()> {
290 self.get_resource(store_id)?.locked(|store| {
291 store.abort_pending_save();
292 store.save_now()
293 })
294 }
295
296 pub fn save_some(&self, ids: &[impl AsRef<str>]) -> Result<()> {
298 ids.iter().try_for_each(|id| self.save(id))
299 }
300
301 pub fn save_some_now(&self, ids: &[impl AsRef<str>]) -> Result<()> {
303 ids.iter().try_for_each(|id| self.save_now(id))
304 }
305
306 pub fn save_all(&self) -> Result<()> {
308 let app = self.handle.app();
312 self
313 .rids()
314 .into_iter()
315 .try_for_each(|rid| StoreResource::<R, C>::save(app, rid))
316 }
317
318 pub fn save_all_now(&self) -> Result<()> {
320 let app = self.handle.app();
321 self
322 .rids()
323 .into_iter()
324 .try_for_each(|rid| StoreResource::<R, C>::save_now(app, rid))
325 }
326
327 #[inline]
330 pub fn default_save_strategy(&self) -> SaveStrategy {
331 self.default_save_strategy
332 }
333
334 pub fn set_autosave(&self, duration: Duration) {
336 if let Ok(mut autosave) = self.autosave.lock() {
337 autosave.set_duration(duration);
338 autosave.start::<R, C>(self.handle.app());
339 }
340 }
341
342 pub fn clear_autosave(&self) {
344 if let Ok(mut autosave) = self.autosave.lock() {
345 autosave.stop();
346 }
347 }
348
349 pub fn watch<F>(&self, store_id: impl AsRef<str>, f: F) -> Result<WatcherId>
351 where
352 F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
353 {
354 self
355 .get_resource(store_id)?
356 .locked(|store| Ok(store.watch(f)))
357 }
358
359 pub fn unwatch(
361 &self,
362 store_id: impl AsRef<str>,
363 watcher_id: impl Into<WatcherId>,
364 ) -> Result<bool> {
365 self
366 .get_resource(store_id)?
367 .locked(|store| Ok(store.unwatch(watcher_id)))
368 }
369
370 pub fn allow_save(&self, id: impl AsRef<str>) {
372 let id = StoreId::from(id.as_ref());
373 self.save_denylist.remove(&id);
374 }
375
376 pub fn deny_save(&self, id: impl AsRef<str>) {
378 let id = StoreId::from(id.as_ref());
379 self.save_denylist.insert(id);
380 }
381
382 pub fn allow_sync(&self, id: impl AsRef<str>) {
384 let id = StoreId::from(id.as_ref());
385 self.sync_denylist.remove(&id);
386 }
387
388 pub fn deny_sync(&self, id: impl AsRef<str>) {
390 let id = StoreId::from(id.as_ref());
391 self.sync_denylist.insert(id);
392 }
393
394 #[doc(hidden)]
396 pub fn destroy(&self, id: impl AsRef<str>) -> Result<()> {
397 let id = StoreId::from(id.as_ref());
398 self.unload_and(&id, Store::destroy)
399 }
400
401 #[doc(hidden)]
403 pub fn unload_store(&self, id: &StoreId) -> Result<()> {
404 self.unload_and(id, |store| store.save_now())
408 }
409
410 fn unload_and<F>(&self, id: &StoreId, f: F) -> Result<()>
411 where
412 F: FnOnce(&mut Store<R, C>) -> Result<()>,
413 {
414 let app = self.handle.app();
415 if let Some((_, rid)) = self.stores.remove(id) {
416 let resource = StoreResource::<R, C>::take(app, rid)?;
417 resource.locked(f)?;
418 emit(app, STORE_UNLOAD_EVENT, id, None::<&str>)?;
419 }
420
421 Ok(())
422 }
423
424 #[doc(hidden)]
426 pub fn on_exit(&self) -> Result<()> {
427 self.clear_autosave();
428
429 let app = self.handle.app();
430 for rid in self.rids() {
431 if let Ok(resource) = StoreResource::<R, C>::take(app, rid) {
432 resource.locked(|store| {
433 store.abort_pending_save();
434 if store.save_on_exit {
435 let _ = store.save_now();
436 }
437 });
438 }
439 }
440
441 Ok(())
442 }
443}
444
445impl<R, C> Resource for StoreCollection<R, C>
446where
447 R: Runtime,
448 C: CollectionMarker,
449{
450}
451
452impl<R, C> fmt::Debug for StoreCollection<R, C>
453where
454 R: Runtime,
455 C: CollectionMarker,
456{
457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458 f.debug_struct("StoreCollection")
459 .field("name", &self.name)
460 .finish_non_exhaustive()
461 }
462}