use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use async_trait::async_trait;
use tokio::sync::RwLock;
use tracing::{debug, info};
use crate::generated::{Feature, FeatureSettings};
#[async_trait]
pub trait FeatureControl: Send + Sync {
fn name(&self) -> &str;
async fn enable(&self, setting: Option<serde_json::Value>) -> Result<()>;
async fn disable(&self) -> Result<()>;
async fn update(
&self,
old_setting: Option<serde_json::Value>,
setting: Option<serde_json::Value>,
) -> Result<()>;
async fn lid_change(&self, open: bool);
}
#[async_trait]
pub trait FeatureManager: Send + Sync {
async fn register(&self, feature: Arc<dyn FeatureControl>);
async fn update_config(
&self,
old_config: Option<serde_json::Value>,
new_config: serde_json::Value,
);
async fn lid_change(&self, open: bool);
}
pub struct FeatureManagerImpl {
features: RwLock<HashMap<String, Arc<dyn FeatureControl>>>,
}
impl FeatureManagerImpl {
pub fn new() -> Self {
Self {
features: RwLock::new(HashMap::new()),
}
}
}
fn get_features_from_config(config: &serde_json::Value) -> HashMap<String, Feature> {
let mut ret = HashMap::new();
match config.get("features") {
Some(ft) => match ft.as_object() {
Some(ftobj) => {
for (f, fobj) in ftobj {
ret.insert(f.clone(), serde_json::from_value(fobj.clone()).unwrap());
}
ret
}
None => ret,
},
None => ret,
}
}
fn get_settings_from_config(
config: &serde_json::Value,
) -> serde_json::Map<String, serde_json::Value> {
match config.get("settings") {
Some(ft) => match ft.as_object() {
Some(ftobj) => ftobj.clone(),
None => Default::default(),
},
None => Default::default(),
}
}
#[async_trait]
impl FeatureManager for FeatureManagerImpl {
async fn register(&self, feature: Arc<dyn FeatureControl>) {
let mut features = self.features.write().await;
features.insert(feature.name().to_string(), feature.clone());
info!("registered feature '{}'", feature.name());
}
async fn update_config(
&self,
old_config: Option<serde_json::Value>,
new_config: serde_json::Value,
) {
let old_settings = if old_config.is_none() {
Default::default()
} else {
get_settings_from_config(old_config.as_ref().unwrap())
};
let old_features = if old_config.is_none() {
Default::default()
} else {
get_features_from_config(old_config.as_ref().unwrap())
};
let new_settings = get_settings_from_config(&new_config);
let new_features = get_features_from_config(&new_config);
let feature_ctrls_lock = self.features.write().await;
let mut enable: Vec<&String> = vec![];
let mut disable: Vec<&String> = vec![];
let mut changed: Vec<(&String, &serde_json::Value)> = vec![];
for (f, fobj) in &new_features {
match old_features.get(f) {
Some(old_fobj) => {
if fobj.status {
if !old_fobj.status {
enable.push(f)
}
} else {
if old_fobj.status {
disable.push(f)
}
}
}
None => {
if fobj.status {
enable.push(f)
}
}
}
}
for f in enable {
match feature_ctrls_lock.get(f) {
Some(fc) => {
let settings = new_settings.get(f);
tokio::spawn({
let settings = settings.cloned();
let fc = fc.clone();
let f = f.clone();
debug!("enabling feature: {} with settings {:?}", f, settings);
async move {
match fc.enable(settings).await {
Ok(_) => {
tracing::info!("enabled feature {}", f)
}
Err(err) => {
tracing::error!("failed to enable feature {}: {}", f, err)
}
};
}
});
}
None => {
tracing::error!("try to enable non-exist feature {}", f)
}
}
}
for f in disable {
match feature_ctrls_lock.get(f) {
Some(fc) => {
match fc.disable().await {
Ok(_) => {
tracing::info!("disabled feature {}", f)
}
Err(err) => {
tracing::error!("failed to disable feature {}: {}", f, err)
}
};
}
None => {
tracing::error!("try to disable non-exist feature {}", f)
}
}
}
}
async fn lid_change(&self, open: bool) {
info!("lid changed, open {}", open);
for (f, fc) in &*self.features.read().await {
let fc = fc.clone();
tokio::spawn(async move {
fc.lid_change(open).await;
});
}
}
}