1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Author: D.S. Ljungmark <spider@skuggor.se>, Modio AB
// SPDX-License-Identifier: AGPL-3.0-or-later
use log::{debug, warn};
use std::path::Path;

#[derive(Clone)]
pub struct Timefail {
    timefail_path: Box<Path>,
    timeadjust_path: Box<Path>,
}

const TIMEFAIL: &str = "/run/state/timefail";
const TIMEADJUST: &str = "/run/state/time_adjust";

const DEVEL_TIMEFAIL: &str = "development/timefail";
const DEVEL_TIMEADJUST: &str = "development/time_adjust";
use std::default::Default;

impl Timefail {
    #[must_use]
    pub fn new(timefail_path: Box<Path>, timeadjust_path: Box<Path>) -> Self {
        debug!("timefail path: {:?}", &timefail_path);
        debug!("timeadjust path: {:?}", &timeadjust_path);

        let tf = Self {
            timefail_path,
            timeadjust_path,
        };

        if tf.is_timefail() {
            warn!(
                "Timefail flag {:?} exists, clock is assumed wrong.",
                &tf.timefail_path
            );
        };
        tf
    }
}
impl Default for Timefail {
    #[must_use]
    // Create a default timefail object
    fn default() -> Self {
        let timefail_path = Path::new(TIMEFAIL);
        let timeadjust_path = Path::new(TIMEADJUST);
        Self::new(timefail_path.into(), timeadjust_path.into())
    }
}
impl Timefail {
    // Create a development path timefail object
    #[must_use]
    pub fn development() -> Self {
        let timefail_path = Path::new(DEVEL_TIMEFAIL);
        let timeadjust_path = Path::new(DEVEL_TIMEADJUST);
        Self::new(timefail_path.into(), timeadjust_path.into())
    }
    // Create a development path timefail object
    #[must_use]
    pub fn session(state: bool) -> Self {
        if state {
            Self::development()
        } else {
            Self::default()
        }
    }

    #[must_use]
    pub fn is_timefail(&self) -> bool {
        self.timefail_path.is_file()
    }

    #[must_use]
    pub fn is_adjust(&self) -> bool {
        self.timeadjust_path.is_file()
    }

    pub async fn get_adjust(&self) -> Option<f32> {
        use std::str::FromStr;
        use tokio::fs;
        let path = self.timeadjust_path.to_path_buf();
        fs::read_to_string(path)
            .await
            .ok()
            .and_then(|adjstr| f32::from_str(&adjstr).ok())
    }

    pub async fn remove_adjust(&self) -> Result<(), std::io::Error> {
        use tokio::fs;
        let path = self.timeadjust_path.to_path_buf();
        fs::remove_file(path).await
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;
    use crate::testing::Tempbase;
    use std::error::Error;
    use tokio::fs::File;
    use tokio::io::AsyncWriteExt;

    #[tokio::test]
    async fn test_parse_adjust_nofile() -> Result<(), Box<dyn Error>> {
        let tbase = Tempbase::new();
        let timefail = Timefail::new(tbase.timefail_path(), tbase.timeadjust_path());
        assert_eq!(timefail.get_adjust().await, None);
        Ok(())
    }

    #[tokio::test]
    async fn test_parse_adjust_empty() -> Result<(), Box<dyn Error>> {
        let tbase = Tempbase::new();
        let adjust_file = tbase.timeadjust_path().to_path_buf();
        let file = File::create(adjust_file).await?;
        let timefail = Timefail::new(tbase.timefail_path(), tbase.timeadjust_path());
        file.sync_all().await?;
        assert_eq!(timefail.get_adjust().await, None);
        Ok(())
    }

    #[tokio::test]
    async fn test_parse_adjust_strings() -> Result<(), Box<dyn Error>> {
        let tbase = Tempbase::new();
        let adjust_file = tbase.timeadjust_path().to_path_buf();
        let mut file = File::create(adjust_file).await?;
        let timefail = Timefail::new(tbase.timefail_path(), tbase.timeadjust_path());

        file.write_all(b"abc123").await?;
        file.sync_all().await?;
        assert_eq!(timefail.get_adjust().await, None);
        Ok(())
    }
    #[tokio::test]
    async fn test_parse_adjust_happy() -> Result<(), Box<dyn Error>> {
        let tbase = Tempbase::new();
        let mut file = File::create(tbase.timeadjust_path().to_path_buf()).await?;
        let timefail = Timefail::new(tbase.timefail_path(), tbase.timeadjust_path());

        file.write_all(b"1").await?;
        file.sync_all().await?;
        assert_eq!(timefail.get_adjust().await, Some(1.0));
        Ok(())
    }
}