assistant_daemon 0.1.0

Daemon program for providing many features.
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);
}

/// Manage features
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;

        // features to toggle
        let mut enable: Vec<&String> = vec![];
        let mut disable: Vec<&String> = vec![];
        // features that settings are changed
        let mut changed: Vec<(&String, &serde_json::Value)> = vec![];

        // handle features to enable and disable
        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 => {
                    // assume none is disabled
                    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;
            });
        }
    }
}