1mod autosave;
2mod builder;
3mod path;
4
5use crate::error::Result;
6use crate::event::{emit, STORE_UNLOAD_EVENT};
7use crate::meta;
8use crate::store::{SaveStrategy, Store, StoreId, StoreResource, StoreState, WatcherId};
9use autosave::Autosave;
10use dashmap::DashMap;
11use path::set_path;
12use serde::de::DeserializeOwned;
13use serde_json::Value as Json;
14use std::collections::HashSet;
15use std::fmt;
16use std::path::{Path, PathBuf};
17use std::sync::{Arc, Mutex, OnceLock};
18use std::time::Duration;
19use tauri::{AppHandle, Resource, ResourceId, Runtime};
20
21pub use builder::StoreCollectionBuilder;
22
23pub(crate) static RESOURCE_ID: OnceLock<ResourceId> = OnceLock::new();
24
25pub type OnLoadFn<R> = dyn Fn(&Store<R>) -> Result<()> + Send + Sync;
27
28pub struct StoreCollection<R: Runtime> {
31 pub(crate) app: AppHandle<R>,
32 pub(crate) name: Box<str>,
33 pub(crate) path: Mutex<PathBuf>,
34 pub(crate) stores: DashMap<StoreId, ResourceId>,
35 pub(crate) on_load: Option<Box<OnLoadFn<R>>>,
36 pub(crate) autosave: Mutex<Autosave>,
37 pub(crate) default_save_strategy: SaveStrategy,
38 pub(crate) save_denylist: Option<HashSet<StoreId>>,
39 pub(crate) sync_denylist: Option<HashSet<StoreId>>,
40 pub(crate) pretty: bool,
41}
42
43impl<R: Runtime> StoreCollection<R> {
44 pub fn builder() -> StoreCollectionBuilder<R> {
46 StoreCollectionBuilder::new()
47 }
48
49 pub(crate) fn get_resource(&self, id: impl AsRef<str>) -> Result<Arc<StoreResource<R>>> {
50 let id = StoreId::from(id.as_ref());
51 let rid = match self.rid(&id) {
52 Some(rid) => rid,
53 None => self.load_store(id)?,
54 };
55
56 StoreResource::get(&self.app, rid)
57 }
58
59 fn load_store(&self, id: StoreId) -> Result<ResourceId> {
60 let (rid, resource) = Store::load(&self.app, &id)?;
61 if let Some(on_load) = &self.on_load {
62 resource.locked(|store| on_load(store))?;
63 }
64
65 self.stores.insert(id, rid);
66 Ok(rid)
67 }
68
69 fn rid(&self, store_id: &StoreId) -> Option<ResourceId> {
71 self.stores.get(store_id).map(|it| *it.value())
72 }
73
74 fn rids(&self) -> Vec<ResourceId> {
76 self.stores.iter().map(|it| *it.value()).collect()
77 }
78
79 pub fn ids(&self) -> Vec<StoreId> {
81 self
82 .stores
83 .iter()
84 .map(|it| it.key().clone())
85 .collect()
86 }
87
88 pub fn path(&self) -> PathBuf {
90 self.path.lock().unwrap().clone()
91 }
92
93 pub fn set_path(&self, path: impl AsRef<Path>) -> Result<()> {
96 set_path(self, path)?;
97 meta::save(self)?;
98 Ok(())
99 }
100
101 pub fn with_store<F, T>(&self, store_id: impl AsRef<str>, f: F) -> Result<T>
103 where
104 F: FnOnce(&mut Store<R>) -> T,
105 {
106 Ok(self.get_resource(store_id)?.locked(f))
107 }
108
109 pub fn state(&self, store_id: impl AsRef<str>) -> Result<StoreState> {
111 self
112 .get_resource(store_id)?
113 .locked(|store| Ok(store.state().clone()))
114 }
115
116 pub fn try_state<T>(&self, store_id: impl AsRef<str>) -> Result<T>
118 where
119 T: DeserializeOwned,
120 {
121 self
122 .get_resource(store_id)?
123 .locked(|store| store.try_state())
124 }
125
126 pub fn get(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Option<Json> {
128 self
129 .get_resource(store_id)
130 .ok()?
131 .locked(|store| store.get(key).cloned())
132 }
133
134 pub fn try_get<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Result<T>
136 where
137 T: DeserializeOwned,
138 {
139 self
140 .get_resource(store_id)?
141 .locked(|store| store.try_get(key))
142 }
143
144 pub fn try_get_or<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>, default: T) -> T
148 where
149 T: DeserializeOwned,
150 {
151 self.try_get(store_id, key).unwrap_or(default)
152 }
153
154 pub fn try_get_or_default<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
158 where
159 T: Default + DeserializeOwned,
160 {
161 self.try_get(store_id, key).unwrap_or_default()
162 }
163
164 pub fn try_get_or_else<T>(
168 &self,
169 store_id: impl AsRef<str>,
170 key: impl AsRef<str>,
171 f: impl FnOnce() -> T,
172 ) -> T
173 where
174 T: DeserializeOwned,
175 {
176 self
177 .try_get(store_id, key)
178 .unwrap_or_else(|_| f())
179 }
180
181 pub fn set<K, V>(&self, store_id: impl AsRef<str>, key: K, value: V) -> Result<()>
183 where
184 K: AsRef<str>,
185 V: Into<Json>,
186 {
187 self
188 .get_resource(store_id)?
189 .locked(|store| store.set(key, value))
190 }
191
192 pub fn patch<S>(&self, store_id: impl AsRef<str>, state: S) -> Result<()>
194 where
195 S: Into<StoreState>,
196 {
197 self
198 .get_resource(store_id)?
199 .locked(|store| store.patch(state))
200 }
201
202 pub fn save(&self, store_id: impl AsRef<str>) -> Result<()> {
204 self
205 .get_resource(store_id)?
206 .locked(|store| store.save())
207 }
208
209 pub fn save_now(&self, store_id: impl AsRef<str>) -> Result<()> {
211 self.get_resource(store_id)?.locked(|store| {
212 store.abort_pending_save();
213 store.save_now()
214 })
215 }
216
217 pub fn save_some(&self, ids: &[impl AsRef<str>]) -> Result<()> {
219 ids.iter().try_for_each(|id| self.save(id))
220 }
221
222 pub fn save_some_now(&self, ids: &[impl AsRef<str>]) -> Result<()> {
224 ids.iter().try_for_each(|id| self.save_now(id))
225 }
226
227 pub fn save_all(&self) -> Result<()> {
229 self
233 .rids()
234 .into_iter()
235 .try_for_each(|rid| StoreResource::save(&self.app, rid))
236 }
237
238 pub fn save_all_now(&self) -> Result<()> {
240 self
241 .rids()
242 .into_iter()
243 .try_for_each(|rid| StoreResource::save_now(&self.app, rid))
244 }
245
246 pub fn default_save_strategy(&self) -> SaveStrategy {
249 self.default_save_strategy
250 }
251
252 pub fn set_autosave(&self, duration: Duration) {
254 if let Ok(mut autosave) = self.autosave.lock() {
255 autosave.set_duration(duration);
256 autosave.start(&self.app);
257 }
258 }
259
260 pub fn clear_autosave(&self) {
262 if let Ok(mut autosave) = self.autosave.lock() {
263 autosave.stop();
264 }
265 }
266
267 pub fn watch<F>(&self, store_id: impl AsRef<str>, f: F) -> Result<WatcherId>
269 where
270 F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
271 {
272 self
273 .get_resource(store_id)?
274 .locked(|store| Ok(store.watch(f)))
275 }
276
277 pub fn unwatch(
279 &self,
280 store_id: impl AsRef<str>,
281 watcher_id: impl Into<WatcherId>,
282 ) -> Result<bool> {
283 self
284 .get_resource(store_id)?
285 .locked(|store| Ok(store.unwatch(watcher_id)))
286 }
287
288 #[doc(hidden)]
290 pub fn unload_store(&self, id: &StoreId) -> Result<()> {
291 if let Some((_, rid)) = self.stores.remove(id) {
292 let resource = StoreResource::take(&self.app, rid)?;
296 resource.locked(|store| store.save_now())?;
297
298 emit(&self.app, STORE_UNLOAD_EVENT, id, None::<&str>)?;
299 }
300
301 Ok(())
302 }
303
304 #[doc(hidden)]
306 pub fn on_exit(&self) -> Result<()> {
307 self.clear_autosave();
308
309 for rid in self.rids() {
310 if let Ok(resource) = StoreResource::take(&self.app, rid) {
311 resource.locked(|store| {
312 store.abort_pending_save();
313 if store.save_on_exit {
314 let _ = store.save_now();
315 }
316 });
317 }
318 }
319
320 meta::save(self)
321 }
322}
323
324impl<R: Runtime> Resource for StoreCollection<R> {}
325
326impl<R: Runtime> fmt::Debug for StoreCollection<R> {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 f.debug_struct("StoreCollection")
329 .field("default_save_strategy", &self.default_save_strategy)
330 .field("pretty", &self.pretty)
331 .finish_non_exhaustive()
332 }
333}