Created config manager with path read functionality

This commit is contained in:
michael-bailey 2022-09-01 08:37:20 +01:00
parent fe960a2018
commit 9e91e1aa0a
4 changed files with 225 additions and 0 deletions

View File

@ -0,0 +1,136 @@
use std::{
collections::BTreeMap,
fs::{File, OpenOptions},
io::Read,
sync::Once,
};
use actix::{Actor, Addr, Context, Handler, Recipient};
use toml::Value;
use crate::{
config_manager::{
messages::{
ConfigManagerDataMessage, ConfigManagerDataResponse,
ConfigManagerOutput,
},
types::ConfigValue,
},
prelude::messages::ObservableMessage,
};
static mut SHARED: Option<Addr<ConfigManager>> = None;
static INIT: Once = Once::new();
pub(crate) struct ConfigManager {
_file: Option<File>,
_stored: ConfigValue,
root: ConfigValue,
_subscribers: Vec<Recipient<ObservableMessage<ConfigManagerOutput>>>,
}
impl ConfigManager {
pub fn new(file: Option<String>) -> Addr<Self> {
INIT.call_once(|| {
// Since this access is inside a call_once, before any other accesses, it is safe
unsafe {
// todo: add proper error handling
let file = OpenOptions::new()
.write(true)
.read(true)
.open(file.unwrap_or("./config_file.toml".into()))
.ok();
let mut output = String::new();
file.as_ref().map(|mut v| {
v.read_to_string(&mut output)
.expect("failed to read from file")
});
let stored = output
.parse::<Value>()
.map(|v| v.into())
.ok()
.unwrap_or_else(|| ConfigValue::Dict(BTreeMap::new()));
let root = stored.clone();
let shared = Self {
_file: file,
root,
_stored: stored,
_subscribers: Vec::default(),
}
.start();
SHARED = Some(shared);
}
});
unsafe { SHARED.clone().unwrap() }
}
}
impl ConfigManager {
pub(crate) fn get_value(
&self,
val_path: String,
) -> Result<ConfigValue, &'static str> {
use ConfigValue::{Array, Dict};
let path: Vec<String> = val_path.split('.').map(|v| v.into()).collect();
let mut current_node: &ConfigValue = &self.root;
for i in path {
match current_node {
Dict(v) => match v.get(&i) {
Some(v) => current_node = v,
None => return Err("path does not exist"),
},
Array(v) => {
if let Ok(index) = i.parse::<usize>() {
current_node = &v[index];
}
}
_ => return Err("invalid path"),
}
}
Ok(current_node.clone())
}
}
impl Actor for ConfigManager {
type Context = Context<Self>;
fn started(&mut self, _ctx: &mut Self::Context) {}
}
impl Handler<ObservableMessage<ConfigManagerOutput>> for ConfigManager {
type Result = ();
fn handle(
&mut self,
_msg: ObservableMessage<ConfigManagerOutput>,
_ctx: &mut Self::Context,
) -> Self::Result {
todo!()
}
}
impl Handler<ConfigManagerDataMessage> for ConfigManager {
type Result = Result<ConfigManagerDataResponse, &'static str>;
fn handle(
&mut self,
msg: ConfigManagerDataMessage,
_ctx: &mut Self::Context,
) -> Self::Result {
use ConfigManagerDataResponse::{GotValue, SetValue};
match msg {
ConfigManagerDataMessage::GetValue(val) => {
Ok(GotValue(self.get_value(val)?))
}
ConfigManagerDataMessage::SetValue(_, _) => Ok(SetValue),
ConfigManagerDataMessage::SoftSetValue(_, _) => Ok(SetValue),
}
}
}

View File

@ -0,0 +1,22 @@
use crate::config_manager::types::ConfigValue;
use actix::{Message, MessageResponse};
#[derive(Message)]
#[rtype(result = "()")]
pub enum ConfigManagerOutput {
ConfigUpdated(String, ConfigValue),
}
#[derive(Message)]
#[rtype(result = "Result<ConfigManagerDataResponse, &'static str>")]
pub enum ConfigManagerDataMessage {
GetValue(String),
SetValue(String, ConfigValue),
SoftSetValue(String, ConfigValue),
}
#[derive(MessageResponse)]
pub enum ConfigManagerDataResponse {
GotValue(ConfigValue),
SetValue,
}

View File

@ -0,0 +1,7 @@
mod config_manager;
mod types;
mod messages;
pub(crate) use messages::{ConfigManagerDataResponse, ConfigManagerDataMessage, ConfigManagerOutput};
pub(crate) use config_manager::ConfigManager;
pub(crate) use types::ConfigValue;

View File

@ -0,0 +1,60 @@
use std::collections::{BTreeMap, HashMap};
use std::ops::Index;
use serde::{Serialize, Deserialize};
use toml::value::Value;
pub enum Error {
UnknownField,
IncompatableValue,
NoValue,
}
/// # ConfigValue
/// Each value type that can be used within a config file.
/// gets used when reading and writing to a config file.
#[derive(Clone)]
pub enum ConfigValue {
Dict(BTreeMap<String, Self>),
Array(Vec<Self>),
String(String),
Number(i64),
Float(f64),
Bool(bool),
}
impl From<ConfigValue> for Value {
fn from(v: ConfigValue) -> Self {
match v {
ConfigValue::Dict(dict) => Value::Table(
dict.into_iter().map(|(k,v)| (k, v.into())).collect()
),
ConfigValue::Array(arr) => Value::Array(
arr.into_iter().map(|v| v.into()).collect()
),
ConfigValue::String(s) => Value::String(s),
ConfigValue::Number(n) => Value::Integer(n),
ConfigValue::Float(f) => Value::Float(f),
ConfigValue::Bool(b) => Value::Boolean(b),
}
}
}
impl From<Value> for ConfigValue {
fn from(v: Value) -> Self {
match v {
Value::Table(dict) => ConfigValue::Dict(
dict.into_iter().map(|(k,v)| (k, v.into())).collect()
),
Value::Array(arr) => ConfigValue::Array(
arr.into_iter().map(|v| v.into()).collect()
),
Value::String(s) => ConfigValue::String(s),
Value::Integer(n) => ConfigValue::Number(n),
Value::Float(f) => ConfigValue::Float(f),
Value::Boolean(b) => ConfigValue::Bool(b),
Value::Datetime(d) => ConfigValue::String(d.to_string()),
}
}
}