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-cbor")]
42pub use marshaler::CborMarshaler;
43#[cfg(feature = "marshaler-ron")]
44pub use marshaler::{PrettyRonMarshaler, RonMarshaler};
45#[cfg(feature = "marshaler-toml")]
46pub use marshaler::{PrettyTomlMarshaler, TomlMarshaler};
47
48type ResourceTuple<R, C> = (ResourceId, Arc<StoreResource<R, C>>);
49
50pub struct Store<R, C>
52where
53 R: Runtime,
54 C: CollectionMarker,
55{
56 app: AppHandle<R>,
57 pub(crate) id: StoreId,
58 state: StoreState,
59 pub(crate) save_on_exit: bool,
60 save_on_change: bool,
61 save_strategy: Option<SaveStrategy>,
62 debounce_save_handle: OnceLock<SaveHandle<R>>,
63 throttle_save_handle: OnceLock<SaveHandle<R>>,
64 watchers: HashMap<WatcherId, Watcher<R>>,
65 phantom: PhantomData<C>,
66}
67
68impl<R, C> Store<R, C>
69where
70 R: Runtime,
71 C: CollectionMarker,
72{
73 pub(crate) fn load(app: &AppHandle<R>, id: impl AsRef<str>) -> Result<ResourceTuple<R, C>> {
74 let id = StoreId::from(id.as_ref());
75 let collection = app.store_collection_with_marker::<C>();
76 let marshaler = collection.marshaler_table.get(&id);
77 let path = make_path::<R, C>(&collection, &id, marshaler.extension());
78 let state = match fs::read(&path) {
79 Ok(bytes) => marshaler
80 .deserialize(&bytes)
81 .map_err(Error::FailedToDeserialize)?,
82 Err(err) if err.kind() == ErrorKind::NotFound => StoreState::default(),
83 Err(err) => return Err(Error::Io(err)),
84 };
85
86 let mut store = Self {
87 app: app.clone(),
88 id,
89 state,
90 save_on_change: false,
91 save_on_exit: true,
92 save_strategy: None,
93 debounce_save_handle: OnceLock::new(),
94 throttle_save_handle: OnceLock::new(),
95 watchers: HashMap::new(),
96 phantom: PhantomData,
97 };
98
99 store.run_pending_migrations()?;
100
101 Ok(StoreResource::create(app, store))
102 }
103
104 fn run_pending_migrations(&mut self) -> Result<()> {
105 self
106 .app
107 .store_collection_with_marker::<C>()
108 .migrator
109 .lock()
110 .expect("migrator is poisoned")
111 .migrate::<R, C>(&self.app, &self.id, &mut self.state)
112 }
113
114 #[inline]
116 pub fn id(&self) -> StoreId {
117 self.id.clone()
118 }
119
120 pub fn path(&self) -> PathBuf {
122 let collection = self.app.store_collection_with_marker::<C>();
123 let marshaler = collection.marshaler_table.get(&self.id);
124 make_path::<R, C>(&collection, &self.id, marshaler.extension())
125 }
126
127 pub fn app_handle(&self) -> &AppHandle<R> {
129 &self.app
130 }
131
132 #[inline]
134 pub fn raw_state(&self) -> &StoreState {
135 &self.state
136 }
137
138 pub fn state<T>(&self) -> Result<T>
140 where
141 T: DeserializeOwned,
142 {
143 let value = Value::from(&self.state);
144 Ok(serde_json::from_value(value)?)
145 }
146
147 pub fn state_or<T>(&self, default: T) -> T
151 where
152 T: DeserializeOwned,
153 {
154 self.state().unwrap_or(default)
155 }
156
157 pub fn state_or_default<T>(&self) -> T
161 where
162 T: DeserializeOwned + Default,
163 {
164 self.state().unwrap_or_default()
165 }
166
167 pub fn state_or_else<T>(&self, f: impl FnOnce() -> T) -> T
171 where
172 T: DeserializeOwned,
173 {
174 self.state().unwrap_or_else(|_| f())
175 }
176
177 pub fn get_raw(&self, key: impl AsRef<str>) -> Option<&Value> {
179 self.state.get_raw(key)
180 }
181
182 pub unsafe fn get_raw_unchecked(&self, key: impl AsRef<str>) -> &Value {
188 unsafe { self.state.get_raw_unchecked(key) }
189 }
190
191 pub fn get<T>(&self, key: impl AsRef<str>) -> Result<T>
193 where
194 T: DeserializeOwned,
195 {
196 self.state.get(key)
197 }
198
199 pub fn get_or<T>(&self, key: impl AsRef<str>, default: T) -> T
203 where
204 T: DeserializeOwned,
205 {
206 self.state.get_or(key, default)
207 }
208
209 pub fn get_or_default<T>(&self, key: impl AsRef<str>) -> T
213 where
214 T: DeserializeOwned + Default,
215 {
216 self.state.get_or_default(key)
217 }
218
219 pub fn get_or_else<T>(&self, key: impl AsRef<str>, f: impl FnOnce() -> T) -> T
223 where
224 T: DeserializeOwned,
225 {
226 self.state.get_or_else(key, f)
227 }
228
229 pub unsafe fn get_unchecked<T>(&self, key: impl AsRef<str>) -> T
236 where
237 T: DeserializeOwned,
238 {
239 unsafe { self.state.get_unchecked(key) }
240 }
241
242 pub fn set(&mut self, key: impl AsRef<str>, value: impl Into<Value>) -> Result<()> {
244 self.state.set(key, value);
245 self.on_state_change(None::<&str>)
246 }
247
248 #[doc(hidden)]
250 pub fn patch_with_source<S, E>(&mut self, state: S, source: E) -> Result<()>
251 where
252 S: Into<StoreState>,
253 E: Into<EventSource>,
254 {
255 self.state.patch(state);
256 self.on_state_change(source)
257 }
258
259 pub fn patch<S>(&mut self, state: S) -> Result<()>
261 where
262 S: Into<StoreState>,
263 {
264 self.patch_with_source(state, None::<&str>)
265 }
266
267 pub fn has(&self, key: impl AsRef<str>) -> bool {
269 self.state.has(key)
270 }
271
272 pub fn keys(&self) -> impl Iterator<Item = &String> {
274 self.state.keys()
275 }
276
277 pub fn values(&self) -> impl Iterator<Item = &Value> {
279 self.state.values()
280 }
281
282 pub fn entries(&self) -> impl Iterator<Item = (&String, &Value)> {
284 self.state.entries()
285 }
286
287 #[inline]
289 pub fn len(&self) -> usize {
290 self.state.len()
291 }
292
293 #[inline]
295 pub fn is_empty(&self) -> bool {
296 self.state.is_empty()
297 }
298
299 pub fn save(&self) -> Result<()> {
301 match self.save_strategy() {
302 SaveStrategy::Immediate => self.save_now()?,
303 SaveStrategy::Debounce(duration) => {
304 self
305 .debounce_save_handle
306 .get_or_init(|| debounce::<R, C>(self.id.clone(), duration))
307 .call(&self.app);
308 }
309 SaveStrategy::Throttle(duration) => {
310 self
311 .throttle_save_handle
312 .get_or_init(|| throttle::<R, C>(self.id.clone(), duration))
313 .call(&self.app);
314 }
315 }
316
317 Ok(())
318 }
319
320 pub fn save_now(&self) -> Result<()> {
322 let collection = self.app.store_collection_with_marker::<C>();
323 if collection.save_denylist.contains(&self.id) {
324 return Ok(());
325 }
326
327 let marshaler = collection.marshaler_table.get(&self.id);
328 let bytes = marshaler
329 .serialize(&self.state)
330 .map_err(Error::FailedToSerialize)?;
331
332 let path = self.path();
333 if let Some(parent) = path.parent() {
334 fs::create_dir_all(parent)?;
335 }
336
337 let mut file = File::create(path)?;
338 file.write_all(&bytes)?;
339 file.flush()?;
340
341 if cfg!(feature = "file-sync-all") {
342 file.sync_all()?;
343 }
344
345 Ok(())
346 }
347
348 #[inline]
351 pub fn save_on_exit(&mut self, enabled: bool) {
352 self.save_on_exit = enabled;
353 }
354
355 #[inline]
357 pub fn save_on_change(&mut self, enabled: bool) {
358 self.save_on_change = enabled;
359 }
360
361 pub fn save_strategy(&self) -> SaveStrategy {
363 self.save_strategy.unwrap_or_else(|| {
364 self
365 .app
366 .store_collection_with_marker::<C>()
367 .default_save_strategy
368 })
369 }
370
371 pub fn set_save_strategy(&mut self, strategy: SaveStrategy) {
374 if strategy.is_debounce() {
375 self
376 .debounce_save_handle
377 .take()
378 .inspect(SaveHandle::abort);
379 } else if strategy.is_throttle() {
380 self
381 .throttle_save_handle
382 .take()
383 .inspect(SaveHandle::abort);
384 }
385
386 self.save_strategy = Some(strategy);
387 }
388
389 pub fn watch<F>(&mut self, f: F) -> WatcherId
391 where
392 F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
393 {
394 let (id, listener) = Watcher::new(f);
395 self.watchers.insert(id, listener);
396 id
397 }
398
399 pub fn unwatch(&mut self, id: impl Into<WatcherId>) -> bool {
401 self.watchers.remove(&id.into()).is_some()
402 }
403
404 pub fn set_options(&mut self, options: StoreOptions) -> Result<()> {
406 self.set_options_with_source(options, None::<&str>)
407 }
408
409 #[doc(hidden)]
411 pub fn set_options_with_source<E>(&mut self, options: StoreOptions, source: E) -> Result<()>
412 where
413 E: Into<EventSource>,
414 {
415 set_options(self, options);
416 self.on_config_change(source)
417 }
418
419 fn on_state_change(&self, source: impl Into<EventSource>) -> Result<()> {
420 self.emit_state_change(source)?;
421 self.call_watchers();
422
423 if self.save_on_change {
424 self.save()?;
425 }
426
427 Ok(())
428 }
429
430 fn emit_state_change(&self, source: impl Into<EventSource>) -> Result<()> {
431 let source: EventSource = source.into();
432
433 if !source.is_backend()
436 && self
437 .app
438 .store_collection_with_marker::<C>()
439 .sync_denylist
440 .contains(&self.id)
441 {
442 return Ok(());
443 }
444
445 emit(
446 &self.app,
447 STORE_STATE_CHANGE_EVENT,
448 &StatePayload::from(self),
449 source,
450 )
451 }
452
453 fn on_config_change(&self, source: impl Into<EventSource>) -> Result<()> {
454 self.emit_config_change(source)
455 }
456
457 fn emit_config_change(&self, source: impl Into<EventSource>) -> Result<()> {
458 emit(
459 &self.app,
460 STORE_CONFIG_CHANGE_EVENT,
461 &ConfigPayload::from(self),
462 source,
463 )
464 }
465
466 fn call_watchers(&self) {
468 if self.watchers.is_empty() {
469 return;
470 }
471
472 for watcher in self.watchers.values() {
473 let app = self.app.clone();
474 let watcher = watcher.clone();
475 spawn_blocking(move || watcher.call(app));
476 }
477 }
478
479 pub(crate) fn abort_pending_save(&self) {
480 self
481 .debounce_save_handle
482 .get()
483 .map(SaveHandle::abort);
484
485 self
486 .throttle_save_handle
487 .get()
488 .map(SaveHandle::abort);
489 }
490
491 pub(crate) fn destroy(&mut self) -> Result<()> {
492 self.abort_pending_save();
493 self.state.clear();
494 fs::remove_file(self.path())?;
495 Ok(())
496 }
497}
498
499impl<R, C> fmt::Debug for Store<R, C>
500where
501 R: Runtime,
502 C: CollectionMarker,
503{
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 f.debug_struct("Store")
506 .field("id", &self.id)
507 .field("state", &self.state)
508 .field("watchers", &self.watchers.len())
509 .field("save_on_exit", &self.save_on_exit)
510 .field("save_on_change", &self.save_on_change)
511 .field("save_strategy", &self.save_strategy)
512 .finish_non_exhaustive()
513 }
514}
515
516fn make_path<R, C>(collection: &StoreCollection<R, C>, id: &StoreId, extension: &str) -> PathBuf
517where
518 R: Runtime,
519 C: CollectionMarker,
520{
521 debug_assert!(
522 !extension.eq_ignore_ascii_case("tauristore"),
523 "illegal store extension: {extension}"
524 );
525
526 let filename = if cfg!(debug_assertions) && collection.debug_stores {
527 format!("{id}.dev.{extension}")
528 } else {
529 format!("{id}.{extension}")
530 };
531
532 collection.path_of(id).join(filename)
533}