use Format;
use Type;
use Settings;
use SupportedType;
use std::fs;
use std::fs::File;
use failure::Error;
#[derive(Serialize,Deserialize,Clone)]
pub struct ShadowSettings<T> where T : Format + Clone {
ioconfig: T,
global : Settings<T>,
local : Option<Settings<T>>,
}
impl<T> ShadowSettings<T> where T : Format + Clone {
pub fn new(config : T) -> ShadowSettings<T> {
ShadowSettings {
ioconfig : config.clone(),
global : Settings::new(config.clone()),
local : None
}
}
pub fn new_and_load(config : T) -> ShadowSettings<T> {
let mut setting = ShadowSettings::new(config);
if let Err(error) = setting.load() {
warn!("{}",error);
}
setting
}
pub fn create_from(mut file : &File, config : T) -> Result<ShadowSettings<T>,Error> {
Ok(ShadowSettings {
ioconfig : config.clone(),
global : Settings::create_from(&mut file,config.clone())?,
local : None,
})
}
pub fn load(&mut self) -> Result<(),Error> {
let result_global = self.load_global();
let result_local = self.load_local();
if result_global.is_err() && result_local.is_err() {
return Err(format_err!("Global: {}, Local: {}",result_global.unwrap_err(),result_local.unwrap_err()));
} else if result_global.is_err() {
return Err(format_err!("Global: {}",result_global.unwrap_err()));
} else if result_local.is_err() {
return Err(format_err!("Local: {}",result_local.unwrap_err()));
}
Ok(())
}
pub fn load_global(&mut self) -> Result<(),Error> {
let global_path = self.ioconfig.get_path_and_file();
if let Ok(mut file) = File::open(&global_path) {
info!("Using {} for global file",global_path);
self.load_global_from(&mut file)?;
}
Ok(())
}
pub fn load_local(&mut self) -> Result<(),Error> {
let local_path = self.ioconfig.get_local_path_and_filename();
if let Ok(mut file) = File::open(&local_path) {
info!("Using {} for local file",local_path);
self.load_local_from(&mut file)?;
}
Ok(())
}
pub fn load_global_from(&mut self, file : &mut File) -> Result<(),Error> {
self.global = Settings::create_from(file, self.ioconfig.clone())?;
Ok(())
}
pub fn load_local_from(&mut self, file : &mut File) -> Result<(),Error> {
self.local = Some(Settings::create_from(file, self.ioconfig.clone())?);
Ok(())
}
pub fn save(&self) -> Result<(),Error> {
let global_path = self.ioconfig.get_path_and_file();
fs::create_dir_all(self.ioconfig.get_path())?;
info!("Saving global to {}",global_path);
let mut file = File::create(global_path)?;
self.save_global_to(&mut file)?;
if self.local.is_some() {
let local_path = self.ioconfig.get_local_path_and_filename();
info!("Saving local to {}",local_path);
let mut local_file = File::create(local_path)?;
self.save_local_to(&mut local_file)?;
}
Ok(())
}
pub fn save_global_to(&self, file : &File) -> Result<(),Error> {
self.global.save_to(file)
}
pub fn save_local_to(&self, file : &File) -> Result<(), Error> {
if let Some(ref local) = self.local {
local.save_to(file)
} else {
info!("Attempting to save local settings but none exist.");
Ok(())
}
}
pub fn get_value(&self, key_path : &str) -> Option<Type> {
if let Some(ref local) = self.local {
match local.get_value(key_path) {
None => self.global.get_value(key_path),
Some(value) => {
match value {
Type::Complex(mut value) => {
if let Some(Type::Complex(global)) = self.global.get_value(key_path) {
for (k,v) in global {
if value.get(&k).is_none() {
value.insert(k, v);
}
}
}
Some(Type::Complex(value))
},
_ => Some(value),
}
},
}
} else {
self.global.get_value(key_path)
}
}
pub fn get_value_or<A:?Sized>(&self, key_path : &str, default_value : &A) -> Type
where A : SupportedType,
{
match self.get_value(key_path) {
Some(value) => value,
None => default_value.wrap(),
}
}
pub fn get_value_local(&self, key_path : &str) -> Option<Type> {
match self.local {
Some(ref local) => local.get_value(key_path),
None => None,
}
}
pub fn get_value_global(&self, key_path : &str) -> Option<Type> {
self.global.get_value(key_path)
}
pub fn set_value_local<A:?Sized>(&mut self, key_path : &str, value : &A) -> Result<(),Error>
where A : SupportedType ,
{
match self.local {
Some(ref mut local) => local.set_value(key_path,value),
None => {
let mut local = Settings::new(self.ioconfig.clone());
let result = local.set_value(key_path,value);
self.local = Some(local);
result
},
}
}
pub fn set_value_global<A:?Sized>(&mut self, key_path : &str, value : &A) -> Result<(),Error>
where A : SupportedType ,
{
self.global.set_value(key_path,value)
}
pub fn delete_key_local(&mut self, key_path : &str) -> Option<Type> {
match self.local {
Some(ref mut local) => local.delete_key(key_path),
None => None,
}
}
pub fn delete_key_global(&mut self, key_path : &str) -> Option<Type> {
self.global.delete_key(key_path)
}
pub fn delete_file_global(&self) -> bool {
self.global.delete_file()
}
pub fn delete_file_local(&self) -> bool {
match fs::remove_file(self.ioconfig.get_local_path_and_filename()){
Err(_) => false,
Ok(_) => true,
}
}
pub fn keys_global(&self) -> Vec<String> {
self.global.keys()
}
pub fn keys_local(&self) -> Vec<String> {
if let Some(ref local) = self.local {
local.keys()
} else {
Vec::new()
}
}
}
#[cfg(test)]
mod tests {
use SupportedType;
use Format;
use SettingsRaw;
use Type;
use ShadowSettings;
use failure::Error;
use std::collections::HashMap;
#[derive(Clone)]
struct Configuration { }
impl Format for Configuration {
fn filename(&self) -> String { "".to_string() }
fn folder(&self) -> String { "".to_string() }
fn from_str<T>(&self,_:&str) -> Result<SettingsRaw,Error> where T : Format + Clone {
Ok(HashMap::<String,Type>::new())
}
fn to_string<T:?Sized>(&self,_:&T) -> Result<String,Error> where T : SupportedType {
Ok("unimplemented".to_string())
}
}
#[test]
fn set_and_get_value() {
let mut test_obj = ShadowSettings::new(Configuration{});
assert_eq!(test_obj.set_value_global("a.b.c.d","mortan").is_ok(),true);
assert_eq!(test_obj.set_value_global("a.b.c.e","bobby lee").is_ok(),true);
assert_eq!(test_obj.set_value_global("a.b.f",&4453).is_ok(),true);
assert_eq!(test_obj.set_value_global("a.is_enabled",&true).is_ok(),true);
assert_eq!(test_obj.set_value_local("a.b.c.d",&false).is_ok(),true);
assert_eq!(test_obj.get_value_global("a.b.c.d"),Some(Type::Text("mortan".to_string())));
assert_eq!(test_obj.get_value_global("a.b.c.e"),Some(Type::Text("bobby lee".to_string())));
assert_eq!(test_obj.get_value_global("a.b.f"),Some(Type::Int(4453)));
assert_eq!(test_obj.get_value_global("a.is_enabled"),Some(Type::Switch(true)));
assert_eq!(test_obj.get_value_local("a.b.c.d"),Some(Type::Switch(false)));
assert_eq!(test_obj.get_value("a.b.c.d"),Some(Type::Switch(false)));
assert_eq!(test_obj.get_value("a.b.c.e"),Some(Type::Text("bobby lee".to_string())));
assert_eq!(test_obj.get_value("a.b.f"),Some(Type::Int(4453)));
assert_eq!(test_obj.get_value("a.is_enabled"),Some(Type::Switch(true)));
}
#[test]
fn get_value_or() {
let mut test_obj = ShadowSettings::new(Configuration{});
assert_eq!(test_obj.set_value_global("a.b.c.d","mortan").is_ok(),true);
assert_eq!(test_obj.set_value_global("a.b.c.e","bobby lee").is_ok(),true);
assert_eq!(test_obj.get_value_or("a.b.c.d", "not going to be used"),Type::Text("mortan".to_string()));
assert_eq!(test_obj.get_value_or("a.b.c.f", "will be used"),Type::Text("will be used".to_string()));
}
#[test]
fn get_value_shadow_complex() {
let mut test_obj = ShadowSettings::new(Configuration{});
assert_eq!(test_obj.set_value_global("a.b.c.d","mortan").is_ok(),true);
assert_eq!(test_obj.set_value_global("a.b.c.e","bobby lee").is_ok(),true);
assert_eq!(test_obj.set_value_local("a.b.c.e","lee bo").is_ok(),true);
let other_setting = test_obj.get_value("a.b.c").unwrap().to_complex().unwrap();
assert_eq!(other_setting.get("d"), Some(&Type::Text("mortan".to_string())));
assert_eq!(other_setting.get("e"), Some(&Type::Text("lee bo".to_string())));
}
}