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
use anyhow::{Context, Result};
use chrono::Utc;
use std::fs;
use tracing::{debug, info};
use crate::mattermost::MMStatus;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
const MAX_SECS_BEFORE_FORCE_UPDATE: i64 = 60 * 60;
#[derive(Debug)]
pub struct Cache {
path: PathBuf,
}
impl Cache {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub enum Location {
Known(String),
Unknown,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct State {
location: Location,
timestamp: i64,
}
impl State {
pub fn new(cache: &Cache) -> Result<Self> {
let res: State;
if let Ok(json) = &fs::read(&cache.path) {
res = serde_json::from_str(&String::from_utf8_lossy(json)).context(format!(
"Unable to deserialize state file {:?} (try to remove it)",
&cache.path
))?;
} else {
res = Self {
location: Location::Unknown,
timestamp: 0,
};
}
debug!("Previous known location `{:?}`", res.location);
Ok(res)
}
pub fn set_location(&mut self, location: Location, cache: &Cache) -> Result<()> {
info!("Set location to `{:?}`", location);
self.location = location;
self.timestamp = Utc::now().timestamp();
fs::write(
&cache.path,
serde_json::to_string(&self)
.unwrap_or_else(|_| panic!("Serialization of State Failed :{:?}", &self)),
)
.with_context(|| format!("Writing to cache file {:?}", cache.path))?;
Ok(())
}
pub fn update_status(
&mut self,
current_location: Location,
status: Option<&MMStatus>,
cache: &Cache,
) -> Result<()> {
if current_location == Location::Unknown {
return Ok(());
} else if current_location == self.location {
if Utc::now().timestamp() - self.timestamp <= MAX_SECS_BEFORE_FORCE_UPDATE {
debug!(
"No change for {}s : no update to mattermost status",
MAX_SECS_BEFORE_FORCE_UPDATE
);
return Ok(());
}
}
self.set_location(current_location, cache)?;
status.unwrap().send()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
mod should {
use super::*;
use mktemp::Temp;
#[test]
fn remember_state() -> Result<()> {
let temp = Temp::new_file().unwrap().to_path_buf();
let cache = Cache::new(&temp);
let mut state = State::new(&cache)?;
assert_eq!(state.location, Location::Unknown);
state.set_location(Location::Known("abcd".to_string()), &cache)?;
assert_eq!(state.location, Location::Known("abcd".to_string()));
let mut state = State::new(&cache)?;
assert_eq!(state.location, Location::Known("abcd".to_string()));
state.set_location(Location::Known("work".to_string()), &cache)?;
assert_eq!(state.location, Location::Known("work".to_string()));
Ok(())
}
}
}