@ -2,19 +2,26 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
use crate ::{ ChangePayload , Error } ;
use crate ::{ ChangePayload , StoreCollection } ;
use serde_json ::Value as JsonValue ;
use serde_json ::Value as JsonValue ;
use std ::{
use std ::{
collections ::HashMap ,
collections ::HashMap ,
fs ::{ create_dir_all , read , File } ,
fs ::{ create_dir_all , read , File } ,
io ::Write ,
io ::Write ,
path ::{ Path , PathBuf } ,
path ::{ Path , PathBuf } ,
sync ::{ Arc , Mutex } ,
time ::Duration ,
} ;
use tauri ::{ AppHandle , Emitter , Manager , Resource , Runtime } ;
use tokio ::{
select ,
sync ::mpsc ::{ unbounded_channel , UnboundedSender } ,
time ::sleep ,
} ;
} ;
use tauri ::{ AppHandle , Emitter , Manager , Runtime } ;
type SerializeFn =
type SerializeFn =
fn ( & HashMap < String , JsonValue > ) -> Result < Vec < u8 > , Box < dyn std ::error ::Error + Send + Sync > > ;
fn ( & HashMap < String , JsonValue > ) -> Result < Vec < u8 > , Box < dyn std ::error ::Error + Send + Sync > > ;
type DeserializeFn =
pub ( crate ) type DeserializeFn =
fn ( & [ u8 ] ) -> Result < HashMap < String , JsonValue > , Box < dyn std ::error ::Error + Send + Sync > > ;
fn ( & [ u8 ] ) -> Result < HashMap < String , JsonValue > , Box < dyn std ::error ::Error + Send + Sync > > ;
fn default_serialize (
fn default_serialize (
@ -30,35 +37,38 @@ fn default_deserialize(
}
}
/// Builds a [`Store`]
/// Builds a [`Store`]
pub struct StoreBuilder {
pub struct StoreBuilder < R : Runtime > {
app : AppHandle < R > ,
path : PathBuf ,
path : PathBuf ,
defaults : Option < HashMap < String , JsonValue > > ,
defaults : Option < HashMap < String , JsonValue > > ,
cache : HashMap < String , JsonValue > ,
cache : HashMap < String , JsonValue > ,
serialize : SerializeFn ,
serialize : SerializeFn ,
deserialize : DeserializeFn ,
deserialize : DeserializeFn ,
auto_save : Option < Duration > ,
}
}
impl StoreBuilder {
impl < R : Runtime > StoreBuilder < R > {
/// Creates a new [`StoreBuilder`].
/// Creates a new [`StoreBuilder`].
///
///
/// # Examples
/// # Examples
/// ```
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// tauri::Builder::default()
/// use tauri_plugin_store::StoreBuilder;
/// .plugin(tauri_plugin_store::Builder::default().build())
///
/// .setup(|app| {
/// let builder = StoreBuilder::<tauri::Wry>::new("store.bin");
/// let builder = tauri_plugin_store::StoreBuilder::new(app, "store.bin");
///
/// Ok(())
/// # Ok(())
/// });
/// # }
/// ```
/// ```
pub fn new < P: AsRef < Path > > ( path : P ) -> Self {
pub fn new < M: Manager < R > , P: AsRef < Path > > ( manager : & M , path : P ) -> Self {
Self {
Self {
app : manager . app_handle ( ) . clone ( ) ,
// Since Store.path is only exposed to the user in emit calls we may as well simplify it here already.
// 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 ( ) ,
path : dunce ::simplified ( path . as_ref ( ) ) . to_path_buf ( ) ,
defaults : None ,
defaults : None ,
cache : Default ::default ( ) ,
cache : Default ::default ( ) ,
serialize : default_serialize ,
serialize : default_serialize ,
deserialize : default_deserialize ,
deserialize : default_deserialize ,
auto_save : None ,
}
}
}
}
@ -66,19 +76,18 @@ impl StoreBuilder {
///
///
/// # Examples
/// # Examples
/// ```
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// tauri::Builder::default()
/// use tauri_plugin_store::StoreBuilder;
/// .plugin(tauri_plugin_store::Builder::default().build())
/// use std::collections::HashMap;
/// .setup(|app| {
///
/// let mut defaults = std::collections::HashMap::new();
/// let mut defaults = HashMap::new();
/// defaults.insert("foo".to_string(), "bar".into());
///
/// defaults.insert("foo".to_string(), "bar".into());
///
/// let builder = StoreBuilder::<tauri::Wry>::new("store.bin")
/// .defaults(defaults);
///
///
/// # Ok(())
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin")
/// # }
/// .defaults(defaults)
/// .build();
/// Ok(())
/// });
/// ```
pub fn defaults ( mut self , defaults : HashMap < String , JsonValue > ) -> Self {
pub fn defaults ( mut self , defaults : HashMap < String , JsonValue > ) -> Self {
self . cache . clone_from ( & defaults ) ;
self . cache . clone_from ( & defaults ) ;
self . defaults = Some ( defaults ) ;
self . defaults = Some ( defaults ) ;
@ -89,15 +98,18 @@ impl StoreBuilder {
///
///
/// # Examples
/// # Examples
/// ```
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// tauri::Builder::default()
/// use tauri_plugin_store::StoreBuilder;
/// .plugin(tauri_plugin_store::Builder::default().build())
///
/// .setup(|app| {
/// let builder = StoreBuilder::<tauri::Wry>::new("store.bin")
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin")
/// .default("foo".to_string(), "bar".into());
/// .default("foo".to_string(), "bar")
///
/// .build();
/// # Ok(())
/// Ok(())
/// # }
/// });
pub fn default ( mut self , key : String , value : JsonValue ) -> Self {
/// ```
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 . cache . insert ( key . clone ( ) , value . clone ( ) ) ;
self . defaults
self . defaults
. get_or_insert ( HashMap ::new ( ) )
. get_or_insert ( HashMap ::new ( ) )
@ -109,14 +121,15 @@ impl StoreBuilder {
///
///
/// # Examples
/// # Examples
/// ```
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// tauri::Builder::default()
/// use tauri_plugin_store::StoreBuilder;
/// .plugin(tauri_plugin_store::Builder::default().build())
///
/// .setup(|app| {
/// let builder = StoreBuilder::<tauri::Wry>::new("store.json")
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json")
/// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into));
/// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into))
///
/// .build();
/// # Ok(())
/// Ok(())
/// # }
/// });
/// ```
pub fn serialize ( mut self , serialize : SerializeFn ) -> Self {
pub fn serialize ( mut self , serialize : SerializeFn ) -> Self {
self . serialize = serialize ;
self . serialize = serialize ;
self
self
@ -126,53 +139,102 @@ impl StoreBuilder {
///
///
/// # Examples
/// # Examples
/// ```
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// tauri::Builder::default()
/// use tauri_plugin_store::StoreBuilder;
/// .plugin(tauri_plugin_store::Builder::default().build())
///
/// .setup(|app| {
/// let builder = StoreBuilder::<tauri::Wry>::new("store.json")
/// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json")
/// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into));
/// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into))
///
/// .build();
/// # Ok(())
/// Ok(())
/// # }
/// });
/// ```
pub fn deserialize ( mut self , deserialize : DeserializeFn ) -> Self {
pub fn deserialize ( mut self , deserialize : DeserializeFn ) -> Self {
self . deserialize = deserialize ;
self . deserialize = deserialize ;
self
self
}
}
/// 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();
/// Ok(())
/// });
/// ```
pub fn auto_save ( mut self , debounce_duration : Duration ) -> Self {
self . auto_save = Some ( debounce_duration ) ;
self
}
/// Builds the [`Store`].
/// Builds the [`Store`].
///
///
/// # Examples
/// # Examples
/// ```
/// ```
/// tauri::Builder::default()
/// tauri::Builder::default()
/// .plugin(tauri_plugin_store::Builder::default().build())
/// .setup(|app| {
/// .setup(|app| {
/// let store = tauri_plugin_store::StoreBuilder::new("store.json").build(app.handle().clone());
/// let store = tauri_plugin_store::StoreBuilder::new( app, "store.json").build();
/// Ok(())
/// Ok(())
/// });
/// });
/// ```
/// ```
pub fn build < R : Runtime > ( self , app : AppHandle < R > ) -> Store < R > {
pub fn build ( self ) -> 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 {
Store {
app ,
path : self . path ,
defaults : self . defaults ,
defaults : self . defaults ,
cache : self . cache ,
serialize : self . serialize ,
serialize : self . serialize ,
deserialize : self . deserialize ,
deserialize : self . deserialize ,
auto_save : self . auto_save ,
auto_save_debounce_sender : Arc ::new ( Mutex ::new ( None ) ) ,
store ,
}
}
}
}
}
}
pub ( crate ) enum AutoSaveMessage {
Reset ,
Cancel ,
}
#[ derive(Clone) ]
#[ derive(Clone) ]
pub struct Store < R : Runtime > {
pub struct Store Inner < R : Runtime > {
pub ( crate ) app : AppHandle < R > ,
pub ( crate ) app : AppHandle < R > ,
pub ( crate ) path : PathBuf ,
pub ( crate ) path : PathBuf ,
defaults : Option < HashMap < String , JsonValue > > ,
pub ( crate ) cache : HashMap < String , JsonValue > ,
pub ( crate ) cache : HashMap < String , JsonValue > ,
pub ( crate ) serialize : SerializeFn ,
pub ( crate ) deserialize : DeserializeFn ,
}
}
impl < R : Runtime > Store < R > {
impl < R : Runtime > StoreInner < R > {
pub fn save ( & self ) -> Result < ( ) , Error > {
pub fn new ( app : AppHandle < R > , path : PathBuf ) -> Self {
Self {
app ,
path ,
cache : HashMap ::new ( ) ,
}
}
pub fn save ( & self , serialize_fn : SerializeFn ) -> crate ::Result < ( ) > {
let app_dir = self
let app_dir = self
. app
. app
. path ( )
. path ( )
@ -182,7 +244,7 @@ impl<R: Runtime> Store<R> {
create_dir_all ( store_path . parent ( ) . expect ( "invalid store path" ) ) ? ;
create_dir_all ( store_path . parent ( ) . expect ( "invalid store path" ) ) ? ;
let bytes = ( self . serialize ) ( & self . cache ) . map_err ( Error ::Serialize ) ? ;
let bytes = serialize_fn ( & self . cache ) . map_err ( crate ::Error ::Serialize ) ? ;
let mut f = File ::create ( & store_path ) ? ;
let mut f = File ::create ( & store_path ) ? ;
f . write_all ( & bytes ) ? ;
f . write_all ( & bytes ) ? ;
@ -190,7 +252,7 @@ impl<R: Runtime> Store<R> {
}
}
/// Update the store from the on-disk state
/// Update the store from the on-disk state
pub fn load ( & mut self ) -> Result < ( ) , Error > {
pub fn load ( & mut self , deserialize_fn : DeserializeFn ) -> crate ::Result < ( ) > {
let app_dir = self
let app_dir = self
. app
. app
. path ( )
. path ( )
@ -201,23 +263,16 @@ impl<R: Runtime> Store<R> {
let bytes = read ( store_path ) ? ;
let bytes = read ( store_path ) ? ;
self . cache
self . cache
. extend ( ( self . deserialize ) ( & bytes ) . map_err ( Error ::Deserialize ) ? ) ;
. extend ( deserialize_fn ( & bytes ) . map_err ( crate ::Error ::Deserialize ) ? ) ;
Ok ( ( ) )
Ok ( ( ) )
}
}
pub fn insert ( & mut self , key : String , value : JsonValue ) -> Result < ( ) , Error > {
pub fn insert ( & mut self , key : impl Into < String > , value : impl Into < JsonValue > ) {
let key = key . into ( ) ;
let value = value . into ( ) ;
self . cache . insert ( key . clone ( ) , value . clone ( ) ) ;
self . cache . insert ( key . clone ( ) , value . clone ( ) ) ;
self . app . emit (
let _ = self . emit_change_event ( & key , & value ) ;
"store://change" ,
ChangePayload {
path : & self . path ,
key : & key ,
value : & value ,
} ,
) ? ;
Ok ( ( ) )
}
}
pub fn get ( & self , key : impl AsRef < str > ) -> Option < & JsonValue > {
pub fn get ( & self , key : impl AsRef < str > ) -> Option < & JsonValue > {
@ -228,57 +283,36 @@ impl<R: Runtime> Store<R> {
self . cache . contains_key ( key . as_ref ( ) )
self . cache . contains_key ( key . as_ref ( ) )
}
}
pub fn delete ( & mut self , key : impl AsRef < str > ) -> Result < bool , Error > {
pub fn delete ( & mut self , key : impl AsRef < str > ) -> bool {
let flag = self . cache . remove ( key . as_ref ( ) ) . is_some ( ) ;
let flag = self . cache . remove ( key . as_ref ( ) ) . is_some ( ) ;
if flag {
if flag {
self . app . emit (
let _ = self . emit_change_event ( key . as_ref ( ) , & JsonValue ::Null ) ;
"store://change" ,
ChangePayload {
path : & self . path ,
key : key . as_ref ( ) ,
value : & JsonValue ::Null ,
} ,
) ? ;
}
}
Ok ( flag )
flag
}
}
pub fn clear ( & mut self ) -> Result < ( ) , Error > {
pub fn clear ( & mut self ) {
let keys : Vec < String > = self . cache . keys ( ) . cloned ( ) . collect ( ) ;
let keys : Vec < String > = self . cache . keys ( ) . cloned ( ) . collect ( ) ;
self . cache . clear ( ) ;
self . cache . clear ( ) ;
for key in keys {
for key in & keys {
self . app . emit (
let _ = self . emit_change_event ( key , & JsonValue ::Null ) ;
"store://change" ,
ChangePayload {
path : & self . path ,
key : & key ,
value : & JsonValue ::Null ,
} ,
) ? ;
}
}
Ok ( ( ) )
}
}
pub fn reset ( & mut self ) -> Result < ( ) , Error > {
pub fn reset ( & mut self , defaults : & Option < HashMap < String , JsonValue > > ) {
let has_defaults = self . defaults . is_some ( ) ;
if let Some ( defaults ) = & defaults {
for ( key , value ) in & self . cache {
if has_defaults {
if defaults . get ( key ) ! = Some ( value ) {
if let Some ( defaults ) = & self . defaults {
let _ =
for ( key , value ) in & self . cache {
self . emit_change_event ( key , defaults . get ( key ) . unwrap_or ( & JsonValue ::Null ) ) ;
if defaults . get ( key ) ! = Some ( value ) {
let _ = self . app . emit (
"store://change" ,
ChangePayload {
path : & self . path ,
key ,
value : defaults . get ( key ) . unwrap_or ( & JsonValue ::Null ) ,
} ,
) ;
}
}
}
self . cache . clone_from ( defaults ) ;
}
}
Ok ( ( ) )
for ( key , value ) in defaults {
if ! self . cache . contains_key ( key ) {
let _ = self . emit_change_event ( key , value ) ;
}
}
self . cache . clone_from ( defaults ) ;
} else {
} else {
self . clear ( )
self . clear ( )
}
}
@ -303,14 +337,152 @@ impl<R: Runtime> Store<R> {
pub fn is_empty ( & self ) -> bool {
pub fn is_empty ( & self ) -> bool {
self . cache . is_empty ( )
self . cache . is_empty ( )
}
}
fn emit_change_event ( & self , key : & str , value : & JsonValue ) -> crate ::Result < ( ) > {
self . app . emit (
"store://change" ,
ChangePayload {
path : & self . path ,
key ,
value ,
} ,
) ? ;
Ok ( ( ) )
}
}
}
impl < R : Runtime > std ::fmt ::Debug for Store < R > {
impl < R : Runtime > std ::fmt ::Debug for Store Inner < R > {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < ' _ > ) -> std ::fmt ::Result {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < ' _ > ) -> std ::fmt ::Result {
f . debug_struct ( "Store" )
f . debug_struct ( "Store" )
. field ( "path" , & self . path )
. field ( "path" , & self . path )
. field ( "defaults" , & self . defaults )
. field ( "cache" , & self . cache )
. field ( "cache" , & self . cache )
. finish ( )
. finish ( )
}
}
}
}
pub struct Store < R : Runtime > {
defaults : Option < HashMap < String , JsonValue > > ,
serialize : SerializeFn ,
deserialize : DeserializeFn ,
auto_save : Option < Duration > ,
auto_save_debounce_sender : Arc < Mutex < Option < UnboundedSender < AutoSaveMessage > > > > ,
store : Arc < Mutex < StoreInner < R > > > ,
}
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 > {
let mut store = self . store . lock ( ) . unwrap ( ) ;
f ( & mut store )
}
pub fn set ( & self , key : impl Into < String > , value : impl Into < JsonValue > ) {
self . store . lock ( ) . unwrap ( ) . insert ( key . into ( ) , value . into ( ) ) ;
let _ = self . trigger_auto_save ( ) ;
}
pub fn get ( & self , key : impl AsRef < str > ) -> Option < JsonValue > {
self . store . lock ( ) . unwrap ( ) . get ( key ) . cloned ( )
}
pub fn has ( & self , key : impl AsRef < str > ) -> bool {
self . store . lock ( ) . unwrap ( ) . has ( key )
}
pub fn delete ( & self , key : impl AsRef < str > ) -> bool {
let deleted = self . store . lock ( ) . unwrap ( ) . delete ( key ) ;
if deleted {
let _ = self . trigger_auto_save ( ) ;
}
deleted
}
pub fn clear ( & self ) {
self . store . lock ( ) . unwrap ( ) . clear ( ) ;
let _ = self . trigger_auto_save ( ) ;
}
pub fn reset ( & self ) {
self . store . lock ( ) . unwrap ( ) . reset ( & self . defaults ) ;
let _ = self . trigger_auto_save ( ) ;
}
pub fn keys ( & self ) -> Vec < String > {
self . store . lock ( ) . unwrap ( ) . keys ( ) . cloned ( ) . collect ( )
}
pub fn values ( & self ) -> Vec < JsonValue > {
self . store . lock ( ) . unwrap ( ) . values ( ) . cloned ( ) . collect ( )
}
pub fn entries ( & self ) -> Vec < ( String , JsonValue ) > {
self . store
. lock ( )
. unwrap ( )
. entries ( )
. map ( | ( k , v ) | ( k . to_owned ( ) , v . to_owned ( ) ) )
. collect ( )
}
pub fn length ( & self ) -> usize {
self . store . lock ( ) . unwrap ( ) . len ( )
}
pub fn load ( & self ) -> crate ::Result < ( ) > {
self . store . lock ( ) . unwrap ( ) . load ( self . deserialize )
}
pub fn save ( & self ) -> crate ::Result < ( ) > {
self . store . lock ( ) . unwrap ( ) . save ( self . serialize )
}
fn trigger_auto_save ( & self ) -> crate ::Result < ( ) > {
let Some ( auto_save_delay ) = self . auto_save else {
return Ok ( ( ) ) ;
} ;
if auto_save_delay . is_zero ( ) {
return self . save ( ) ;
}
let mut auto_save_debounce_sender = self . auto_save_debounce_sender . lock ( ) . unwrap ( ) ;
if let Some ( ref sender ) = * auto_save_debounce_sender {
let _ = sender . send ( AutoSaveMessage ::Reset ) ;
return Ok ( ( ) ) ;
}
let ( sender , mut receiver ) = unbounded_channel ( ) ;
auto_save_debounce_sender . replace ( sender ) ;
drop ( auto_save_debounce_sender ) ;
let store = self . store . clone ( ) ;
let serialize_fn = self . serialize ;
let auto_save_debounce_sender = self . auto_save_debounce_sender . clone ( ) ;
tauri ::async_runtime ::spawn ( async move {
loop {
select ! {
should_cancel = receiver . recv ( ) = > {
if matches! ( should_cancel , Some ( AutoSaveMessage ::Cancel ) | None ) {
return ;
}
}
_ = sleep ( auto_save_delay ) = > {
let _ = store . lock ( ) . unwrap ( ) . save ( serialize_fn ) ;
auto_save_debounce_sender . lock ( ) . unwrap ( ) . take ( ) ;
return ;
}
} ;
}
} ) ;
Ok ( ( ) )
}
}
impl < R : Runtime > Drop for Store < R > {
fn drop ( & mut self ) {
let auto_save_debounce_sender = self . auto_save_debounce_sender . lock ( ) . unwrap ( ) ;
if let Some ( ref sender ) = * auto_save_debounce_sender {
let _ = sender . send ( AutoSaveMessage ::Cancel ) ;
}
}
}