1mod id;
2mod marshaler;
3mod options;
4mod resource;
5mod save;
6mod state;
7mod watch;
8
9use crate::collection::CollectionMarker;
10use crate::error::{Error, Result};
11use crate::event::{
12 emit, ConfigPayload, EventSource, StatePayload, STORE_CONFIG_CHANGE_EVENT,
13 STORE_STATE_CHANGE_EVENT,
14};
15use crate::manager::ManagerExt;
16use crate::StoreCollection;
17use options::set_options;
18use save::{debounce, throttle, SaveHandle};
19use serde::de::DeserializeOwned;
20use serde_json::Value;
21use std::collections::HashMap;
22use std::fs::File;
23use std::io::ErrorKind;
24use std::io::Write;
25use std::marker::PhantomData;
26use std::path::PathBuf;
27use std::sync::{Arc, OnceLock};
28use std::{fmt, fs};
29use tauri::async_runtime::spawn_blocking;
30use tauri::{AppHandle, ResourceId, Runtime};
31use watch::Watcher;
32
33pub use id::StoreId;
34pub use marshaler::{JsonMarshaler, Marshaler, MarshalingError, PrettyJsonMarshaler};
35pub use options::StoreOptions;
36pub(crate) use resource::StoreResource;
37pub use save::SaveStrategy;
38pub use state::StoreState;
39pub use watch::WatcherId;
40
41#[cfg(feature = "marshaler-toml")]
42pub use marshaler::{PrettyTomlMarshaler, TomlMarshaler};
43
44type ResourceTuple<R, C> = (ResourceId, Arc<StoreResource<R, C>>);
45
46pub struct Store<R, C>
48where
49 R: Runtime,
50 C: CollectionMarker,
51{
52 app: AppHandle<R>,
53 pub(crate) id: StoreId,
54 state: StoreState,
55 pub(crate) save_on_exit: bool,
56 save_on_change: bool,
57 save_strategy: Option<SaveStrategy>,
58 debounce_save_handle: OnceLock<SaveHandle<R>>,
59 throttle_save_handle: OnceLock<SaveHandle<R>>,
60 watchers: HashMap<WatcherId, Watcher<R>>,
61 phantom: PhantomData<C>,
62}
63
64impl<R, C> Store<R, C>
65where
66 R: Runtime,
67 C: CollectionMarker,
68{
69 pub(crate) fn load(app: &AppHandle<R>, id: impl AsRef<str>) -> Result<ResourceTuple<R, C>> {
70 let id = StoreId::from(id.as_ref());
71 let collection = app.store_collection_with_marker::<C>();
72 let marshaler = collection.marshaler_table.get(&id);
73 let path = make_path::<R, C>(&collection, &id, marshaler.extension());
74 let state = match fs::read(&path) {
75 Ok(bytes) => marshaler
76 .deserialize(&bytes)
77 .map_err(Error::FailedToDeserialize)?,
78 Err(err) if err.kind() == ErrorKind::NotFound => StoreState::default(),
79 Err(err) => return Err(Error::Io(err)),
80 };
81
82 let mut store = Self {
83 app: app.clone(),
84 id,
85 state,
86 save_on_change: false,
87 save_on_exit: true,
88 save_strategy: None,
89 debounce_save_handle: OnceLock::new(),
90 throttle_save_handle: OnceLock::new(),
91 watchers: HashMap::new(),
92 phantom: PhantomData,
93 };
94
95 store.run_pending_migrations()?;
96
97 Ok(StoreResource::create(app, store))
98 }
99
100 fn run_pending_migrations(&mut self) -> Result<()> {
101 self
102 .app
103 .store_collection_with_marker::<C>()
104 .migrator
105 .lock()
106 .expect("migrator is poisoned")
107 .migrate::<R, C>(&self.app, &self.id, &mut self.state)
108 }
109
110 #[inline]
112 pub fn id(&self) -> StoreId {
113 self.id.clone()
114 }
115
116 pub fn path(&self) -> PathBuf {
118 let collection = self.app.store_collection_with_marker::<C>();
119 let marshaler = collection.marshaler_table.get(&self.id);
120 make_path::<R, C>(&collection, &self.id, marshaler.extension())
121 }
122
123 pub fn app_handle(&self) -> &AppHandle<R> {
125 &self.app
126 }
127
128 #[inline]
130 pub fn raw_state(&self) -> &StoreState {
131 &self.state
132 }
133
134 pub fn state<T>(&self) -> Result<T>
136 where
137 T: DeserializeOwned,
138 {
139 let value = Value::from(&self.state);
140 Ok(serde_json::from_value(value)?)
141 }
142
143 pub fn state_or<T>(&self, default: T) -> T
147 where
148 T: DeserializeOwned,
149 {
150 self.state().unwrap_or(default)
151 }
152
153 pub fn state_or_default<T>(&self) -> T
157 where
158 T: DeserializeOwned + Default,
159 {
160 self.state().unwrap_or_default()
161 }
162
163 pub fn state_or_else<T>(&self, f: impl FnOnce() -> T) -> T
167 where
168 T: DeserializeOwned,
169 {
170 self.state().unwrap_or_else(|_| f())
171 }
172
173 pub fn get_raw(&self, key: impl AsRef<str>) -> Option<&Value> {
175 self.state.get_raw(key)
176 }
177
178 pub unsafe fn get_raw_unchecked(&self, key: impl AsRef<str>) -> &Value {
184 unsafe { self.state.get_raw_unchecked(key) }
185 }
186
187 pub fn get<T>(&self, key: impl AsRef<str>) -> Result<T>
189 where
190 T: DeserializeOwned,
191 {
192 self.state.get(key)
193 }
194
195 pub fn get_or<T>(&self, key: impl AsRef<str>, default: T) -> T
199 where
200 T: DeserializeOwned,
201 {
202 self.state.get_or(key, default)
203 }
204
205 pub fn get_or_default<T>(&self, key: impl AsRef<str>) -> T
209 where
210 T: DeserializeOwned + Default,
211 {
212 self.state.get_or_default(key)
213 }
214
215 pub fn get_or_else<T>(&self, key: impl AsRef<str>, f: impl FnOnce() -> T) -> T
219 where
220 T: DeserializeOwned,
221 {
222 self.state.get_or_else(key, f)
223 }
224
225 pub unsafe fn get_unchecked<T>(&self, key: impl AsRef<str>) -> T
232 where
233 T: DeserializeOwned,
234 {
235 unsafe { self.state.get_unchecked(key) }
236 }
237
238 pub fn set(&mut self, key: impl AsRef<str>, value: impl Into<Value>) -> Result<()> {
240 self.state.set(key, value);
241 self.on_state_change(None::<&str>)
242 }
243
244 #[doc(hidden)]
246 pub fn patch_with_source<S, E>(&mut self, state: S, source: E) -> Result<()>
247 where
248 S: Into<StoreState>,
249 E: Into<EventSource>,
250 {
251 self.state.patch(state);
252 self.on_state_change(source)
253 }
254
255 pub fn patch<S>(&mut self, state: S) -> Result<()>
257 where
258 S: Into<StoreState>,
259 {
260 self.patch_with_source(state, None::<&str>)
261 }
262
263 pub fn has(&self, key: impl AsRef<str>) -> bool {
265 self.state.has(key)
266 }
267
268 pub fn keys(&self) -> impl Iterator<Item = &String> {
270 self.state.keys()
271 }
272
273 pub fn values(&self) -> impl Iterator<Item = &Value> {
275 self.state.values()
276 }
277
278 pub fn entries(&self) -> impl Iterator<Item = (&String, &Value)> {
280 self.state.entries()
281 }
282
283 #[inline]
285 pub fn len(&self) -> usize {
286 self.state.len()
287 }
288
289 #[inline]
291 pub fn is_empty(&self) -> bool {
292 self.state.is_empty()
293 }
294
295 pub fn save(&self) -> Result<()> {
297 match self.save_strategy() {
298 SaveStrategy::Immediate => self.save_now()?,
299 SaveStrategy::Debounce(duration) => {
300 self
301 .debounce_save_handle
302 .get_or_init(|| debounce::<R, C>(self.id.clone(), duration))
303 .call(&self.app);
304 }
305 SaveStrategy::Throttle(duration) => {
306 self
307 .throttle_save_handle
308 .get_or_init(|| throttle::<R, C>(self.id.clone(), duration))
309 .call(&self.app);
310 }
311 }
312
313 Ok(())
314 }
315
316 pub fn save_now(&self) -> Result<()> {
318 let collection = self.app.store_collection_with_marker::<C>();
319 if collection.save_denylist.contains(&self.id) {
320 return Ok(());
321 }
322
323 let marshaler = collection.marshaler_table.get(&self.id);
324 let bytes = marshaler
325 .serialize(&self.state)
326 .map_err(Error::FailedToSerialize)?;
327
328 let path = self.path();
329 if let Some(parent) = path.parent() {
330 fs::create_dir_all(parent)?;
331 }
332
333 let mut file = File::create(path)?;
334 file.write_all(&bytes)?;
335 file.flush()?;
336
337 if cfg!(feature = "file-sync-all") {
338 file.sync_all()?;
339 }
340
341 Ok(())
342 }
343
344 #[inline]
347 pub fn save_on_exit(&mut self, enabled: bool) {
348 self.save_on_exit = enabled;
349 }
350
351 #[inline]
353 pub fn save_on_change(&mut self, enabled: bool) {
354 self.save_on_change = enabled;
355 }
356
357 pub fn save_strategy(&self) -> SaveStrategy {
359 self.save_strategy.unwrap_or_else(|| {
360 self
361 .app
362 .store_collection_with_marker::<C>()
363 .default_save_strategy
364 })
365 }
366
367 pub fn set_save_strategy(&mut self, strategy: SaveStrategy) {
370 if strategy.is_debounce() {
371 self
372 .debounce_save_handle
373 .take()
374 .inspect(SaveHandle::abort);
375 } else if strategy.is_throttle() {
376 self
377 .throttle_save_handle
378 .take()
379 .inspect(SaveHandle::abort);
380 }
381
382 self.save_strategy = Some(strategy);
383 }
384
385 pub fn watch<F>(&mut self, f: F) -> WatcherId
387 where
388 F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
389 {
390 let (id, listener) = Watcher::new(f);
391 self.watchers.insert(id, listener);
392 id
393 }
394
395 pub fn unwatch(&mut self, id: impl Into<WatcherId>) -> bool {
397 self.watchers.remove(&id.into()).is_some()
398 }
399
400 pub fn set_options(&mut self, options: StoreOptions) -> Result<()> {
402 self.set_options_with_source(options, None::<&str>)
403 }
404
405 #[doc(hidden)]
407 pub fn set_options_with_source<E>(&mut self, options: StoreOptions, source: E) -> Result<()>
408 where
409 E: Into<EventSource>,
410 {
411 set_options(self, options);
412 self.on_config_change(source)
413 }
414
415 fn on_state_change(&self, source: impl Into<EventSource>) -> Result<()> {
416 self.emit_state_change(source)?;
417 self.call_watchers();
418
419 if self.save_on_change {
420 self.save()?;
421 }
422
423 Ok(())
424 }
425
426 fn emit_state_change(&self, source: impl Into<EventSource>) -> Result<()> {
427 let source: EventSource = source.into();
428
429 if !source.is_backend()
432 && self
433 .app
434 .store_collection_with_marker::<C>()
435 .sync_denylist
436 .contains(&self.id)
437 {
438 return Ok(());
439 }
440
441 emit(
442 &self.app,
443 STORE_STATE_CHANGE_EVENT,
444 &StatePayload::from(self),
445 source,
446 )
447 }
448
449 fn on_config_change(&self, source: impl Into<EventSource>) -> Result<()> {
450 self.emit_config_change(source)
451 }
452
453 fn emit_config_change(&self, source: impl Into<EventSource>) -> Result<()> {
454 emit(
455 &self.app,
456 STORE_CONFIG_CHANGE_EVENT,
457 &ConfigPayload::from(self),
458 source,
459 )
460 }
461
462 fn call_watchers(&self) {
464 if self.watchers.is_empty() {
465 return;
466 }
467
468 for watcher in self.watchers.values() {
469 let app = self.app.clone();
470 let watcher = watcher.clone();
471 spawn_blocking(move || watcher.call(app));
472 }
473 }
474
475 pub(crate) fn abort_pending_save(&self) {
476 self
477 .debounce_save_handle
478 .get()
479 .map(SaveHandle::abort);
480
481 self
482 .throttle_save_handle
483 .get()
484 .map(SaveHandle::abort);
485 }
486
487 pub(crate) fn destroy(&mut self) -> Result<()> {
488 self.abort_pending_save();
489 self.state.clear();
490 fs::remove_file(self.path())?;
491 Ok(())
492 }
493}
494
495impl<R, C> fmt::Debug for Store<R, C>
496where
497 R: Runtime,
498 C: CollectionMarker,
499{
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 f.debug_struct("Store")
502 .field("id", &self.id)
503 .field("state", &self.state)
504 .field("watchers", &self.watchers.len())
505 .field("save_on_exit", &self.save_on_exit)
506 .field("save_on_change", &self.save_on_change)
507 .field("save_strategy", &self.save_strategy)
508 .finish_non_exhaustive()
509 }
510}
511
512fn make_path<R, C>(collection: &StoreCollection<R, C>, id: &StoreId, extension: &str) -> PathBuf
513where
514 R: Runtime,
515 C: CollectionMarker,
516{
517 debug_assert!(
518 !extension.eq_ignore_ascii_case("tauristore"),
519 "illegal store extension: {extension}"
520 );
521
522 let filename = if cfg!(debug_assertions) && collection.debug_stores {
523 format!("{id}.dev.{extension}")
524 } else {
525 format!("{id}.{extension}")
526 };
527
528 collection.path_of(id).join(filename)
529}