Refactor config_trait crate and add doc comment examples

This commit is contained in:
Luke D. Jones
2023-01-08 20:41:17 +13:00
parent 5133d398eb
commit 00839aaa6f
6 changed files with 177 additions and 107 deletions

View File

@@ -18,65 +18,63 @@ where
fn new() -> Self; fn new() -> Self;
/// Return the config files names, such as `wibble.cfg` /// Return the config files names, such as `wibble.cfg`
fn file_name() -> &'static str; fn file_name(&self) -> String;
/// Return the full path to the directory the config file resides in /// Return the full path to the directory the config file resides in
fn config_dir() -> PathBuf; fn config_dir() -> PathBuf;
/// Return the full path to the config file /// Return the full path to the config file
fn file_path() -> PathBuf { fn file_path(&self) -> PathBuf {
let mut config = Self::config_dir(); let mut config = Self::config_dir();
if !config.exists() { if !config.exists() {
create_dir(config.as_path()) create_dir(config.as_path())
.unwrap_or_else(|e| panic!("Could not create {:?} {e}", Self::config_dir())); .unwrap_or_else(|e| panic!("Could not create {:?} {e}", Self::config_dir()));
} }
config.push(Self::file_name()); config.push(self.file_name());
config config
} }
/// Directly open the config file for read and write. If the config file /// Directly open the config file for read and write. If the config file
/// does not exist it is created, including the directories the file /// does not exist it is created, including the directories the file
/// resides in. /// resides in.
fn file_open() -> File { fn file_open(&self) -> File {
OpenOptions::new() OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
.create(true) .create(true)
.open(Self::file_path()) .open(self.file_path())
.unwrap_or_else(|e| panic!("Could not open {:?} {e}", Self::file_path())) .unwrap_or_else(|e| panic!("Could not open {:?} {e}", self.file_path()))
} }
/// Open and parse the config file to self from ron format /// Open and parse the config file to self from ron format
fn read(&mut self) { fn read(&mut self) {
let mut file = match OpenOptions::new().read(true).open(Self::file_path()) { let mut file = match OpenOptions::new().read(true).open(self.file_path()) {
Ok(data) => data, Ok(data) => data,
Err(err) => { Err(err) => {
error!("Error reading {:?}: {}", Self::file_path(), err); error!("Error reading {:?}: {}", self.file_path(), err);
return; return;
} }
}; };
let mut buf = String::new(); let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) { if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 { if l == 0 {
warn!("File is empty {:?}", Self::file_path()); warn!("File is empty {:?}", self.file_path());
} else if let Ok(data) = ron::from_str(&buf) { } else if let Ok(data) = ron::from_str(&buf) {
*self = data; *self = data;
} else if let Ok(data) = serde_json::from_str(&buf) {
*self = data;
} else { } else {
warn!("Could not deserialise {:?}", Self::file_path()); warn!("Could not deserialise {:?}", self.file_path());
} }
} }
} }
/// Write the config file data to pretty ron format /// Write the config file data to pretty ron format
fn write(&self) { fn write(&self) {
let mut file = match File::create(Self::file_path()) { let mut file = match File::create(self.file_path()) {
Ok(data) => data, Ok(data) => data,
Err(e) => { Err(e) => {
error!( error!(
"Couldn't overwrite config {:?}, error: {e}", "Couldn't overwrite config {:?}, error: {e}",
Self::file_path() self.file_path()
); );
return; return;
} }
@@ -84,7 +82,7 @@ where
let ron = match ron::ser::to_string_pretty(&self, PrettyConfig::new().depth_limit(4)) { let ron = match ron::ser::to_string_pretty(&self, PrettyConfig::new().depth_limit(4)) {
Ok(data) => data, Ok(data) => data,
Err(e) => { Err(e) => {
error!("Parse {:?} to RON failed, error: {e}", Self::file_path()); error!("Parse {:?} to RON failed, error: {e}", self.file_path());
return; return;
} }
}; };
@@ -93,17 +91,17 @@ where
} }
/// Renames the existing file to `<file>-old` /// Renames the existing file to `<file>-old`
fn rename_file_old() { fn rename_file_old(&self) {
warn!( warn!(
"Renaming {} to {}-old and recreating config", "Renaming {} to {}-old and recreating config",
Self::file_name(), self.file_name(),
Self::file_name() self.file_name()
); );
let cfg_old = Self::file_path().to_string_lossy().to_string() + "-old"; let cfg_old = self.file_path().to_string_lossy().to_string() + "-old";
std::fs::rename(Self::file_path(), cfg_old).unwrap_or_else(|err| { std::fs::rename(self.file_path(), cfg_old).unwrap_or_else(|err| {
error!( error!(
"Could not rename. Please remove {} then restart service: Error {}", "Could not rename. Please remove {} then restart service: Error {}",
Self::file_name(), self.file_name(),
err err
) )
}); });
@@ -111,112 +109,184 @@ where
} }
/// Base trait for loading/parsing. This can be used to help update configs to /// Base trait for loading/parsing. This can be used to help update configs to
/// new versions ```ignore /// new versions
/// impl StdConfigLoad1<FanCurveConfigV1> for FanCurveConfig {} ///
/// # Example
/// ```rust
/// use std::path::PathBuf;
/// use serde::{Deserialize, Serialize};
/// use config_traits::{StdConfig, StdConfigLoad1};
///
/// #[derive(Deserialize, Serialize)]
/// struct FanCurveConfig {}
///
/// impl StdConfig for FanCurveConfig {
/// fn new() -> Self { Self {} }
///
/// fn file_name(&self) -> std::string::String { "test_name.conf".to_owned() }
///
/// fn config_dir() -> PathBuf { PathBuf::from("/tmp") }
/// }
///
/// impl StdConfigLoad1 for FanCurveConfig {}
/// ``` /// ```
/// ///
/// If all of the generics fails to parse, then the old config is renamed and a /// If all of the generics fails to parse, then the old config is renamed and a
/// new one created /// new one created
pub trait StdConfigLoad1<T> pub trait StdConfigLoad1
where where
T: StdConfig + DeserializeOwned + Serialize, Self: StdConfig + DeserializeOwned + Serialize,
{ {
fn load() -> T { fn load(mut self) -> Self {
let mut file = T::file_open(); let mut file = self.file_open();
let mut buf = String::new(); let mut buf = String::new();
let config: T;
if let Ok(read_len) = file.read_to_string(&mut buf) { if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 { if read_len != 0 {
config = T::new(); if let Ok(data) = ron::from_str(&buf) {
} else if let Ok(data) = ron::from_str(&buf) { self = data;
config = data; } else if let Ok(data) = serde_json::from_str(&buf) {
} else if let Ok(data) = serde_json::from_str(&buf) { self = data;
config = data; } else {
self.rename_file_old();
self = Self::new();
}
} else { } else {
T::rename_file_old(); error!("Config file {} zero read length", self.file_name());
config = T::new();
} }
} else {
config = T::new();
} }
config.write(); self.write();
config self
} }
} }
/// Base trait for loading/parsing. This is intended to be used to help update /// Base trait for loading/parsing. This is intended to be used to help update
/// configs to new versions ```ignore /// configs to new versions
/// impl StdConfigLoad2<FanCurveConfigV1, ProfileConfigV2> for FanCurveConfig {} ///
/// # Example
/// ```rust
/// use std::path::PathBuf;
/// use serde::{Deserialize, Serialize};
/// use config_traits::{StdConfig, StdConfigLoad2};
///
/// #[derive(Deserialize, Serialize)]
/// struct FanCurveConfigOld {}
///
/// #[derive(Deserialize, Serialize)]
/// struct FanCurveConfig {}
///
/// impl From<FanCurveConfigOld> for FanCurveConfig {
/// fn from(_: FanCurveConfigOld) -> Self { Self {} }
/// }
///
/// impl StdConfig for FanCurveConfig {
/// fn new() -> Self { Self {} }
///
/// fn file_name(&self) -> std::string::String { "test_name.conf".to_owned() }
///
/// fn config_dir() -> PathBuf { PathBuf::from("/tmp") }
/// }
///
/// impl StdConfigLoad2<FanCurveConfigOld> for FanCurveConfig {}
/// ``` /// ```
/// ///
/// If all of the generics fails to parse, then the old config is renamed and a /// If all of the generics fails to parse, then the old config is renamed and a
/// new one created /// new one created
pub trait StdConfigLoad2<T1, T2> pub trait StdConfigLoad2<OldConfig>
where where
T1: StdConfig + DeserializeOwned + Serialize, Self: StdConfig + DeserializeOwned + Serialize,
T2: DeserializeOwned + Into<T1>, OldConfig: DeserializeOwned + Into<Self>,
{ {
fn load() -> T1 { fn load(mut self) -> Self {
let mut file = T1::file_open(); let mut file = self.file_open();
let mut buf = String::new(); let mut buf = String::new();
let config: T1;
if let Ok(read_len) = file.read_to_string(&mut buf) { if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 { if read_len != 0 {
config = T1::new(); if let Ok(data) = ron::from_str(&buf) {
} else if let Ok(data) = ron::from_str(&buf) { self = data;
config = data; } else if let Ok(data) = serde_json::from_str(&buf) {
} else if let Ok(data) = serde_json::from_str(&buf) { self = data;
config = data; } else if let Ok(data) = serde_json::from_str::<OldConfig>(&buf) {
} else if let Ok(data) = serde_json::from_str::<T2>(&buf) { self = data.into();
config = data.into(); } else {
self.rename_file_old();
self = Self::new();
}
} else { } else {
T1::rename_file_old(); error!("Config file {} zero read length", self.file_name());
config = T1::new();
} }
} else {
config = T1::new();
} }
config.write(); self.write();
config self
} }
} }
/// Base trait for loading/parsing. This is intended to be used to help update /// Base trait for loading/parsing. This is intended to be used to help update
/// configs to new versions ```ignore /// configs to new versions
/// impl StdConfigLoad3<FanCurveConfigV1, ProfileConfigV2, ProfileConfigV3> for FanCurveConfig {} ///
/// # Example
/// ```rust
/// use std::path::PathBuf;
/// use serde::{Deserialize, Serialize};
/// use config_traits::{StdConfig, StdConfigLoad3};
///
/// #[derive(Deserialize, Serialize)]
/// struct FanCurveConfigOld {}
///
/// #[derive(Deserialize, Serialize)]
/// struct FanCurveConfigOlder {}
///
/// #[derive(Deserialize, Serialize)]
/// struct FanCurveConfig {}
///
/// impl From<FanCurveConfigOld> for FanCurveConfig {
/// fn from(_: FanCurveConfigOld) -> Self { Self {} }
/// }
///
/// impl From<FanCurveConfigOlder> for FanCurveConfig {
/// fn from(_: FanCurveConfigOlder) -> Self { Self {} }
/// }
///
/// impl StdConfig for FanCurveConfig {
/// fn new() -> Self { Self {} }
///
/// fn file_name(&self) -> std::string::String { "test_name.conf".to_owned() }
///
/// fn config_dir() -> PathBuf { PathBuf::from("/tmp") }
/// }
///
/// impl StdConfigLoad3<FanCurveConfigOld, FanCurveConfigOlder> for FanCurveConfig {}
/// ``` /// ```
/// ///
/// If all of the generics fails to parse, then the old config is renamed and a /// If all of the generics fails to parse, then the old config is renamed and a
/// new one created /// new one created
pub trait StdConfigLoad3<T1, T2, T3> pub trait StdConfigLoad3<OldConfig, OldConfig2>: StdConfig
where where
T1: StdConfig + DeserializeOwned + Serialize, Self: StdConfig + DeserializeOwned + Serialize,
T2: DeserializeOwned + Into<T1>, OldConfig: DeserializeOwned + Into<Self>,
T3: DeserializeOwned + Into<T1>, OldConfig2: DeserializeOwned + Into<Self>,
{ {
fn load() -> T1 { fn load(mut self) -> Self {
let mut file = T1::file_open(); let mut file = self.file_open();
let mut buf = String::new(); let mut buf = String::new();
let config: T1;
if let Ok(read_len) = file.read_to_string(&mut buf) { if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 { if read_len != 0 {
config = T1::new(); if let Ok(data) = ron::from_str(&buf) {
} else if let Ok(data) = ron::from_str(&buf) { self = data;
config = data; } else if let Ok(data) = serde_json::from_str(&buf) {
} else if let Ok(data) = serde_json::from_str(&buf) { self = data;
config = data; } else if let Ok(data) = serde_json::from_str::<OldConfig>(&buf) {
} else if let Ok(data) = serde_json::from_str::<T2>(&buf) { self = data.into();
config = data.into(); } else if let Ok(data) = serde_json::from_str::<OldConfig2>(&buf) {
} else if let Ok(data) = serde_json::from_str::<T3>(&buf) { self = data.into();
config = data.into(); } else {
self.rename_file_old();
self = Self::new();
}
} else { } else {
T1::rename_file_old(); error!("Config file {} zero read length", self.file_name());
config = T1::new();
} }
} else {
config = T1::new();
} }
config.write(); self.write();
config self
} }
} }

View File

@@ -28,12 +28,12 @@ impl StdConfig for Config {
std::path::PathBuf::from(crate::CONFIG_PATH_BASE) std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
} }
fn file_name() -> &'static str { fn file_name(&self) -> String {
CONFIG_FILE CONFIG_FILE.to_string()
} }
} }
impl StdConfigLoad3<Config, Config455, Config458> for Config {} impl StdConfigLoad3<Config455, Config458> for Config {}
#[derive(Deserialize, Serialize, Default)] #[derive(Deserialize, Serialize, Default)]
#[serde(default)] #[serde(default)]

View File

@@ -144,12 +144,12 @@ impl StdConfig for AnimeConfig {
std::path::PathBuf::from(crate::CONFIG_PATH_BASE) std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
} }
fn file_name() -> &'static str { fn file_name(&self) -> String {
CONFIG_FILE CONFIG_FILE.to_string()
} }
} }
impl StdConfigLoad3<AnimeConfig, AnimeConfigV341, AnimeConfigV352> for AnimeConfig {} impl StdConfigLoad3<AnimeConfigV341, AnimeConfigV352> for AnimeConfig {}
impl AnimeConfig { impl AnimeConfig {
// fn clamp_config_brightness(mut config: &mut AnimeConfig) { // fn clamp_config_brightness(mut config: &mut AnimeConfig) {

View File

@@ -194,12 +194,12 @@ impl StdConfig for AuraConfig {
std::path::PathBuf::from(crate::CONFIG_PATH_BASE) std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
} }
fn file_name() -> &'static str { fn file_name(&self) -> String {
CONFIG_FILE CONFIG_FILE.to_string()
} }
} }
impl StdConfigLoad1<AuraConfig> for AuraConfig {} impl StdConfigLoad1 for AuraConfig {}
impl AuraConfig { impl AuraConfig {
fn create_default(support_data: &LaptopLedData) -> Self { fn create_default(support_data: &LaptopLedData) -> Self {

View File

@@ -27,12 +27,12 @@ impl StdConfig for ProfileConfig {
PathBuf::from(CONFIG_PATH_BASE) PathBuf::from(CONFIG_PATH_BASE)
} }
fn file_name() -> &'static str { fn file_name(&self) -> String {
CONFIG_FILE CONFIG_FILE.to_string()
} }
} }
impl StdConfigLoad1<ProfileConfig> for ProfileConfig {} impl StdConfigLoad1 for ProfileConfig {}
#[derive(Deserialize, Serialize, Debug, Default)] #[derive(Deserialize, Serialize, Debug, Default)]
pub struct FanCurveConfig { pub struct FanCurveConfig {
@@ -76,9 +76,9 @@ impl StdConfig for FanCurveConfig {
PathBuf::from(CONFIG_PATH_BASE) PathBuf::from(CONFIG_PATH_BASE)
} }
fn file_name() -> &'static str { fn file_name(&self) -> String {
CONFIG_FAN_FILE CONFIG_FAN_FILE.to_string()
} }
} }
impl StdConfigLoad1<ProfileConfig> for FanCurveConfig {} impl StdConfigLoad1 for FanCurveConfig {}

View File

@@ -6,7 +6,7 @@ use std::time::Duration;
use ::zbus::export::futures_util::lock::Mutex; use ::zbus::export::futures_util::lock::Mutex;
use ::zbus::Connection; use ::zbus::Connection;
use config_traits::{StdConfigLoad1, StdConfigLoad3}; use config_traits::{StdConfig, StdConfigLoad1, StdConfigLoad3};
use daemon::config::Config; use daemon::config::Config;
use daemon::ctrl_anime::config::AnimeConfig; use daemon::ctrl_anime::config::AnimeConfig;
use daemon::ctrl_anime::trait_impls::CtrlAnimeZbus; use daemon::ctrl_anime::trait_impls::CtrlAnimeZbus;
@@ -71,7 +71,7 @@ async fn start_daemon() -> Result<(), Box<dyn Error>> {
// Start zbus server // Start zbus server
let mut connection = Connection::system().await?; let mut connection = Connection::system().await?;
let config = Config::load(); let config = Config::new().load();
let config = Arc::new(Mutex::new(config)); let config = Arc::new(Mutex::new(config));
supported.add_to_server(&mut connection).await; supported.add_to_server(&mut connection).await;
@@ -97,7 +97,7 @@ async fn start_daemon() -> Result<(), Box<dyn Error>> {
} }
if Profile::is_platform_profile_supported() { if Profile::is_platform_profile_supported() {
let profile_config = ProfileConfig::load(); let profile_config = ProfileConfig::new().load();
match CtrlPlatformProfile::new(profile_config) { match CtrlPlatformProfile::new(profile_config) {
Ok(ctrl) => { Ok(ctrl) => {
let zbus = ProfileZbus(Arc::new(Mutex::new(ctrl))); let zbus = ProfileZbus(Arc::new(Mutex::new(ctrl)));
@@ -112,7 +112,7 @@ async fn start_daemon() -> Result<(), Box<dyn Error>> {
warn!("platform_profile support not found"); warn!("platform_profile support not found");
} }
match CtrlAnime::new(AnimeConfig::load()) { match CtrlAnime::new(AnimeConfig::new().load()) {
Ok(ctrl) => { Ok(ctrl) => {
let zbus = CtrlAnimeZbus(Arc::new(Mutex::new(ctrl))); let zbus = CtrlAnimeZbus(Arc::new(Mutex::new(ctrl)));
let sig_ctx = CtrlAnimeZbus::signal_context(&connection)?; let sig_ctx = CtrlAnimeZbus::signal_context(&connection)?;
@@ -124,7 +124,7 @@ async fn start_daemon() -> Result<(), Box<dyn Error>> {
} }
let laptop = LaptopLedData::get_data(); let laptop = LaptopLedData::get_data();
let aura_config = AuraConfig::load(); let aura_config = AuraConfig::new().load();
match CtrlKbdLed::new(laptop, aura_config) { match CtrlKbdLed::new(laptop, aura_config) {
Ok(ctrl) => { Ok(ctrl) => {
let zbus = CtrlKbdLedZbus(Arc::new(Mutex::new(ctrl))); let zbus = CtrlKbdLedZbus(Arc::new(Mutex::new(ctrl)));