luftpost 1.0.0

Watches luftdaten.info particulates sensors and sends E-Mails if measurements exceed thresholds
Documentation
use sensor::SensorId;
use serde_json;
use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};

error_chain! {
    errors {
        FailedToLoadState(sensor: String, state_dir: String) {
            description("failed to load state")
            display("failed to load state for sensor '{}' from state directory '{}'", sensor, state_dir)
        }
        FailedToSaveState(sensor: String, state_dir: String) {
            description("failed to save state")
            display("failed to save state of sensor '{}' to state directory '{}'", sensor, state_dir)
        }
    }
    foreign_links {
        Io(::std::io::Error);
        JsonError(::serde_json::Error);
    }
}


#[derive(Debug, Deserialize, Serialize)]
#[derive(PartialEq)]
pub enum AlarmState {
    Normal,
    ThresholdExceeded,
}

#[derive(Debug, Deserialize, Serialize)]
#[derive(PartialEq)]
pub struct SensorState {
    pub sensor_id: SensorId,
    pub alarm_state: AlarmState,
}

impl SensorState {
    pub fn load<P: AsRef<Path>>(sensor_id: &SensorId, state_dir: P) -> Result<SensorState> {
        let fp = create_filepath(sensor_id, state_dir.as_ref());
        load_from_file(fp).chain_err(|| ErrorKind::FailedToLoadState(sensor_id.clone(), state_dir.as_ref().to_string_lossy().to_string()))
    }

    pub fn save<P: AsRef<Path>>(&self, state_dir: P) -> Result<()> {
        let fp = create_filepath(&self.sensor_id, state_dir.as_ref());
        save_state_to_file(self, fp).chain_err(|| ErrorKind::FailedToSaveState(self.sensor_id.clone(), state_dir.as_ref().to_string_lossy().to_string()))
    }
}

fn create_filepath<P: AsRef<Path>>(sensor_id: &SensorId, state_dir: P) -> PathBuf {
    let mut pb = state_dir.as_ref().to_path_buf();
    let filename = format!("{}.json", sensor_id);
    pb.push(filename);

    pb
}

fn load_from_file<P: AsRef<Path>>(file_path: P) -> Result<SensorState> {
    let mut file = File::open(file_path)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;

    let state = serde_json::from_str(&content)?;

    Ok(state)
}

fn save_state_to_file<P: AsRef<Path>>(state: &SensorState, file_path: P) -> Result<()> {
    let content = serde_json::to_string(state) ?;

    let mut file = File::create(file_path) ?;
    file.write_all(content.as_bytes()) ?;

    Ok(())
}


    #[cfg(test)]
mod test {
    use super::*;
    use mktemp::Temp;

    #[test]
    pub fn save_state_to_file_ok() -> () {
        let sensor_state = SensorState { sensor_id: "123456789".to_string() , alarm_state: AlarmState::Normal };

        let file = Temp::new_file().unwrap().to_path_buf();
        let res = save_state_to_file(&sensor_state, file);
        assert!(res.is_ok());
    }

    #[test]
    pub fn load_from_file_ok() -> () {
        let expected_sensor_state = SensorState { sensor_id: "123456789".to_string() , alarm_state: AlarmState::ThresholdExceeded};
        let file = Temp::new_file().unwrap().to_path_buf();
        let res = save_state_to_file(&expected_sensor_state,&file);
        assert!(res.is_ok());

        let res = load_from_file(&file);
        assert!(res.is_ok());
        assert_eq!(res.unwrap(), expected_sensor_state);
    }
}