use crate::{
error::FlagError,
flag_descriptor::FlagDescriptor,
flag_type::FlagType
};
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{Arc, RwLock}
};
use {
log::info,
serde::{Serialize, Deserialize}
};
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub(crate) struct Inner {
pub(crate) binary_flags: u8,
pub(crate) hex_flags: u64,
pub(crate) enum_flags: u8,
pub(crate) flags: HashMap<String, FlagDescriptor<u64>>,
}
#[derive(Serialize, Deserialize)]
struct FlagManagerShadow {
state: Inner,
file_path: Option<PathBuf>,
file_extension_flags: HashMap<String, Vec<FlagDescriptor<u64>>>,
extension_flags: HashMap<String, Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct FlagManager {
pub(crate) state: Arc<RwLock<Inner>>,
pub(crate) file_path: Option<PathBuf>,
pub(crate) file_extension_flags: HashMap<String, Vec<FlagDescriptor<u64>>>,
pub(crate) extension_flags: HashMap<String, Vec<String>>,
}
impl Serialize for FlagManager {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer
{
let inner = self.state.read().map_err(serde::ser::Error::custom)?;
let shadow = FlagManagerShadow {
state: inner.clone(),
file_path: self.file_path.clone(),
file_extension_flags: self.file_extension_flags.clone(),
extension_flags: self.extension_flags.clone(),
};
shadow.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for FlagManager {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de>
{
let shadow = FlagManagerShadow::deserialize(deserializer)?;
Ok(Self {
state: Arc::new(RwLock::new(shadow.state)),
file_path: shadow.file_path,
file_extension_flags: shadow.file_extension_flags,
extension_flags: shadow.extension_flags,
})
}
}
impl FlagManager {
pub fn new() -> Self {
Self {
state: Arc::new(RwLock::new(Inner::default())),
file_path: None,
file_extension_flags: HashMap::new(),
extension_flags: HashMap::new(),
}
}
pub fn add_extension_flag<S1, S2>(
mut self, extension: S1, name: S2, flag_type: FlagType, value: u64
) -> Self where S1: Into<String>, S2: Into<String> {
let descriptor = FlagDescriptor::new(name.into(), flag_type, value, None, None);
self.file_extension_flags
.entry(extension.into())
.or_insert_with(Vec::new)
.push(descriptor);
self
}
pub fn with_file_path(mut self, path: impl Into<PathBuf>) -> Self {
self.file_path = Some(path.into());
self
}
}
impl FlagManager {
pub fn add_flag(
&self,
name: &str,
flag_type: FlagType,
value: u64,
description: Option<String>,
created_at: Option<String>,
) -> Result<(), FlagError> {
let mut inner = self.state.write().unwrap();
if inner.flags.contains_key(name) {
return Err(FlagError::FlagAlreadyExists);
}
let flag = FlagDescriptor::new(name.to_string(), flag_type, value, description, created_at);
inner.flags.insert(name.to_string(), flag);
info!("Registered flag: {}", name);
Ok(())
}
pub fn set_flag(&self, name: &str) -> Result<(), FlagError> {
let mut inner = self.state.write().unwrap();
let (val, f_type) = inner.flags.get(name)
.map(|f| (f.value, f.flag_type))
.ok_or_else(|| FlagError::NotFound(name.to_string()))?;
match f_type {
FlagType::Binary => inner.binary_flags |= val as u8,
FlagType::Hex => inner.hex_flags |= val,
FlagType::Enum => inner.enum_flags |= val as u8,
}
info!("Set active bit(s) for: {}", name);
Ok(())
}
pub fn check_flag(&self, name: &str) -> Result<bool, FlagError> {
let inner = self.state.read().unwrap();
let flag = inner.flags.get(name).ok_or_else(|| FlagError::NotFound(name.to_string()))?;
let is_set = match flag.flag_type {
FlagType::Binary => (inner.binary_flags & flag.value as u8) != 0,
FlagType::Hex => (inner.hex_flags & flag.value) != 0,
FlagType::Enum => (inner.enum_flags & flag.value as u8) != 0,
};
Ok(is_set)
}
}
#[cfg(not(feature = "inner_only"))]
impl FlagManager {
pub fn save_to_file(&self, path: &Path) -> Result<(), FlagError> {
let encoded = bincode::serialize(self).map_err(|e| FlagError::NotFound(e.to_string()))?;
std::fs::write(path, encoded).map_err(|e| FlagError::NotFound(e.to_string()))?;
Ok(())
}
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, FlagError> {
let path_ref = path.as_ref();
let data = std::fs::read(path_ref).map_err(|e| FlagError::NotFound(e.to_string()))?;
let decoded: Self = bincode::deserialize(&data).map_err(|e| FlagError::NotFound(e.to_string()))?;
Ok(decoded)
}
}
#[cfg(feature = "inner_only")]
impl FlagManager {
pub fn save_inner_to_file(&self, path: &Path) -> Result<(), FlagError> {
let inner = self.state.read().unwrap();
let encoded = bincode::serialize(&*inner)?;
std::fs::write(path, encoded)?;
Ok(())
}
pub fn load_inner_from_file<P: AsRef<Path>>(path: P) -> Result<Self, FlagError> {
let path_ref = path.as_ref();
let data = std::fs::read(path_ref)?;
let decoded: Inner = bincode::deserialize(&data)?;
Ok(Self {
state: Arc::new(RwLock::new(decoded)),
file_path: Some(path_ref.to_path_buf()),
file_extension_flags: HashMap::new(),
extension_flags: HashMap::new(),
})
}
}
impl FlagManager {
pub fn refresh(&self) -> Result<(), FlagError> {
if let Some(ref path) = self.file_path {
let fresh = Self::load_from_file(path)?;
let mut inner = self.state.write().unwrap();
let fresh_inner = fresh.state.read().unwrap();
*inner = fresh_inner.clone();
info!("State refreshed from disk.");
Ok(())
} else {
Err(FlagError::NotFound("no path provided".to_string()))
}
}
pub fn sync_to_disk(&self) -> Result<(), FlagError> {
if let Some(ref path) = self.file_path {
self.save_to_file(path)
} else {
Err(FlagError::NotFound("no path provided".to_string()))
}
}
}