use regex::Regex; use serde::{Deserialize, Serialize}; use snafu::ResultExt; use std::collections::HashMap; use std::fs; use crate::constants::HARDWARE_CONFIG_FILE; use crate::error::{FileReadError, FileWriteError, PeachConfigError}; use crate::utils::get_output; use crate::RtcOption; /// Returns a HashMap of all the peach-packages which are currently installed /// mapped to their version number e.g. { "peach-probe": "1.2.0", "peach-network": "1.4.0" } pub fn get_currently_installed_microservices() -> Result, PeachConfigError> { // gets a list of all packages currently installed with dpkg let packages = get_output(&["dpkg", "-l"])?; // this regex matches packages which contain the word peach in them // and has two match groups // 1. the first match group gets the package name // 2. the second match group gets the version number of the package let re: Regex = Regex::new(r"\S+\s+(\S*peach\S+)\s+(\S+).*\n").unwrap(); // the following iterator, iterates through the captures matched via the regex // and for each capture, creates a value in the hash map, // which maps the name of the package, to its version number // e.g. { "peach-probe": "1.2.0", "peach-network": "1.4.0" } let peach_packages: HashMap = re .captures_iter(&packages) .filter_map(|cap| { let groups = (cap.get(1), cap.get(2)); match groups { (Some(package), Some(version)) => { Some((package.as_str().to_string(), version.as_str().to_string())) } _ => None, } }) .collect(); // finally the hashmap of packages and version numbers is returned Ok(peach_packages) } /// Output form of manifest #[derive(Debug, Serialize, Deserialize)] pub struct Manifest { // packages is a map of {package_name: version} packages: HashMap, hardware: Option, } /// The form that hardware configs are saved in when peach-config setup runs successfully #[derive(Debug, Serialize, Deserialize)] pub struct HardwareConfig { // packages is a map of {package_name: version} i2c: bool, rtc: Option, } /// Log which hardware settings were configured to a .json file /// # Arguments /// /// * `i2c` - a boolean flag, if true i2c will be configured /// * `rtc` - an optional enum, if supplied indicates which real-time-clock model /// is being used /// /// Any error results in a PeachConfigError, otherwise the saved HardwareConfig object /// is returned. pub fn save_hardware_config( i2c: bool, rtc: Option, ) -> Result { let hardware_config = HardwareConfig { i2c, rtc }; let json_str = serde_json::to_string(&hardware_config)?; fs::write(HARDWARE_CONFIG_FILE, json_str).context(FileWriteError { file: HARDWARE_CONFIG_FILE.to_string(), })?; Ok(hardware_config) } /// Load the hardware configs that were saved from the last successful run of peach-config setup /// /// Returns an Ok(Some) containing the configuration if one is found, /// and returns Ok(None) if no hardware configuration was found. fn load_hardware_config() -> Result, PeachConfigError> { // if there is no hardware_config, return None let hardware_config_exists = std::path::Path::new(HARDWARE_CONFIG_FILE).exists(); if !hardware_config_exists { Ok(None) } // otherwise we load hardware_config from json else { let contents = fs::read_to_string(HARDWARE_CONFIG_FILE).context(FileReadError { file: HARDWARE_CONFIG_FILE.to_string(), })?; let hardware_config: HardwareConfig = serde_json::from_str(&contents)?; Ok(Some(hardware_config)) } } /// Outputs a Manifest in json form to stdout /// which contains the currently installed peach packages /// as well as the hardware configuration of the last run of peach-config setup. pub fn generate_manifest() -> Result<(), PeachConfigError> { let packages = get_currently_installed_microservices()?; let hardware_config_option = load_hardware_config()?; let manifest = Manifest { packages, hardware: hardware_config_option, }; let output = serde_json::to_string(&manifest)?; println!("{}", output); Ok(()) }