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::{path::BaseDirectory, AppHandle, Emitter, Manager, Resource, ResourceId, Runtime};
15use tokio::{
16 select,
17 sync::mpsc::{unbounded_channel, UnboundedSender},
18 time::sleep,
19};
20
21pub type SerializeFn =
22 fn(&HashMap<String, JsonValue>) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>>;
23pub type DeserializeFn =
24 fn(&[u8]) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error + Send + Sync>>;
25
26pub fn resolve_store_path<R: Runtime>(
27 app: &AppHandle<R>,
28 path: impl AsRef<Path>,
29) -> crate::Result<PathBuf> {
30 Ok(dunce::simplified(&app.path().resolve(path, BaseDirectory::AppData)?).to_path_buf())
31}
32
33pub struct StoreBuilder<R: Runtime> {
35 app: AppHandle<R>,
36 path: PathBuf,
37 defaults: Option<HashMap<String, JsonValue>>,
38 serialize_fn: SerializeFn,
39 deserialize_fn: DeserializeFn,
40 auto_save: Option<Duration>,
41 create_new: bool,
42}
43
44impl<R: Runtime> StoreBuilder<R> {
45 pub fn new<M: Manager<R>, P: AsRef<Path>>(manager: &M, path: P) -> Self {
57 let app = manager.app_handle().clone();
58 let state = app.state::<StoreState>();
59 let serialize_fn = state.default_serialize;
60 let deserialize_fn = state.default_deserialize;
61 Self {
62 app,
63 path: path.as_ref().to_path_buf(),
64 defaults: None,
65 serialize_fn,
66 deserialize_fn,
67 auto_save: Some(Duration::from_millis(100)),
68 create_new: false,
69 }
70 }
71
72 pub fn defaults(mut self, defaults: HashMap<String, JsonValue>) -> Self {
89 self.defaults = Some(defaults);
90 self
91 }
92
93 pub fn default(mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
107 let key = key.into();
108 let value = value.into();
109 self.defaults
110 .get_or_insert(HashMap::new())
111 .insert(key, value);
112 self
113 }
114
115 pub fn serialize(mut self, serialize: SerializeFn) -> Self {
129 self.serialize_fn = serialize;
130 self
131 }
132
133 pub fn deserialize(mut self, deserialize: DeserializeFn) -> Self {
147 self.deserialize_fn = deserialize;
148 self
149 }
150
151 pub fn auto_save(mut self, debounce_duration: Duration) -> Self {
165 self.auto_save = Some(debounce_duration);
166 self
167 }
168
169 pub fn disable_auto_save(mut self) -> Self {
171 self.auto_save = None;
172 self
173 }
174
175 pub fn create_new(mut self) -> Self {
177 self.create_new = true;
178 self
179 }
180
181 pub(crate) fn build_inner(mut self) -> crate::Result<(Arc<Store<R>>, ResourceId)> {
182 let stores = self.app.state::<StoreState>().stores.clone();
183 let mut stores = stores.lock().unwrap();
184
185 self.path = resolve_store_path(&self.app, self.path)?;
186
187 if self.create_new {
188 if let Some(rid) = stores.remove(&self.path) {
189 let _ = self.app.resources_table().take::<Store<R>>(rid);
190 }
191 } else if let Some(rid) = stores.get(&self.path) {
192 return Ok((self.app.resources_table().get(*rid).unwrap(), *rid));
193 }
194
195 let mut store_inner = StoreInner::new(
200 self.app.clone(),
201 self.path.clone(),
202 self.defaults.take(),
203 self.serialize_fn,
204 self.deserialize_fn,
205 );
206
207 if !self.create_new {
208 let _ = store_inner.load();
209 }
210
211 let store = Store {
212 auto_save: self.auto_save,
213 auto_save_debounce_sender: Arc::new(Mutex::new(None)),
214 store: Arc::new(Mutex::new(store_inner)),
215 };
216
217 let store = Arc::new(store);
218 let rid = self.app.resources_table().add_arc(store.clone());
219 stores.insert(self.path, rid);
220
221 Ok((store, rid))
222 }
223
224 pub fn build(self) -> crate::Result<Arc<Store<R>>> {
238 let (store, _) = self.build_inner()?;
239 Ok(store)
240 }
241}
242
243enum AutoSaveMessage {
244 Reset,
245 Cancel,
246}
247
248#[derive(Clone)]
249struct StoreInner<R: Runtime> {
250 app: AppHandle<R>,
251 path: PathBuf,
252 cache: HashMap<String, JsonValue>,
253 defaults: Option<HashMap<String, JsonValue>>,
254 serialize_fn: SerializeFn,
255 deserialize_fn: DeserializeFn,
256}
257
258impl<R: Runtime> StoreInner<R> {
259 fn new(
260 app: AppHandle<R>,
261 path: PathBuf,
262 defaults: Option<HashMap<String, JsonValue>>,
263 serialize_fn: SerializeFn,
264 deserialize_fn: DeserializeFn,
265 ) -> Self {
266 Self {
267 app,
268 path,
269 cache: defaults.clone().unwrap_or_default(),
270 defaults,
271 serialize_fn,
272 deserialize_fn,
273 }
274 }
275
276 pub fn save(&self) -> crate::Result<()> {
278 fs::create_dir_all(self.path.parent().expect("invalid store path"))?;
279
280 let bytes = (self.serialize_fn)(&self.cache).map_err(crate::Error::Serialize)?;
281 fs::write(&self.path, bytes)?;
282
283 Ok(())
284 }
285
286 pub fn load(&mut self) -> crate::Result<()> {
288 let bytes = fs::read(&self.path)?;
289
290 self.cache
291 .extend((self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?);
292
293 Ok(())
294 }
295
296 pub fn set(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) {
298 let key = key.into();
299 let value = value.into();
300 self.cache.insert(key.clone(), value.clone());
301 let _ = self.emit_change_event(&key, Some(&value));
302 }
303
304 pub fn get(&self, key: impl AsRef<str>) -> Option<&JsonValue> {
306 self.cache.get(key.as_ref())
307 }
308
309 pub fn has(&self, key: impl AsRef<str>) -> bool {
311 self.cache.contains_key(key.as_ref())
312 }
313
314 pub fn delete(&mut self, key: impl AsRef<str>) -> bool {
316 let flag = self.cache.remove(key.as_ref()).is_some();
317 if flag {
318 let _ = self.emit_change_event(key.as_ref(), None);
319 }
320 flag
321 }
322
323 pub fn clear(&mut self) {
327 let keys: Vec<String> = self.cache.keys().cloned().collect();
328 self.cache.clear();
329 for key in &keys {
330 let _ = self.emit_change_event(key, None);
331 }
332 }
333
334 pub fn reset(&mut self) {
338 if let Some(defaults) = &self.defaults {
339 for (key, value) in &self.cache {
340 if defaults.get(key) != Some(value) {
341 let _ = self.emit_change_event(key, defaults.get(key));
342 }
343 }
344 for (key, value) in defaults {
345 if !self.cache.contains_key(key) {
346 let _ = self.emit_change_event(key, Some(value));
347 }
348 }
349 self.cache.clone_from(defaults);
350 } else {
351 self.clear()
352 }
353 }
354
355 pub fn keys(&self) -> impl Iterator<Item = &String> {
357 self.cache.keys()
358 }
359
360 pub fn values(&self) -> impl Iterator<Item = &JsonValue> {
362 self.cache.values()
363 }
364
365 pub fn entries(&self) -> impl Iterator<Item = (&String, &JsonValue)> {
367 self.cache.iter()
368 }
369
370 pub fn len(&self) -> usize {
372 self.cache.len()
373 }
374
375 pub fn is_empty(&self) -> bool {
377 self.cache.is_empty()
378 }
379
380 fn emit_change_event(&self, key: &str, value: Option<&JsonValue>) -> crate::Result<()> {
381 let state = self.app.state::<StoreState>();
382 let stores = state.stores.lock().unwrap();
383 let exists = value.is_some();
384 self.app.emit(
385 "store://change",
386 ChangePayload {
387 path: &self.path,
388 resource_id: stores.get(&self.path).copied(),
389 key,
390 value,
391 exists,
392 },
393 )?;
394 Ok(())
395 }
396}
397
398impl<R: Runtime> std::fmt::Debug for StoreInner<R> {
399 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
400 f.debug_struct("Store")
401 .field("path", &self.path)
402 .field("cache", &self.cache)
403 .finish()
404 }
405}
406
407pub struct Store<R: Runtime> {
408 auto_save: Option<Duration>,
409 auto_save_debounce_sender: Arc<Mutex<Option<UnboundedSender<AutoSaveMessage>>>>,
410 store: Arc<Mutex<StoreInner<R>>>,
411}
412
413impl<R: Runtime> Resource for Store<R> {
414 fn close(self: Arc<Self>) {
415 let store = self.store.lock().unwrap();
416 let state = store.app.state::<StoreState>();
417 let mut stores = state.stores.lock().unwrap();
418 stores.remove(&store.path);
419 }
420}
421
422impl<R: Runtime> Store<R> {
423 pub fn set(&self, key: impl Into<String>, value: impl Into<JsonValue>) {
432 self.store.lock().unwrap().set(key.into(), value.into());
433 let _ = self.trigger_auto_save();
434 }
435
436 pub fn get(&self, key: impl AsRef<str>) -> Option<JsonValue> {
438 self.store.lock().unwrap().get(key).cloned()
439 }
440
441 pub fn has(&self, key: impl AsRef<str>) -> bool {
443 self.store.lock().unwrap().has(key)
444 }
445
446 pub fn delete(&self, key: impl AsRef<str>) -> bool {
448 let deleted = self.store.lock().unwrap().delete(key);
449 if deleted {
450 let _ = self.trigger_auto_save();
451 }
452 deleted
453 }
454
455 pub fn clear(&self) {
459 self.store.lock().unwrap().clear();
460 let _ = self.trigger_auto_save();
461 }
462
463 pub fn reset(&self) {
467 self.store.lock().unwrap().reset();
468 let _ = self.trigger_auto_save();
469 }
470
471 pub fn keys(&self) -> Vec<String> {
473 self.store.lock().unwrap().keys().cloned().collect()
474 }
475
476 pub fn values(&self) -> Vec<JsonValue> {
478 self.store.lock().unwrap().values().cloned().collect()
479 }
480
481 pub fn entries(&self) -> Vec<(String, JsonValue)> {
483 self.store
484 .lock()
485 .unwrap()
486 .entries()
487 .map(|(k, v)| (k.to_owned(), v.to_owned()))
488 .collect()
489 }
490
491 pub fn length(&self) -> usize {
493 self.store.lock().unwrap().len()
494 }
495
496 pub fn is_empty(&self) -> bool {
498 self.store.lock().unwrap().is_empty()
499 }
500
501 pub fn reload(&self) -> crate::Result<()> {
503 self.store.lock().unwrap().load()
504 }
505
506 pub fn save(&self) -> crate::Result<()> {
508 if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {
509 let _ = sender.send(AutoSaveMessage::Cancel);
510 }
511 self.store.lock().unwrap().save()
512 }
513
514 pub fn close_resource(&self) {
516 let store = self.store.lock().unwrap();
517 let app = store.app.clone();
518 let state = app.state::<StoreState>();
519 let stores = state.stores.lock().unwrap();
520 if let Some(rid) = stores.get(&store.path).copied() {
521 drop(store);
522 drop(stores);
523 let _ = app.resources_table().close(rid);
524 }
525 }
526
527 fn trigger_auto_save(&self) -> crate::Result<()> {
528 let Some(auto_save_delay) = self.auto_save else {
529 return Ok(());
530 };
531 if auto_save_delay.is_zero() {
532 return self.save();
533 }
534 let mut auto_save_debounce_sender = self.auto_save_debounce_sender.lock().unwrap();
535 if let Some(ref sender) = *auto_save_debounce_sender {
536 let _ = sender.send(AutoSaveMessage::Reset);
537 return Ok(());
538 }
539 let (sender, mut receiver) = unbounded_channel();
540 auto_save_debounce_sender.replace(sender);
541 drop(auto_save_debounce_sender);
542 let store = self.store.clone();
543 let auto_save_debounce_sender = self.auto_save_debounce_sender.clone();
544 tauri::async_runtime::spawn(async move {
545 loop {
546 select! {
547 should_cancel = receiver.recv() => {
548 if matches!(should_cancel, Some(AutoSaveMessage::Cancel) | None) {
549 return;
550 }
551 }
552 _ = sleep(auto_save_delay) => {
553 auto_save_debounce_sender.lock().unwrap().take();
554 let _ = store.lock().unwrap().save();
555 return;
556 }
557 };
558 }
559 });
560 Ok(())
561 }
562
563 fn apply_pending_auto_save(&self) {
564 if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {
566 let _ = sender.send(AutoSaveMessage::Cancel);
567 let _ = self.save();
568 };
569 }
570}
571
572impl<R: Runtime> Drop for Store<R> {
573 fn drop(&mut self) {
574 self.apply_pending_auto_save();
575 }
576}