NOSHP-Client 0.3.3

The Client library for Nik's Open-source Smart Home Platform
Documentation
use std::{fs, path::Path};

use serde::{Deserialize, Serialize};
use toml::Table;

use crate::client_types::types::{DeviceCapabilityStatus, DeviceCapabilityType};

#[derive(Serialize, Deserialize)]
pub struct ClientConfig {
    pub server_ip: Option<String>,
    pub device_name: String,
    pub capability: Table,
}

#[derive(Clone)]
pub struct ParsedConfig {
    pub server_ip: Option<String>,
    pub device_name: String,
    pub capabilities: Vec<DeviceCapabilityStatus>,
}

impl ClientConfig {
    pub fn load_config(path: &str) -> anyhow::Result<ParsedConfig> {
        let config_path = Path::new(path);

        if config_path.exists() {
            let content = fs::read_to_string(config_path)?;
            let config: ClientConfig = toml::from_str(&content)?;

            let capabilities = config.capability.into_iter().map(|(name, value)|{
                let value = match value {
                    toml::Value::Table(v) => v,
                    _ => {
                        println!("capability {name} in config file has invalid fields, expected a table as contents");
                        Table::new()
                    }
                };

                let available = match value.get("available") {
                    Some(v) => v.clone(),
                    None => toml::Value::Boolean(false)
                };

                let available = match available {
                    toml::Value::Boolean(v) => v,
                    _ => {
                        eprintln!("Capability {name} in config file has invalid fields, expected a boolean for available field");
                        false
                    }
                };

                let capa_type = match value.get("type") {
                    Some(v) => v.clone(),
                    None => {
                        eprintln!("Capability {name} in config file is missing a type, defaulting to button");
                        toml::Value::Boolean(false)
                    }
                };

                let capa_type = match capa_type {
                    toml::Value::String(v) => v,
                    _ => String::from("button")
                };

                let capa_type = match &*capa_type {
                    "button" => DeviceCapabilityType::Button,
                    "slider" => DeviceCapabilityType::Slider,
                    _ => {
                        eprintln!("Unrecognized capability type for capability {name}, defaulting to button");
                        DeviceCapabilityType::Button
                    }
                };

                DeviceCapabilityStatus {
                    capability: name,
                    available,
                    r#type: capa_type as i32,
                    value: None
                }
            }).collect();

            return Ok(ParsedConfig {
                server_ip: config.server_ip,
                device_name: config.device_name,
                capabilities,
            });
        }

        println!("No config found, generating a default config");
        let config = ClientConfig::default();
        let toml = toml::to_string(&config)?;

        fs::write(config_path, toml)?;
        return Ok(ParsedConfig {
            server_ip: None,
            device_name: config.device_name,
            capabilities: Vec::new(),
        });
    }
}

impl Default for ClientConfig {
    fn default() -> Self {
        Self {
            server_ip: Some(String::from(
                "Your server IP here, or nothing if you want to set it programmatically",
            )),
            device_name: String::from("Unnamed Device"),
            capability: Table::new(),
        }
    }
}

impl Default for ParsedConfig {
    fn default() -> Self {
        Self {
            server_ip: None,
            device_name: String::from("Unnamed Device"),
            capabilities: Vec::new(),
        }
    }
}