1#![doc(
8 html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png",
9 html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
10)]
11
12pub use error::{Error, Result};
13use serde::{Deserialize, Serialize};
14pub use serde_json::Value as JsonValue;
15use std::{
16 collections::HashMap,
17 path::{Path, PathBuf},
18 sync::{Arc, Mutex},
19 time::Duration,
20};
21pub use store::{resolve_store_path, DeserializeFn, SerializeFn, Store, StoreBuilder};
22use tauri::{
23 plugin::{self, TauriPlugin},
24 AppHandle, Manager, ResourceId, RunEvent, Runtime, State,
25};
26
27mod error;
28mod store;
29
30#[derive(Serialize, Clone)]
31#[serde(rename_all = "camelCase")]
32struct ChangePayload<'a> {
33 path: &'a Path,
34 resource_id: Option<u32>,
35 key: &'a str,
36 value: Option<&'a JsonValue>,
37 exists: bool,
38}
39
40#[derive(Debug)]
41struct StoreState {
42 stores: Arc<Mutex<HashMap<PathBuf, ResourceId>>>,
43 serialize_fns: HashMap<String, SerializeFn>,
44 deserialize_fns: HashMap<String, DeserializeFn>,
45 default_serialize: SerializeFn,
46 default_deserialize: DeserializeFn,
47}
48
49#[derive(Serialize, Deserialize)]
50#[serde(untagged)]
51enum AutoSave {
52 DebounceDuration(u64),
53 Bool(bool),
54}
55
56fn builder<R: Runtime>(
57 app: AppHandle<R>,
58 store_state: State<'_, StoreState>,
59 path: PathBuf,
60 auto_save: Option<AutoSave>,
61 serialize_fn_name: Option<String>,
62 deserialize_fn_name: Option<String>,
63 create_new: bool,
64) -> Result<StoreBuilder<R>> {
65 let mut builder = app.store_builder(path);
66 if let Some(auto_save) = auto_save {
67 match auto_save {
68 AutoSave::DebounceDuration(duration) => {
69 builder = builder.auto_save(Duration::from_millis(duration));
70 }
71 AutoSave::Bool(false) => {
72 builder = builder.disable_auto_save();
73 }
74 _ => {}
75 }
76 }
77
78 if let Some(serialize_fn_name) = serialize_fn_name {
79 let serialize_fn = store_state
80 .serialize_fns
81 .get(&serialize_fn_name)
82 .ok_or_else(|| crate::Error::SerializeFunctionNotFound(serialize_fn_name))?;
83 builder = builder.serialize(*serialize_fn);
84 }
85
86 if let Some(deserialize_fn_name) = deserialize_fn_name {
87 let deserialize_fn = store_state
88 .deserialize_fns
89 .get(&deserialize_fn_name)
90 .ok_or_else(|| crate::Error::DeserializeFunctionNotFound(deserialize_fn_name))?;
91 builder = builder.deserialize(*deserialize_fn);
92 }
93
94 if create_new {
95 builder = builder.create_new();
96 }
97
98 Ok(builder)
99}
100
101#[tauri::command]
102async fn load<R: Runtime>(
103 app: AppHandle<R>,
104 store_state: State<'_, StoreState>,
105 path: PathBuf,
106 auto_save: Option<AutoSave>,
107 serialize_fn_name: Option<String>,
108 deserialize_fn_name: Option<String>,
109 create_new: Option<bool>,
110) -> Result<ResourceId> {
111 let builder = builder(
112 app,
113 store_state,
114 path,
115 auto_save,
116 serialize_fn_name,
117 deserialize_fn_name,
118 create_new.unwrap_or_default(),
119 )?;
120 let (_, rid) = builder.build_inner()?;
121 Ok(rid)
122}
123
124#[tauri::command]
125async fn get_store<R: Runtime>(
126 app: AppHandle<R>,
127 store_state: State<'_, StoreState>,
128 path: PathBuf,
129) -> Result<Option<ResourceId>> {
130 let stores = store_state.stores.lock().unwrap();
131 Ok(stores.get(&resolve_store_path(&app, path)?).copied())
132}
133
134#[tauri::command]
135async fn set<R: Runtime>(
136 app: AppHandle<R>,
137 rid: ResourceId,
138 key: String,
139 value: JsonValue,
140) -> Result<()> {
141 let store = app.resources_table().get::<Store<R>>(rid)?;
142 store.set(key, value);
143 Ok(())
144}
145
146#[tauri::command]
147async fn get<R: Runtime>(
148 app: AppHandle<R>,
149 rid: ResourceId,
150 key: String,
151) -> Result<(Option<JsonValue>, bool)> {
152 let store = app.resources_table().get::<Store<R>>(rid)?;
153 let value = store.get(key);
154 let exists = value.is_some();
155 Ok((value, exists))
156}
157
158#[tauri::command]
159async fn has<R: Runtime>(app: AppHandle<R>, rid: ResourceId, key: String) -> Result<bool> {
160 let store = app.resources_table().get::<Store<R>>(rid)?;
161 Ok(store.has(key))
162}
163
164#[tauri::command]
165async fn delete<R: Runtime>(app: AppHandle<R>, rid: ResourceId, key: String) -> Result<bool> {
166 let store = app.resources_table().get::<Store<R>>(rid)?;
167 Ok(store.delete(key))
168}
169
170#[tauri::command]
171async fn clear<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
172 let store = app.resources_table().get::<Store<R>>(rid)?;
173 store.clear();
174 Ok(())
175}
176
177#[tauri::command]
178async fn reset<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
179 let store = app.resources_table().get::<Store<R>>(rid)?;
180 store.reset();
181 Ok(())
182}
183
184#[tauri::command]
185async fn keys<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<Vec<String>> {
186 let store = app.resources_table().get::<Store<R>>(rid)?;
187 Ok(store.keys())
188}
189
190#[tauri::command]
191async fn values<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<Vec<JsonValue>> {
192 let store = app.resources_table().get::<Store<R>>(rid)?;
193 Ok(store.values())
194}
195
196#[tauri::command]
197async fn entries<R: Runtime>(
198 app: AppHandle<R>,
199 rid: ResourceId,
200) -> Result<Vec<(String, JsonValue)>> {
201 let store = app.resources_table().get::<Store<R>>(rid)?;
202 Ok(store.entries())
203}
204
205#[tauri::command]
206async fn length<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<usize> {
207 let store = app.resources_table().get::<Store<R>>(rid)?;
208 Ok(store.length())
209}
210
211#[tauri::command]
212async fn reload<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
213 let store = app.resources_table().get::<Store<R>>(rid)?;
214 store.reload()
215}
216
217#[tauri::command]
218async fn save<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
219 let store = app.resources_table().get::<Store<R>>(rid)?;
220 store.save()
221}
222
223pub trait StoreExt<R: Runtime> {
224 fn store(&self, path: impl AsRef<Path>) -> Result<Arc<Store<R>>>;
241 fn store_builder(&self, path: impl AsRef<Path>) -> StoreBuilder<R>;
260 fn get_store(&self, path: impl AsRef<Path>) -> Option<Arc<Store<R>>>;
287}
288
289impl<R: Runtime, T: Manager<R>> StoreExt<R> for T {
290 fn store(&self, path: impl AsRef<Path>) -> Result<Arc<Store<R>>> {
291 StoreBuilder::new(self.app_handle(), path).build()
292 }
293
294 fn store_builder(&self, path: impl AsRef<Path>) -> StoreBuilder<R> {
295 StoreBuilder::new(self.app_handle(), path)
296 }
297
298 fn get_store(&self, path: impl AsRef<Path>) -> Option<Arc<Store<R>>> {
299 let collection = self.state::<StoreState>();
300 let stores = collection.stores.lock().unwrap();
301 stores
302 .get(&resolve_store_path(self.app_handle(), path.as_ref()).ok()?)
303 .and_then(|rid| self.resources_table().get(*rid).ok())
304 }
305}
306
307fn default_serialize(
308 cache: &HashMap<String, JsonValue>,
309) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
310 Ok(serde_json::to_vec_pretty(&cache)?)
311}
312
313fn default_deserialize(
314 bytes: &[u8],
315) -> std::result::Result<HashMap<String, JsonValue>, Box<dyn std::error::Error + Send + Sync>> {
316 serde_json::from_slice(bytes).map_err(Into::into)
317}
318
319pub struct Builder {
320 serialize_fns: HashMap<String, SerializeFn>,
321 deserialize_fns: HashMap<String, DeserializeFn>,
322 default_serialize: SerializeFn,
323 default_deserialize: DeserializeFn,
324}
325
326impl Default for Builder {
327 fn default() -> Self {
328 Self {
329 serialize_fns: Default::default(),
330 deserialize_fns: Default::default(),
331 default_serialize,
332 default_deserialize,
333 }
334 }
335}
336
337impl Builder {
338 pub fn new() -> Self {
339 Self::default()
340 }
341
342 pub fn register_serialize_fn(mut self, name: String, serialize_fn: SerializeFn) -> Self {
361 self.serialize_fns.insert(name, serialize_fn);
362 self
363 }
364
365 pub fn register_deserialize_fn(mut self, name: String, deserialize_fn: DeserializeFn) -> Self {
367 self.deserialize_fns.insert(name, deserialize_fn);
368 self
369 }
370
371 pub fn default_serialize_fn(mut self, serialize_fn: SerializeFn) -> Self {
390 self.default_serialize = serialize_fn;
391 self
392 }
393
394 pub fn default_deserialize_fn(mut self, deserialize_fn: DeserializeFn) -> Self {
396 self.default_deserialize = deserialize_fn;
397 self
398 }
399
400 pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
413 plugin::Builder::new("store")
414 .invoke_handler(tauri::generate_handler![
415 load, get_store, set, get, has, delete, clear, reset, keys, values, length,
416 entries, reload, save,
417 ])
418 .setup(move |app_handle, _api| {
419 app_handle.manage(StoreState {
420 stores: Arc::new(Mutex::new(HashMap::new())),
421 serialize_fns: self.serialize_fns,
422 deserialize_fns: self.deserialize_fns,
423 default_serialize: self.default_serialize,
424 default_deserialize: self.default_deserialize,
425 });
426 Ok(())
427 })
428 .on_event(|app_handle, event| {
429 if let RunEvent::Exit = event {
430 let collection = app_handle.state::<StoreState>();
431 let stores = collection.stores.lock().unwrap();
432 for (path, rid) in stores.iter() {
433 if let Ok(store) = app_handle.resources_table().get::<Store<R>>(*rid) {
434 if let Err(err) = store.save() {
435 tracing::error!("failed to save store {path:?} with error {err:?}");
436 }
437 }
438 }
439 }
440 })
441 .build()
442 }
443}