refactor(store): more reworks

pull/1860/head
Tony 8 months ago
parent 7e5e34420b
commit 9811756e6c
No known key found for this signature in database
GPG Key ID: 34BDD3EA27824956

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(e){"use strict";var t,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}async function i(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(e):a?a.value:t.get(e)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,a,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return i("plugin:resources|close",{rid:this.rid})}}async function s(e,t,r){const n={kind:"Any"};return i("plugin:event|listen",{event:e,target:n,handler:a(t)}).then((t=>async()=>async function(e,t){await i("plugin:event|unlisten",{event:e,eventId:t})}(e,t)))}t=new WeakMap,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(r||(r={}));class o extends n{constructor(e,t){super(e),this.path=t}async set(e,t){await i("plugin:store|set",{rid:this.rid,key:e,value:t})}async get(e){return await i("plugin:store|get",{rid:this.rid,key:e})}async has(e){return await i("plugin:store|has",{rid:this.rid,key:e})}async delete(e){return await i("plugin:store|delete",{rid:this.rid,key:e})}async clear(){await i("plugin:store|clear",{rid:this.rid})}async reset(){await i("plugin:store|reset",{rid:this.rid})}async keys(){return await i("plugin:store|keys",{rid:this.rid})}async values(){return await i("plugin:store|values",{rid:this.rid})}async entries(){return await i("plugin:store|entries",{rid:this.rid})}async length(){return await i("plugin:store|length",{rid:this.rid})}async load(){await i("plugin:store|load",{rid:this.rid})}async save(){await i("plugin:store|save",{rid:this.rid})}async onKeyChange(e,t){return await s("store://change",(r=>{r.payload.path===this.path&&r.payload.key===e&&t(r.payload.value)}))}async onChange(e){return await s("store://change",(t=>{t.payload.path===this.path&&e(t.payload.key,t.payload.value)}))}}return e.Store=o,e.createStore=async function(e,t){const r=await i("plugin:store|create_store",{path:e,...t});return new o(r,e)},e}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(e){"use strict";var t,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}async function i(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(e):a?a.value:t.get(e)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,a,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return i("plugin:resources|close",{rid:this.rid})}}async function s(e,t,r){const n={kind:"Any"};return i("plugin:event|listen",{event:e,target:n,handler:a(t)}).then((t=>async()=>async function(e,t){await i("plugin:event|unlisten",{event:e,eventId:t})}(e,t)))}t=new WeakMap,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(r||(r={}));class o extends n{constructor(e,t){super(e),this.path=t}async set(e,t){await i("plugin:store|set",{rid:this.rid,key:e,value:t})}async get(e){return await i("plugin:store|get",{rid:this.rid,key:e})}async has(e){return await i("plugin:store|has",{rid:this.rid,key:e})}async delete(e){return await i("plugin:store|delete",{rid:this.rid,key:e})}async clear(){await i("plugin:store|clear",{rid:this.rid})}async reset(){await i("plugin:store|reset",{rid:this.rid})}async keys(){return await i("plugin:store|keys",{rid:this.rid})}async values(){return await i("plugin:store|values",{rid:this.rid})}async entries(){return await i("plugin:store|entries",{rid:this.rid})}async length(){return await i("plugin:store|length",{rid:this.rid})}async load(){await i("plugin:store|load",{rid:this.rid})}async save(){await i("plugin:store|save",{rid:this.rid})}async onKeyChange(e,t){return await s("store://change",(r=>{r.payload.path===this.path&&r.payload.key===e&&t(r.payload.value)}))}async onChange(e){return await s("store://change",(t=>{t.payload.path===this.path&&e(t.payload.key,t.payload.value)}))}}return e.Store=o,e.createStore=async function(e,t){const r=await i("plugin:store|create_store",{path:e,...t});return new o(r,e)},e.getStore=async function(e){const t=await i("plugin:store|get_store");return new o(t,e)},e}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}

@ -4,6 +4,7 @@
const COMMANDS: &[&str] = &[
"create_store",
"get_store",
"set",
"get",
"has",

@ -8,6 +8,7 @@
use std::time::Duration;
use serde_json::json;
use tauri::Listener;
use tauri_plugin_store::StoreExt;
mod app;
@ -22,13 +23,13 @@ fn main() {
.handle()
.store_builder("settings.json")
.auto_save(Duration::from_millis(100))
.build();
// If there are no saved settings yet, this will return an error so we ignore the return value.
let _ = store.load();
.build()
.unwrap();
app.share_store(store.clone());
app.listen("store://change", |event| {
dbg!(event);
});
let app_settings = AppSettings::load_from_store(&store);
match app_settings {
Ok(app_settings) => {
let theme = app_settings.theme;

@ -3,6 +3,7 @@
"version": "0.1.0",
"identifier": "com.tauri.app-settings-manager",
"build": {
"devUrl": "http://localhost:1420",
"frontendDist": "../dist"
},
"app": {

@ -35,7 +35,15 @@ export async function createStore(path: string, options?: StoreOptions) {
}
/**
* A lazy loaded key-value store persisted by the backend layer.
* @param path: Path of the store in the rust side
*/
export async function getStore(path: string) {
const resourceId = await invoke<number>('plugin:store|get_store')
return new Store(resourceId, path)
}
/**
* A key-value store persisted by the backend layer.
*/
export class Store extends Resource {
constructor(

@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-get-store"
description = "Enables the get_store command without any pre-configured scope."
commands.allow = ["get_store"]
[[permission]]
identifier = "deny-get-store"
description = "Denies the get_store command without any pre-configured scope."
commands.deny = ["get_store"]

@ -165,6 +165,32 @@ Denies the get command without any pre-configured scope.
<tr>
<td>
`store:allow-get-store`
</td>
<td>
Enables the get_store command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`store:deny-get-store`
</td>
<td>
Denies the get_store command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`store:allow-has`
</td>

@ -344,6 +344,16 @@
"type": "string",
"const": "deny-get"
},
{
"description": "Enables the get_store command without any pre-configured scope.",
"type": "string",
"const": "allow-get-store"
},
{
"description": "Denies the get_store command without any pre-configured scope.",
"type": "string",
"const": "deny-get-store"
},
{
"description": "Enables the has command without any pre-configured scope.",
"type": "string",

@ -21,9 +21,9 @@ pub enum Error {
/// IO error.
#[error(transparent)]
Io(#[from] std::io::Error),
/// Store not found
#[error("Store \"{0}\" not found")]
NotFound(PathBuf),
/// Store already exists
#[error("Store at \"{0}\" already exists")]
AlreadyExists(PathBuf),
/// Some Tauri API failed
#[error(transparent)]
Tauri(#[from] tauri::Error),

@ -18,13 +18,13 @@ pub use serde_json::Value as JsonValue;
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{Mutex, Weak},
sync::{Arc, Mutex, Weak},
time::Duration,
};
pub use store::{Store, StoreBuilder, StoreInner};
use tauri::{
plugin::{self, TauriPlugin},
AppHandle, Manager, ResourceId, RunEvent, Runtime, Webview,
AppHandle, Manager, ResourceId, RunEvent, Runtime,
};
mod error;
@ -38,138 +38,185 @@ struct ChangePayload<'a> {
}
pub struct StoreCollection<R: Runtime> {
stores: Mutex<HashMap<PathBuf, Weak<Mutex<StoreInner<R>>>>>,
// frozen: bool,
/// This weak pointer is always pointing to a real reference since we will remove it on drop
stores: Mutex<HashMap<PathBuf, (Weak<Store<R>>, Option<ResourceId>)>>,
}
#[tauri::command]
async fn create_store<R: Runtime>(
app: AppHandle<R>,
webview: Webview<R>,
path: PathBuf,
auto_save: Option<u64>,
) -> Result<ResourceId> {
let mut builder = app.store_builder(path);
let mut builder = app.store_builder(path.clone());
if let Some(auto_save) = auto_save {
builder = builder.auto_save(Duration::from_millis(auto_save));
}
let store = builder.build();
Ok(webview.resources_table().add(store))
let store = builder.build()?;
let rid = app.resources_table().add_arc(store);
let collection = app.state::<StoreCollection<R>>();
let mut stores = collection.stores.lock().unwrap();
if let Some((_, resource_id)) = stores.get_mut(&path) {
resource_id.replace(rid);
}
Ok(rid)
}
#[tauri::command]
async fn get_store<R: Runtime>(app: AppHandle<R>, path: PathBuf) -> Option<ResourceId> {
let collection = app.state::<StoreCollection<R>>();
let mut stores = collection.stores.lock().unwrap();
if let Some((store, resource_id)) = stores.get_mut(&path) {
let rid = if let Some(resource_id) = resource_id {
*resource_id
} else {
let rid = app.resources_table().add_arc(store.upgrade().unwrap());
resource_id.replace(rid);
rid
};
Some(rid)
} else {
None
}
}
#[tauri::command]
async fn set<R: Runtime>(
webview: Webview<R>,
app: AppHandle<R>,
rid: ResourceId,
key: String,
value: JsonValue,
) -> Result<()> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
let store = app.resources_table().get::<Store<R>>(rid)?;
store.set(key, value);
Ok(())
}
#[tauri::command]
async fn get<R: Runtime>(
webview: Webview<R>,
app: AppHandle<R>,
rid: ResourceId,
key: String,
) -> Result<Option<JsonValue>> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
let store = app.resources_table().get::<Store<R>>(rid)?;
Ok(store.get(key))
}
#[tauri::command]
async fn has<R: Runtime>(webview: Webview<R>, rid: ResourceId, key: String) -> Result<bool> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn has<R: Runtime>(app: AppHandle<R>, rid: ResourceId, key: String) -> Result<bool> {
let store = app.resources_table().get::<Store<R>>(rid)?;
Ok(store.has(key))
}
#[tauri::command]
async fn delete<R: Runtime>(webview: Webview<R>, rid: ResourceId, key: String) -> Result<bool> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn delete<R: Runtime>(app: AppHandle<R>, rid: ResourceId, key: String) -> Result<bool> {
let store = app.resources_table().get::<Store<R>>(rid)?;
Ok(store.delete(key))
}
#[tauri::command]
async fn clear<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Result<()> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn clear<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
let store = app.resources_table().get::<Store<R>>(rid)?;
store.clear();
Ok(())
}
#[tauri::command]
async fn reset<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Result<()> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn reset<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
let store = app.resources_table().get::<Store<R>>(rid)?;
store.reset();
Ok(())
}
#[tauri::command]
async fn keys<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Result<Vec<String>> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn keys<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<Vec<String>> {
let store = app.resources_table().get::<Store<R>>(rid)?;
Ok(store.keys())
}
#[tauri::command]
async fn values<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Result<Vec<JsonValue>> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn values<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<Vec<JsonValue>> {
let store = app.resources_table().get::<Store<R>>(rid)?;
Ok(store.values())
}
#[tauri::command]
async fn entries<R: Runtime>(
webview: Webview<R>,
app: AppHandle<R>,
rid: ResourceId,
) -> Result<Vec<(String, JsonValue)>> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
let store = app.resources_table().get::<Store<R>>(rid)?;
Ok(store.entries())
}
#[tauri::command]
async fn length<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Result<usize> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn length<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<usize> {
let store = app.resources_table().get::<Store<R>>(rid)?;
Ok(store.length())
}
#[tauri::command]
async fn load<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Result<()> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn load<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
let store = app.resources_table().get::<Store<R>>(rid)?;
store.load()
}
#[tauri::command]
async fn save<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Result<()> {
let store = webview.resources_table().get::<Store<R>>(rid)?;
async fn save<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
let store = app.resources_table().get::<Store<R>>(rid)?;
store.save()
}
pub trait StoreExt<R: Runtime> {
fn store(&self, path: impl AsRef<Path>) -> Store<R>;
fn create_store(&self, path: impl AsRef<Path>) -> Result<Arc<Store<R>>>;
fn store_builder(&self, path: impl AsRef<Path>) -> StoreBuilder<R>;
fn share_store(&self, store: Arc<Store<R>>);
fn get_store(&self, path: impl AsRef<Path>) -> Option<Arc<Store<R>>>;
}
impl<R: Runtime, T: Manager<R>> StoreExt<R> for T {
fn store(&self, path: impl AsRef<Path>) -> Store<R> {
fn create_store(&self, path: impl AsRef<Path>) -> Result<Arc<Store<R>>> {
StoreBuilder::new(self.app_handle(), path).build()
}
fn store_builder(&self, path: impl AsRef<Path>) -> StoreBuilder<R> {
StoreBuilder::new(self.app_handle(), path)
}
fn share_store(&self, store: Arc<Store<R>>) {
let collection = self.state::<StoreCollection<R>>();
let mut stores = collection.stores.lock().unwrap();
if let Some(path) = store.with_store(|inner_store| {
if stores.contains_key(&inner_store.path) {
None
} else {
Some(inner_store.path.clone())
}
}) {
let weak_store = Arc::downgrade(&store);
let rid = self.resources_table().add_arc(store);
stores.insert(path, (weak_store, Some(rid)));
}
}
fn get_store(&self, path: impl AsRef<Path>) -> Option<Arc<Store<R>>> {
let collection = self.state::<StoreCollection<R>>();
let stores = collection.stores.lock().unwrap();
stores
.get(path.as_ref())
.and_then(|(store, _)| store.upgrade())
}
}
// #[derive(Default)]
pub struct Builder<R: Runtime> {
stores: HashMap<PathBuf, Store<R>>,
// frozen: bool,
}
impl<R: Runtime> Default for Builder<R> {
fn default() -> Self {
Self {
stores: Default::default(),
// frozen: false,
}
}
}
@ -179,68 +226,6 @@ impl<R: Runtime> Builder<R> {
Self::default()
}
// /// Registers a store with the plugin.
// ///
// /// # Examples
// ///
// /// ```
// /// use tauri_plugin_store::{StoreBuilder, Builder};
// ///
// /// tauri::Builder::default()
// /// .setup(|app| {
// /// let store = StoreBuilder::new("store.bin").build(app.handle().clone());
// /// let builder = Builder::default().store(store);
// /// Ok(())
// /// });
// /// ```
// pub fn store(mut self, store: Store<R>) -> Self {
// self.stores.insert(store.path.clone(), store);
// self
// }
// /// Registers multiple stores with the plugin.
// ///
// /// # Examples
// ///
// /// ```
// /// use tauri_plugin_store::{StoreBuilder, Builder};
// ///
// /// tauri::Builder::default()
// /// .setup(|app| {
// /// let store = StoreBuilder::new("store.bin").build(app.handle().clone());
// /// let builder = Builder::default().stores([store]);
// /// Ok(())
// /// });
// /// ```
// pub fn stores<T: IntoIterator<Item = Store<R>>>(mut self, stores: T) -> Self {
// self.stores = stores
// .into_iter()
// .map(|store| (store.path.clone(), store))
// .collect();
// self
// }
// /// Freezes the collection.
// ///
// /// This causes requests for plugins that haven't been registered to fail
// ///
// /// # Examples
// ///
// /// ```
// /// use tauri_plugin_store::{StoreBuilder, Builder};
// ///
// /// tauri::Builder::default()
// /// .setup(|app| {
// /// let store = StoreBuilder::new("store.bin").build(app.handle().clone());
// /// app.handle().plugin(Builder::default().freeze().build());
// /// Ok(())
// /// });
// /// ```
// pub fn freeze(mut self) -> Self {
// self.frozen = true;
// self
// }
/// Builds the plugin.
///
/// # Examples
@ -249,7 +234,7 @@ impl<R: Runtime> Builder<R> {
/// tauri::Builder::default()
/// .plugin(tauri_plugin_store::Builder::default().build())
/// .setup(|app| {
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").build();
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").build()?;
/// Ok(())
/// });
/// ```
@ -257,6 +242,7 @@ impl<R: Runtime> Builder<R> {
plugin::Builder::new("store")
.invoke_handler(tauri::generate_handler![
create_store,
get_store,
set,
get,
has,
@ -282,23 +268,20 @@ impl<R: Runtime> Builder<R> {
app_handle.manage(StoreCollection::<R> {
stores: Mutex::new(HashMap::new()),
// frozen: self.frozen,
});
Ok(())
})
.on_event(|_app_handle, event| {
.on_event(|app_handle, event| {
if let RunEvent::Exit = event {
// let collection = app_handle.state::<StoreCollection<R>>();
// for store in collection.stores.lock().expect("mutex poisoned").values_mut() {
// if let Some(sender) = store.auto_save_debounce_sender.take() {
// let _ = sender.send(AutoSaveMessage::Cancel);
// }
// if let Err(err) = store.save() {
// eprintln!("failed to save store {:?} with error {:?}", store.path, err);
// }
// }
let collection = app_handle.state::<StoreCollection<R>>();
let stores = collection.stores.lock().unwrap();
for (path, (store, _)) in stores.iter() {
let store = store.upgrade().unwrap();
if let Err(err) = store.save() {
eprintln!("failed to save store {path:?} with error {err:?}");
}
}
}
})
.build()

@ -41,7 +41,6 @@ pub struct StoreBuilder<R: Runtime> {
app: AppHandle<R>,
path: PathBuf,
defaults: Option<HashMap<String, JsonValue>>,
cache: HashMap<String, JsonValue>,
serialize: SerializeFn,
deserialize: DeserializeFn,
auto_save: Option<Duration>,
@ -65,7 +64,6 @@ impl<R: Runtime> StoreBuilder<R> {
// Since Store.path is only exposed to the user in emit calls we may as well simplify it here already.
path: dunce::simplified(path.as_ref()).to_path_buf(),
defaults: None,
cache: Default::default(),
serialize: default_serialize,
deserialize: default_deserialize,
auto_save: None,
@ -84,12 +82,11 @@ impl<R: Runtime> StoreBuilder<R> {
///
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin")
/// .defaults(defaults)
/// .build();
/// .build()?;
/// Ok(())
/// });
/// ```
pub fn defaults(mut self, defaults: HashMap<String, JsonValue>) -> Self {
self.cache.clone_from(&defaults);
self.defaults = Some(defaults);
self
}
@ -103,14 +100,13 @@ impl<R: Runtime> StoreBuilder<R> {
/// .setup(|app| {
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin")
/// .default("foo".to_string(), "bar")
/// .build();
/// .build()?;
/// Ok(())
/// });
/// ```
pub fn default(mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
let key = key.into();
let value = value.into();
self.cache.insert(key.clone(), value.clone());
self.defaults
.get_or_insert(HashMap::new())
.insert(key, value);
@ -126,7 +122,7 @@ impl<R: Runtime> StoreBuilder<R> {
/// .setup(|app| {
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json")
/// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into))
/// .build();
/// .build()?;
/// Ok(())
/// });
/// ```
@ -144,7 +140,7 @@ impl<R: Runtime> StoreBuilder<R> {
/// .setup(|app| {
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json")
/// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into))
/// .build();
/// .build()?;
/// Ok(())
/// });
/// ```
@ -155,17 +151,14 @@ impl<R: Runtime> StoreBuilder<R> {
/// Auto save on modified with a debounce duration
///
/// Note: only works if this store is managed by the plugin (e.g. made using [`crate::with_store`] or inserted into [`crate::Builder`])
///
/// # Examples
/// ```
///
/// tauri::Builder::default()
/// .plugin(tauri_plugin_store::Builder::default().build())
/// .setup(|app| {
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json")
/// .auto_save(std::time::Duration::from_millis(100))
/// .build();
/// .build()?;
/// Ok(())
/// });
/// ```
@ -181,35 +174,35 @@ impl<R: Runtime> StoreBuilder<R> {
/// tauri::Builder::default()
/// .plugin(tauri_plugin_store::Builder::default().build())
/// .setup(|app| {
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build();
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build()?;
/// Ok(())
/// });
/// ```
pub fn build(self) -> Store<R> {
pub fn build(self) -> crate::Result<Arc<Store<R>>> {
let collection = self.app.state::<StoreCollection<R>>();
let mut stores = collection.stores.lock().unwrap();
let store = stores
.get(&self.path)
.and_then(|store| store.upgrade())
.unwrap_or_else(|| {
let mut store = StoreInner::new(self.app.clone(), self.path.clone());
let _ = store.load(self.deserialize);
let store = Arc::new(Mutex::new(store));
stores.insert(
self.path.clone(),
Arc::<Mutex<StoreInner<R>>>::downgrade(&store),
);
store
});
drop(stores);
Store {
if stores.contains_key(&self.path) {
return Err(crate::Error::AlreadyExists(self.path));
}
let mut store_inner = StoreInner::new(self.app.clone(), self.path.clone());
if let Some(defaults) = &self.defaults {
store_inner.cache.clone_from(defaults);
}
let _ = store_inner.load(self.deserialize);
let store = Store {
defaults: self.defaults,
serialize: self.serialize,
deserialize: self.deserialize,
auto_save: self.auto_save,
auto_save_debounce_sender: Arc::new(Mutex::new(None)),
store,
}
store: Arc::new(Mutex::new(store_inner)),
};
let store = Arc::new(store);
stores.insert(self.path, (Arc::downgrade(&store), None));
Ok(store)
}
}
@ -372,10 +365,7 @@ pub struct Store<R: Runtime> {
impl<R: Runtime> Resource for Store<R> {}
impl<R: Runtime> Store<R> {
pub fn with_store<T>(
&self,
f: impl FnOnce(&mut StoreInner<R>) -> crate::Result<T>,
) -> crate::Result<T> {
pub fn with_store<T>(&self, f: impl FnOnce(&mut StoreInner<R>) -> T) -> T {
let mut store = self.store.lock().unwrap();
f(&mut store)
}
@ -480,3 +470,17 @@ impl<R: Runtime> Store<R> {
Ok(())
}
}
impl<R: Runtime> Drop for Store<R> {
fn drop(&mut self) {
let store = self.store.lock().unwrap();
// Cancel and save if auto save is pending
if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {
let _ = sender.send(AutoSaveMessage::Cancel);
let _ = store.save(self.serialize);
};
let collection = store.app.state::<StoreCollection<R>>();
let mut stores = collection.stores.lock().unwrap();
stores.remove(&store.path);
}
}

Loading…
Cancel
Save