libfancontrold 0.4.0

Base library for fancontrold.
Documentation
mod error;

pub use error::{Error, Result};

use super::Address;

use std::{
    cmp::Ordering,
    collections::BTreeMap,
    convert::TryFrom,
    path::{Path, PathBuf},
};

use libmedium::units::PwmMode;
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum MultiTempFunction {
    Min,
    #[default]
    Max,
    Average,
}

#[derive(Serialize, Deserialize, Eq, PartialEq)]
#[serde(remote = "PwmMode")]
enum PwmModeDef {
    Dc,
    Pwm,
    Automatic,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct ChannelConfig {
    pub hwmon_device_path: PathBuf,
    pub index: u16,
    pub curve: BTreeMap<i32, u8>,
    pub temps: Vec<Address>,
    #[serde(default)]
    pub virtual_temps: Vec<PathBuf>,
    #[serde(default)]
    pub mtf: MultiTempFunction,
    #[serde(default = "default_min_pwm")]
    pub min_pwm: u8,
    #[serde(default = "default_min_start")]
    pub min_start: u8,
    #[serde(default, with = "PwmModeDef")]
    pub pwm_mode: PwmMode,
    #[serde(default = "default_average")]
    pub average: u8,
}

impl ChannelConfig {
    pub fn new(
        hwmon_device_path: impl Into<PathBuf>,
        index: u16,
        curve: BTreeMap<i32, u8>,
        temps: Vec<Address>,
        virtual_temps: Vec<PathBuf>,
    ) -> Self {
        ChannelConfig {
            hwmon_device_path: hwmon_device_path.into(),
            index,
            curve,
            temps,
            virtual_temps,
            mtf: Default::default(),
            min_pwm: default_min_pwm(),
            min_start: default_min_start(),
            pwm_mode: Default::default(),
            average: default_average(),
        }
    }

    pub fn check(&self) -> Vec<Error> {
        let mut errors = Vec::new();

        if self.temps.is_empty() && self.virtual_temps.is_empty() {
            errors.push(Error::new_empty_temps(self.address()));
        }
        if self.index == 0 {
            errors.push(Error::new_index(self.address()));
        }
        if self.average < 1 || self.average > 10 {
            errors.push(Error::new_average(self.address()));
        }
        if self.curve.is_empty() {
            errors.push(Error::new_empty_curve(self.address()));
        };

        errors
    }

    pub fn sort(&mut self) {
        self.temps.sort();
        self.virtual_temps.sort();
    }

    pub fn address(&self) -> Address {
        Address::new(&self.hwmon_device_path, self.index)
    }
}

impl Ord for ChannelConfig {
    fn cmp(&self, other: &Self) -> Ordering {
        self.hwmon_device_path
            .cmp(&other.hwmon_device_path)
            .then(self.index.cmp(&other.index))
    }
}

impl PartialOrd for ChannelConfig {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

fn default_min_start() -> u8 {
    255
}

fn default_min_pwm() -> u8 {
    100
}

fn default_average() -> u8 {
    1
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct Config {
    channels: Vec<ChannelConfig>,
}

impl Config {
    #[cfg(not(feature = "async"))]
    pub fn read(path: impl AsRef<Path>) -> Result<Config> {
        let path = path.as_ref();

        let config = std::fs::read_to_string(path).map_err(|e| Error::new_load(path, e))?;

        Self::parse(config)
    }

    #[cfg(feature = "async")]
    pub async fn read(path: impl AsRef<Path>) -> Result<Config> {
        let path = path.as_ref();

        let config = tokio::fs::read_to_string(path)
            .await
            .map_err(|e| Error::new_load(path, e))?;

        Self::parse(config)
    }

    pub fn parse(config: impl AsRef<str>) -> Result<Config> {
        let mut config: Config = serde_json::from_str(config.as_ref())?;

        let errors = config.check();
        if errors.len() > 0 {
            return Err(Error::from_vec(errors).unwrap());
        }

        // sort pwms in config and make sure there are no duplicates
        config.sort();
        config
            .channels
            .dedup_by(|a, b| a.hwmon_device_path == b.hwmon_device_path && a.index == b.index);

        Ok(config)
    }

    pub fn as_string(&self) -> String {
        serde_json::to_string_pretty(self).unwrap_or_default()
    }

    pub fn channel_configs(&self) -> &[ChannelConfig] {
        &self.channels
    }

    pub fn check(&self) -> Vec<Error> {
        self.channels.iter().fold(Vec::new(), |mut err, channel| {
            let errors = channel.check();
            err.extend(errors);
            err
        })
    }

    pub fn sort(&mut self) {
        self.channels.sort();

        for channel in &mut self.channels {
            channel.sort();
        }
    }
}

impl From<Vec<ChannelConfig>> for Config {
    fn from(mut channel_configs: Vec<ChannelConfig>) -> Self {
        channel_configs.sort();

        Self {
            channels: channel_configs,
        }
    }
}

impl TryFrom<&str> for Config {
    type Error = Error;

    fn try_from(config: &str) -> Result<Self> {
        Config::parse(config)
    }
}

impl TryFrom<String> for Config {
    type Error = Error;

    fn try_from(config: String) -> Result<Self> {
        Config::parse(config)
    }
}