1use crate::{ChangePayload, StoreState};
6use serde_json::Value as JsonValue;
7use std::{
8 collections::HashMap,
9 fs,
10 path::{Path, PathBuf},
11 sync::{Arc, Mutex},
12 time::Duration,
13};
14use tauri::{AppHandle, Emitter, Manager, Resource, ResourceId, Runtime, path::BaseDirectory};
15use tokio::{
16 select,
17 sync::mpsc::{UnboundedSender, unbounded_channel},
18 time::sleep,
19};
20
21pub type SerializeFn =
25 fn(&HashMap<String, JsonValue>) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>>;
26pub type DeserializeFn =
30 fn(&[u8]) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error + Send + Sync>>;
31
32pub fn resolve_store_path<R: Runtime>(
41 app: &AppHandle<R>,
42 path: impl AsRef<Path>,
43) -> crate::Result<PathBuf> {
44 Ok(dunce::simplified(&app.path().resolve(path, BaseDirectory::AppData)?).to_path_buf())
45}
46
47pub struct StoreBuilder<R: Runtime> {
49 app: AppHandle<R>,
50 path: PathBuf,
51 defaults: Option<HashMap<String, JsonValue>>,
52 serialize_fn: SerializeFn,
53 deserialize_fn: DeserializeFn,
54 auto_save: Option<Duration>,
55 create_new: bool,
56 override_defaults: bool,
57}
58
59impl<R: Runtime> StoreBuilder<R> {
60 pub fn new<M: Manager<R>, P: AsRef<Path>>(manager: &M, path: P) -> Self {
72 let app = manager.app_handle().clone();
73 let state = app.state::<StoreState>();
74 let serialize_fn = state.default_serialize;
75 let deserialize_fn = state.default_deserialize;
76 Self {
77 app,
78 path: path.as_ref().to_path_buf(),
79 defaults: None,
80 serialize_fn,
81 deserialize_fn,
82 auto_save: Some(Duration::from_millis(100)),
83 create_new: false,
84 override_defaults: false,
85 }
86 }
87
88 pub fn defaults(mut self, defaults: HashMap<String, JsonValue>) -> Self {
105 self.defaults = Some(defaults);
106 self
107 }
108
109 pub fn default(mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
123 let key = key.into();
124 let value = value.into();
125 self.defaults
126 .get_or_insert(HashMap::new())
127 .insert(key, value);
128 self
129 }
130
131 pub fn serialize(mut self, serialize: SerializeFn) -> Self {
145 self.serialize_fn = serialize;
146 self
147 }
148
149 pub fn deserialize(mut self, deserialize: DeserializeFn) -> Self {
163 self.deserialize_fn = deserialize;
164 self
165 }
166
167 pub fn auto_save(mut self, debounce_duration: Duration) -> Self {
181 self.auto_save = Some(debounce_duration);
182 self
183 }
184
185 pub fn disable_auto_save(mut self) -> Self {
187 self.auto_save = None;
188 self
189 }
190
191 pub fn create_new(mut self) -> Self {
193 self.create_new = true;
194 self
195 }
196
197 pub fn override_defaults(mut self) -> Self {
199 self.override_defaults = true;
200 self
201 }
202
203 pub(crate) fn build_inner(mut self) -> crate::Result<(Arc<Store<R>>, ResourceId)> {
204 let stores = self.app.state::<StoreState>().stores.clone();
205 let mut stores = stores.write().unwrap();
206
207 self.path = resolve_store_path(&self.app, self.path)?;
208
209 if self.create_new {
210 if let Some(rid) = stores.remove(&self.path) {
211 let _ = self.app.resources_table().take::<Store<R>>(rid);
212 }
213 } else if let Some(rid) = stores.get(&self.path) {
214 return Ok((self.app.resources_table().get(*rid)?, *rid));
218 }
219
220 let mut store_inner = StoreInner::new(
225 self.app.clone(),
226 self.path.clone(),
227 self.defaults.take(),
228 self.serialize_fn,
229 self.deserialize_fn,
230 );
231
232 if !self.create_new {
233 if self.override_defaults {
234 let _ = store_inner.load_ignore_defaults();
235 } else {
236 let _ = store_inner.load();
237 }
238 }
239
240 let store = Store {
241 auto_save: self.auto_save,
242 auto_save_debounce_sender: Arc::new(Mutex::new(None)),
243 store: Arc::new(Mutex::new(store_inner)),
244 };
245
246 let store = Arc::new(store);
247 let rid = self.app.resources_table().add_arc(store.clone());
248 stores.insert(self.path, rid);
249
250 Ok((store, rid))
251 }
252
253 pub fn build(self) -> crate::Result<Arc<Store<R>>> {
267 let (store, _) = self.build_inner()?;
268 Ok(store)
269 }
270}
271
272enum AutoSaveMessage {
273 Reset,
274 Cancel,
275}
276
277#[derive(Clone)]
278struct StoreInner<R: Runtime> {
279 app: AppHandle<R>,
280 path: PathBuf,
281 cache: HashMap<String, JsonValue>,
282 defaults: Option<HashMap<String, JsonValue>>,
283 serialize_fn: SerializeFn,
284 deserialize_fn: DeserializeFn,
285}
286
287impl<R: Runtime> StoreInner<R> {
288 fn new(
289 app: AppHandle<R>,
290 path: PathBuf,
291 defaults: Option<HashMap<String, JsonValue>>,
292 serialize_fn: SerializeFn,
293 deserialize_fn: DeserializeFn,
294 ) -> Self {
295 Self {
296 app,
297 path,
298 cache: defaults.clone().unwrap_or_default(),
299 defaults,
300 serialize_fn,
301 deserialize_fn,
302 }
303 }
304
305 pub fn save(&self) -> crate::Result<()> {
307 fs::create_dir_all(self.path.parent().expect("invalid store path"))?;
308
309 let bytes = (self.serialize_fn)(&self.cache).map_err(crate::Error::Serialize)?;
310 fs::write(&self.path, bytes)?;
311
312 Ok(())
313 }
314
315 pub fn load(&mut self) -> crate::Result<()> {
319 let bytes = fs::read(&self.path)?;
320 let entries = (self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?;
321
322 self.cache = self.defaults.clone().unwrap_or_default();
323 self.cache.extend(entries);
324
325 Ok(())
326 }
327
328 pub fn load_ignore_defaults(&mut self) -> crate::Result<()> {
330 let bytes = fs::read(&self.path)?;
331 self.cache = (self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?;
332 Ok(())
333 }
334
335 pub fn set(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) {
337 let key = key.into();
338 let value = value.into();
339 self.cache.insert(key.clone(), value.clone());
340 let _ = self.emit_change_event(&key, Some(&value));
341 }
342
343 pub fn get(&self, key: impl AsRef<str>) -> Option<&JsonValue> {
345 self.cache.get(key.as_ref())
346 }
347
348 pub fn has(&self, key: impl AsRef<str>) -> bool {
350 self.cache.contains_key(key.as_ref())
351 }
352
353 pub fn delete(&mut self, key: impl AsRef<str>) -> bool {
355 let flag = self.cache.remove(key.as_ref()).is_some();
356 if flag {
357 let _ = self.emit_change_event(key.as_ref(), None);
358 }
359 flag
360 }
361
362 pub fn clear(&mut self) {
366 let keys: Vec<String> = self.cache.keys().cloned().collect();
367 self.cache.clear();
368 for key in &keys {
369 let _ = self.emit_change_event(key, None);
370 }
371 }
372
373 pub fn reset(&mut self) {
377 if let Some(defaults) = &self.defaults {
378 for (key, value) in &self.cache {
379 if defaults.get(key) != Some(value) {
380 let _ = self.emit_change_event(key, defaults.get(key));
381 }
382 }
383 for (key, value) in defaults {
384 if !self.cache.contains_key(key) {
385 let _ = self.emit_change_event(key, Some(value));
386 }
387 }
388 self.cache.clone_from(defaults);
389 } else {
390 self.clear()
391 }
392 }
393
394 pub fn keys(&self) -> impl Iterator<Item = &String> {
396 self.cache.keys()
397 }
398
399 pub fn values(&self) -> impl Iterator<Item = &JsonValue> {
401 self.cache.values()
402 }
403
404 pub fn entries(&self) -> impl Iterator<Item = (&String, &JsonValue)> {
406 self.cache.iter()
407 }
408
409 pub fn len(&self) -> usize {
411 self.cache.len()
412 }
413
414 pub fn is_empty(&self) -> bool {
416 self.cache.is_empty()
417 }
418
419 fn emit_change_event(&self, key: &str, value: Option<&JsonValue>) -> crate::Result<()> {
420 let state = self.app.state::<StoreState>();
421 let stores = state.stores.read().unwrap();
422 let exists = value.is_some();
423 self.app.emit(
424 "store://change",
425 ChangePayload {
426 path: &self.path,
427 resource_id: stores.get(&self.path).copied(),
428 key,
429 value,
430 exists,
431 },
432 )?;
433 Ok(())
434 }
435}
436
437impl<R: Runtime> std::fmt::Debug for StoreInner<R> {
438 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439 f.debug_struct("Store")
440 .field("path", &self.path)
441 .field("cache", &self.cache)
442 .finish()
443 }
444}
445
446pub struct Store<R: Runtime> {
456 auto_save: Option<Duration>,
457 auto_save_debounce_sender: Arc<Mutex<Option<UnboundedSender<AutoSaveMessage>>>>,
458 store: Arc<Mutex<StoreInner<R>>>,
459}
460
461impl<R: Runtime> Resource for Store<R> {
462 fn close(self: Arc<Self>) {
463 let store = self.store.lock().unwrap();
464 let state = store.app.state::<StoreState>();
465 let mut stores = state.stores.write().unwrap();
466 stores.remove(&store.path);
467 }
468}
469
470impl<R: Runtime> Store<R> {
471 pub fn set(&self, key: impl Into<String>, value: impl Into<JsonValue>) {
480 self.store.lock().unwrap().set(key.into(), value.into());
481 let _ = self.trigger_auto_save();
482 }
483
484 pub fn get(&self, key: impl AsRef<str>) -> Option<JsonValue> {
486 self.store.lock().unwrap().get(key).cloned()
487 }
488
489 pub fn has(&self, key: impl AsRef<str>) -> bool {
491 self.store.lock().unwrap().has(key)
492 }
493
494 pub fn delete(&self, key: impl AsRef<str>) -> bool {
496 let deleted = self.store.lock().unwrap().delete(key);
497 if deleted {
498 let _ = self.trigger_auto_save();
499 }
500 deleted
501 }
502
503 pub fn clear(&self) {
507 self.store.lock().unwrap().clear();
508 let _ = self.trigger_auto_save();
509 }
510
511 pub fn reset(&self) {
515 self.store.lock().unwrap().reset();
516 let _ = self.trigger_auto_save();
517 }
518
519 pub fn keys(&self) -> Vec<String> {
521 self.store.lock().unwrap().keys().cloned().collect()
522 }
523
524 pub fn values(&self) -> Vec<JsonValue> {
526 self.store.lock().unwrap().values().cloned().collect()
527 }
528
529 pub fn entries(&self) -> Vec<(String, JsonValue)> {
531 self.store
532 .lock()
533 .unwrap()
534 .entries()
535 .map(|(k, v)| (k.to_owned(), v.to_owned()))
536 .collect()
537 }
538
539 pub fn length(&self) -> usize {
541 self.store.lock().unwrap().len()
542 }
543
544 pub fn is_empty(&self) -> bool {
546 self.store.lock().unwrap().is_empty()
547 }
548
549 pub fn reload(&self) -> crate::Result<()> {
558 self.store.lock().unwrap().load()
559 }
560
561 pub fn reload_ignore_defaults(&self) -> crate::Result<()> {
565 self.store.lock().unwrap().load_ignore_defaults()
566 }
567
568 pub fn save(&self) -> crate::Result<()> {
570 if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {
571 let _ = sender.send(AutoSaveMessage::Cancel);
572 }
573 self.store.lock().unwrap().save()
574 }
575
576 pub fn close_resource(&self) {
578 let store = self.store.lock().unwrap();
579 let app = store.app.clone();
580 let state = app.state::<StoreState>();
581 let stores = state.stores.read().unwrap();
582 if let Some(rid) = stores.get(&store.path).copied() {
583 drop(store);
584 drop(stores);
585 let _ = app.resources_table().close(rid);
586 }
587 }
588
589 fn trigger_auto_save(&self) -> crate::Result<()> {
590 let Some(auto_save_delay) = self.auto_save else {
591 return Ok(());
592 };
593 if auto_save_delay.is_zero() {
594 return self.save();
595 }
596 let mut auto_save_debounce_sender = self.auto_save_debounce_sender.lock().unwrap();
597 if let Some(ref sender) = *auto_save_debounce_sender {
598 let _ = sender.send(AutoSaveMessage::Reset);
599 return Ok(());
600 }
601 let (sender, mut receiver) = unbounded_channel();
602 auto_save_debounce_sender.replace(sender);
603 drop(auto_save_debounce_sender);
604 let store = self.store.clone();
605 let auto_save_debounce_sender = self.auto_save_debounce_sender.clone();
606 tauri::async_runtime::spawn(async move {
607 loop {
608 select! {
609 should_cancel = receiver.recv() => {
610 if matches!(should_cancel, Some(AutoSaveMessage::Cancel) | None) {
611 return;
612 }
613 }
614 _ = sleep(auto_save_delay) => {
615 auto_save_debounce_sender.lock().unwrap().take();
616 let _ = store.lock().unwrap().save();
617 return;
618 }
619 };
620 }
621 });
622 Ok(())
623 }
624
625 fn apply_pending_auto_save(&self) {
626 let auto_save_debounce_sender = self.auto_save_debounce_sender.lock().unwrap().take();
628 if let Some(sender) = auto_save_debounce_sender {
629 let _ = sender.send(AutoSaveMessage::Cancel);
630 let _ = self.save();
631 };
632 }
633}
634
635impl<R: Runtime> Drop for Store<R> {
636 fn drop(&mut self) {
637 self.apply_pending_auto_save();
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644 use serde_json::json;
645 use tauri::{
646 App,
647 test::{MockRuntime, mock_app},
648 };
649
650 fn temp_store_path(name: &str) -> PathBuf {
651 let dir =
652 std::env::temp_dir().join(format!("tauri-plugin-store-{}-{name}", std::process::id()));
653 fs::create_dir_all(&dir).unwrap();
654 dir.join("store.json")
655 }
656
657 fn store(app: &App<MockRuntime>, path: PathBuf) -> StoreInner<MockRuntime> {
658 let mut defaults = HashMap::new();
659 defaults.insert("default-key".to_string(), json!("default"));
660 defaults.insert("shared-key".to_string(), json!("default"));
661 StoreInner::new(
662 app.handle().clone(),
663 path,
664 Some(defaults),
665 crate::default_serialize,
666 crate::default_deserialize,
667 )
668 }
669
670 #[test]
671 fn load_resets_to_defaults_before_merging_the_on_disk_state() {
672 let app = mock_app();
673 let path = temp_store_path("load");
674 let mut store = store(&app, path.clone());
675 fs::write(&path, r#"{ "disk-key": "disk", "shared-key": "disk" }"#).unwrap();
676 store
678 .cache
679 .insert("memory-key".to_string(), json!("memory"));
680
681 store.load().unwrap();
682
683 assert_eq!(store.get("default-key"), Some(&json!("default")));
684 assert_eq!(store.get("disk-key"), Some(&json!("disk")));
685 assert_eq!(store.get("shared-key"), Some(&json!("disk")));
687 assert_eq!(store.get("memory-key"), None);
688
689 let _ = fs::remove_dir_all(path.parent().unwrap());
690 }
691
692 #[test]
693 fn load_ignore_defaults_matches_the_on_disk_state() {
694 let app = mock_app();
695 let path = temp_store_path("load-ignore-defaults");
696 let mut store = store(&app, path.clone());
697 fs::write(&path, r#"{ "disk-key": "disk" }"#).unwrap();
698 store
699 .cache
700 .insert("memory-key".to_string(), json!("memory"));
701
702 store.load_ignore_defaults().unwrap();
703
704 assert_eq!(store.get("disk-key"), Some(&json!("disk")));
705 assert_eq!(store.get("default-key"), None);
706 assert_eq!(store.get("memory-key"), None);
707
708 let _ = fs::remove_dir_all(path.parent().unwrap());
709 }
710}