@ -5,251 +5,201 @@
pub use error ::Error ;
use log ::warn ;
use serde ::Serialize ;
use serde_json ::Value as JsonValue ;
use std ::{ collections ::HashMap , path ::PathBuf , sync ::Mutex } ;
pub use serde_json ::Value as JsonValue ;
use std ::{
collections ::HashMap ,
path ::{ Path , PathBuf } ,
sync ::Mutex ,
} ;
pub use store ::{ Store , StoreBuilder } ;
use tauri ::{
plugin ::{ self , TauriPlugin } ,
AppHandle , Manager , RunEvent , Runtime , State , Window ,
AppHandle , Manager , RunEvent , Runtime , State ,
} ;
mod error ;
mod store ;
#[ derive(Serialize, Clone) ]
struct ChangePayload {
path : Path Buf ,
key : String ,
value : JsonValue ,
struct ChangePayload < ' a > {
path : & ' a Path ,
key : & ' a str ,
value : & ' a JsonValue ,
}
#[ derive(Default) ]
struct StoreCollection {
stores : Mutex < HashMap < PathBuf , Store >> ,
pub struct StoreCollection < R : Runtime > {
stores : Mutex < HashMap < PathBuf , Store <R > >> ,
frozen : bool ,
}
fn with_store < R : Runtime , T , F : FnOnce ( & mut Store ) -> Result < T , Error > > (
app : & AppHandle < R > ,
collection : State < ' _ , StoreCollection >,
path : PathBuf ,
pub fn with_store < R : Runtime , T , F : FnOnce ( & mut Store < R > ) -> Result < T , Error > > (
app : AppHandle < R > ,
collection : State < ' _ , StoreCollection <R > >,
path : impl AsRef < Path > ,
f : F ,
) -> Result < T , Error > {
let mut stores = collection . stores . lock ( ) . expect ( "mutex poisoned" ) ;
if ! stores . contains_key ( & path ) {
let path = path . as_ref ( ) ;
if ! stores . contains_key ( path ) {
if collection . frozen {
return Err ( Error ::NotFound ( path )) ;
return Err ( Error ::NotFound ( path .to_path_buf ( ) )) ;
}
let mut store = StoreBuilder ::new ( path. clone ( ) ) . build ( ) ;
let mut store = StoreBuilder ::new ( app, path . to_path_buf ( ) ) . build ( ) ;
// ignore loading errors, just use the default
if let Err ( err ) = store . load ( app ) {
if let Err ( err ) = store . load ( ) {
warn ! (
"Failed to load store {:?} from disk: {}. Falling back to default values." ,
path , err
) ;
}
stores . insert ( path . clone ( ) , store ) ;
stores . insert ( path . to_path_buf ( ) , store ) ;
}
f ( stores
. get_mut ( & path )
. get_mut ( path )
. expect ( "failed to retrieve store. This is a bug!" ) )
}
#[ tauri::command ]
async fn set < R : Runtime > (
app : AppHandle < R > ,
window : Window < R > ,
stores : State < ' _ , StoreCollection > ,
stores : State < ' _ , StoreCollection < R > > ,
path : PathBuf ,
key : String ,
value : JsonValue ,
) -> Result < ( ) , Error > {
with_store ( & app , stores , path . clone ( ) , | store | {
store . cache . insert ( key . clone ( ) , value . clone ( ) ) ;
let _ = window . emit ( "store://change" , ChangePayload { path , key , value } ) ;
Ok ( ( ) )
} )
with_store ( app , stores , path , | store | store . insert ( key , value ) )
}
#[ tauri::command ]
async fn get < R : Runtime > (
app : AppHandle < R > ,
stores : State < ' _ , StoreCollection >,
stores : State < ' _ , StoreCollection <R > >,
path : PathBuf ,
key : String ,
) -> Result < Option < JsonValue > , Error > {
with_store ( & app , stores , path , | store | {
Ok ( store . cache . get ( & key ) . cloned ( ) )
} )
with_store ( app , stores , path , | store | Ok ( store . get ( key ) . cloned ( ) ) )
}
#[ tauri::command ]
async fn has < R : Runtime > (
app : AppHandle < R > ,
stores : State < ' _ , StoreCollection >,
stores : State < ' _ , StoreCollection <R > >,
path : PathBuf ,
key : String ,
) -> Result < bool , Error > {
with_store ( & app , stores , path , | store | {
Ok ( store . cache . contains_key ( & key ) )
} )
with_store ( app , stores , path , | store | Ok ( store . has ( key ) ) )
}
#[ tauri::command ]
async fn delete < R : Runtime > (
app : AppHandle < R > ,
window : Window < R > ,
stores : State < ' _ , StoreCollection > ,
stores : State < ' _ , StoreCollection < R > > ,
path : PathBuf ,
key : String ,
) -> Result < bool , Error > {
with_store ( & app , stores , path . clone ( ) , | store | {
let flag = store . cache . remove ( & key ) . is_some ( ) ;
if flag {
let _ = window . emit (
"store://change" ,
ChangePayload {
path ,
key ,
value : JsonValue ::Null ,
} ,
) ;
}
Ok ( flag )
} )
with_store ( app , stores , path , | store | store . delete ( key ) )
}
#[ tauri::command ]
async fn clear < R : Runtime > (
app : AppHandle < R > ,
window : Window < R > ,
stores : State < ' _ , StoreCollection > ,
stores : State < ' _ , StoreCollection < R > > ,
path : PathBuf ,
) -> Result < ( ) , Error > {
with_store ( & app , stores , path . clone ( ) , | store | {
let keys = store . cache . keys ( ) . cloned ( ) . collect ::< Vec < String > > ( ) ;
store . cache . clear ( ) ;
for key in keys {
let _ = window . emit (
"store://change" ,
ChangePayload {
path : path . clone ( ) ,
key ,
value : JsonValue ::Null ,
} ,
) ;
}
Ok ( ( ) )
} )
with_store ( app , stores , path , | store | store . clear ( ) )
}
#[ tauri::command ]
async fn reset < R : Runtime > (
app : AppHandle < R > ,
window : Window < R > ,
collection : State < ' _ , StoreCollection > ,
collection : State < ' _ , StoreCollection < R > > ,
path : PathBuf ,
) -> Result < ( ) , Error > {
let has_defaults = collection
. stores
. lock ( )
. expect ( "mutex poisoned" )
. get ( & path )
. map ( | store | store . defaults . is_some ( ) ) ;
if Some ( true ) = = has_defaults {
with_store ( & app , collection , path . clone ( ) , | store | {
if let Some ( defaults ) = & store . defaults {
for ( key , value ) in & store . cache {
if defaults . get ( key ) ! = Some ( value ) {
let _ = window . emit (
"store://change" ,
ChangePayload {
path : path . clone ( ) ,
key : key . clone ( ) ,
value : defaults . get ( key ) . cloned ( ) . unwrap_or ( JsonValue ::Null ) ,
} ,
) ;
}
}
store . cache = defaults . clone ( ) ;
}
Ok ( ( ) )
} )
} else {
clear ( app , window , collection , path ) . await
}
with_store ( app , collection , path , | store | store . reset ( ) )
}
#[ tauri::command ]
async fn keys < R : Runtime > (
app : AppHandle < R > ,
stores : State < ' _ , StoreCollection >,
stores : State < ' _ , StoreCollection < R > > ,
path : PathBuf ,
) -> Result < Vec < String > , Error > {
with_store ( & app , stores , path , | store | {
Ok ( store . cache. keys( ) . cloned ( ) . collect ( ) )
with_store ( app , stores , path , | store | {
Ok ( store . keys ( ) . cloned ( ) . collect ( ) )
} )
}
#[ tauri::command ]
async fn values < R : Runtime > (
app : AppHandle < R > ,
stores : State < ' _ , StoreCollection >,
stores : State < ' _ , StoreCollection < R > > ,
path : PathBuf ,
) -> Result < Vec < JsonValue > , Error > {
with_store ( & app , stores , path , | store | {
Ok ( store . cache. values( ) . cloned ( ) . collect ( ) )
with_store ( app , stores , path , | store | {
Ok ( store . values ( ) . cloned ( ) . collect ( ) )
} )
}
#[ tauri::command ]
async fn entries < R : Runtime > (
app : AppHandle < R > ,
stores : State < ' _ , StoreCollection >,
stores : State < ' _ , StoreCollection < R > > ,
path : PathBuf ,
) -> Result < Vec < ( String , JsonValue ) > , Error > {
with_store ( & app , stores , path , | store | {
Ok ( store . cache . clone ( ) . into_iter ( ) . collect ( ) )
with_store ( app , stores , path , | store | {
Ok ( store
. entries ( )
. map ( | ( k , v ) | ( k . to_owned ( ) , v . to_owned ( ) ) )
. collect ( ) )
} )
}
#[ tauri::command ]
async fn length < R : Runtime > (
app : AppHandle < R > ,
stores : State < ' _ , StoreCollection >,
stores : State < ' _ , StoreCollection <R > >,
path : PathBuf ,
) -> Result < usize , Error > {
with_store ( & app , stores , path , | store | Ok ( stor e. cach e. len ( ) ) )
with_store ( app , stores , path , | store | Ok ( stor e. len ( ) ) )
}
#[ tauri::command ]
async fn load < R : Runtime > (
app : AppHandle < R > ,
stores : State < ' _ , StoreCollection >,
stores : State < ' _ , StoreCollection <R > >,
path : PathBuf ,
) -> Result < ( ) , Error > {
with_store ( & app , stores , path , | store | store . load ( & app ) )
with_store ( app , stores , path , | store | store . load ( ) )
}
#[ tauri::command ]
async fn save < R : Runtime > (
app : AppHandle < R > ,
stores : State < ' _ , StoreCollection >,
stores : State < ' _ , StoreCollection <R > >,
path : PathBuf ,
) -> Result < ( ) , Error > {
with_store ( & app , stores , path , | store | store . save ( & app ) )
with_store ( app , stores , path , | store | store . save ( ) )
}
#[ derive(Default) ]
pub struct Builder {
stores : HashMap < PathBuf , Store >,
// #[derive(Default)]
pub struct Builder < R : Runtime > {
stores : HashMap < PathBuf , Store <R > >,
frozen : bool ,
}
impl Builder {
impl < R : Runtime > Default for Builder < R > {
fn default ( ) -> Self {
Self {
stores : Default ::default ( ) ,
frozen : false ,
}
}
}
impl < R : Runtime > Builder < R > {
/// Registers a store with the plugin.
///
/// # Examples
@ -265,7 +215,7 @@ impl Builder {
/// # Ok(())
/// # }
/// ```
pub fn store ( mut self , store : Store ) -> Self {
pub fn store ( mut self , store : Store < R > ) -> Self {
self . stores . insert ( store . path . clone ( ) , store ) ;
self
}
@ -285,7 +235,7 @@ impl Builder {
/// # Ok(())
/// # }
/// ```
pub fn stores < T : IntoIterator < Item = Store >> ( mut self , stores : T ) -> Self {
pub fn stores < T : IntoIterator < Item = Store <R > >> ( mut self , stores : T ) -> Self {
self . stores = stores
. into_iter ( )
. map ( | store | ( store . path . clone ( ) , store ) )
@ -331,7 +281,7 @@ impl Builder {
/// # Ok(())
/// # }
/// ```
pub fn build < R : Runtime > ( mut self ) -> TauriPlugin < R > {
pub fn build ( mut self ) -> TauriPlugin < R > {
plugin ::Builder ::new ( "store" )
. invoke_handler ( tauri ::generate_handler ! [
set , get , has , delete , clear , reset , keys , values , length , entries , load , save
@ -339,7 +289,7 @@ impl Builder {
. setup ( move | app_handle | {
for ( path , store ) in self . stores . iter_mut ( ) {
// ignore loading errors, just use the default
if let Err ( err ) = store . load ( app_handle ) {
if let Err ( err ) = store . load ( ) {
warn ! (
"Failed to load store {:?} from disk: {}. Falling back to default values." ,
path , err
@ -356,10 +306,10 @@ impl Builder {
} )
. on_event ( | app_handle , event | {
if let RunEvent ::Exit = event {
let collection = app_handle . state ::< StoreCollection >( ) ;
let collection = app_handle . state ::< StoreCollection <R > >( ) ;
for store in collection . stores . lock ( ) . expect ( "mutex poisoned" ) . values ( ) {
if let Err ( err ) = store . save ( app_handle ) {
if let Err ( err ) = store . save ( ) {
eprintln! ( "failed to save store {:?} with error {:?}" , store . path , err ) ;
}
}