1#![warn(missing_docs)]
2use anyhow::{bail, Context, Result};
4use std::fs;
5use std::path::PathBuf;
6use std::thread::sleep;
7use std::{collections::HashMap, time};
8use tracing::{debug, error, info, warn};
9use tracing_subscriber::prelude::*;
10use tracing_subscriber::{fmt, layer::SubscriberExt, EnvFilter};
11
12pub mod config;
13pub mod mattermost;
14pub mod offtime;
15pub mod state;
16pub mod utils;
17pub mod wifiscan;
18pub use config::{Args, SecretType, WifiStatusConfig};
19pub use mattermost::{BaseSession, MMStatus, Session};
20use offtime::Off;
21pub use state::{Cache, Location, State};
22pub use wifiscan::{WiFi, WifiInterface};
23
24pub fn setup_tracing(args: &Args) -> Result<()> {
27 let fmt_layer = fmt::layer().with_target(false);
28 let filter_layer = EnvFilter::try_new(args.verbose.get_level_filter().to_string()).unwrap();
29
30 tracing_subscriber::registry()
31 .with(filter_layer)
32 .with(fmt_layer)
33 .init();
34 Ok(())
35}
36
37pub fn get_cache(dir: Option<PathBuf>) -> Result<Cache> {
39 let mut state_file_name: PathBuf;
40 if let Some(ref state_dir) = dir {
41 state_file_name = PathBuf::from(state_dir);
42 fs::create_dir_all(&state_dir)
43 .with_context(|| format!("Creating cache dir {:?}", &state_dir))?;
44 } else {
45 bail!("Internal Error, no `state_dir` configured");
46 }
47
48 state_file_name.push("automattermostatus.state");
49 Ok(Cache::new(state_file_name))
50}
51
52pub fn prepare_status(args: &Args) -> Result<HashMap<Location, MMStatus>> {
55 let mut res = HashMap::new();
56 for s in &args.status {
57 let sc: WifiStatusConfig = s.parse().with_context(|| format!("Parsing {}", s))?;
58 debug!("Adding : {:?}", sc);
59 res.insert(
60 Location::Known(sc.wifi_string),
61 MMStatus::new(sc.text, sc.emoji),
62 );
63 }
64 Ok(res)
65}
66
67pub fn create_session(args: &Args) -> Result<Box<dyn BaseSession>> {
69 args.mm_url.as_ref().expect("Mattermost URL is not defined");
70 args.secret_type
71 .as_ref()
72 .expect("Internal Error: secret_type is not defined");
73 args.mm_secret.as_ref().expect("Secret is not defined");
74 let mut session = Session::new(args.mm_url.as_ref().unwrap());
75 let mut session: Box<dyn BaseSession> = match args.secret_type.as_ref().unwrap() {
76 SecretType::Password => Box::new(session.with_credentials(
77 args.mm_user.as_ref().unwrap(),
78 args.mm_secret.as_ref().unwrap(),
79 )),
80 SecretType::Token => Box::new(session.with_token(args.mm_secret.as_ref().unwrap())),
81 };
82 session.login()?;
83 Ok(session)
84}
85
86pub fn get_wifi_and_update_status_loop(
89 args: Args,
90 mut status_dict: HashMap<Location, MMStatus>,
91) -> Result<()> {
92 let cache = get_cache(args.state_dir.to_owned()).context("Reading cached state")?;
93 let mut state = State::new(&cache).context("Creating cache")?;
94 let delay_duration = time::Duration::new(
95 args.delay
96 .expect("Internal error: args.delay shouldn't be None")
97 .into(),
98 0,
99 );
100 let wifi = WiFi::new(
101 &args
102 .interface_name
103 .clone()
104 .expect("Internal error: args.interface_name shouldn't be None"),
105 );
106 if !wifi
107 .is_wifi_enabled()
108 .context("Checking if wifi is enabled")?
109 {
110 error!("wifi is disabled");
111 } else {
112 info!("Wifi is enabled");
113 }
114 let mut session = create_session(&args)?;
115 loop {
116 if !&args.is_off_time() {
117 let ssids = wifi.visible_ssid().context("Getting visible SSIDs")?;
118 debug!("Visible SSIDs {:#?}", ssids);
119 let mut found_ssid = false;
120 for (l, mmstatus) in status_dict.iter_mut() {
122 if let Location::Known(wifi_substring) = l {
123 if ssids.iter().any(|x| x.contains(wifi_substring)) {
124 if wifi_substring.is_empty() {
125 debug!("We do not match against empty SSID reserved for off time");
126 continue;
127 }
128 debug!("known wifi '{}' detected", wifi_substring);
129 found_ssid = true;
130 mmstatus.expires_at(&args.expires_at);
131 if let Err(e) = state.update_status(
132 l.clone(),
133 Some(mmstatus),
134 &mut session,
135 &cache,
136 delay_duration.as_secs(),
137 ) {
138 error!("Fail to update status : {}", e)
139 }
140 break;
141 }
142 }
143 }
144 if !found_ssid {
145 debug!("Unknown wifi");
146 if let Err(e) = state.update_status(
147 Location::Unknown,
148 None,
149 &mut session,
150 &cache,
151 delay_duration.as_secs(),
152 ) {
153 error!("Fail to update status : {}", e)
154 }
155 }
156 } else {
157 let off_location = Location::Known("".to_string());
159 if let Some(offstatus) = status_dict.get_mut(&off_location) {
160 debug!("Setting state for Offtime");
161 if let Err(e) = state.update_status(
162 off_location,
163 Some(offstatus),
164 &mut session,
165 &cache,
166 delay_duration.as_secs(),
167 ) {
168 error!("Fail to update status : {}", e)
169 }
170 }
171 }
172 if let Some(0) = args.delay {
173 break;
174 } else {
175 sleep(delay_duration);
176 }
177 }
178 Ok(())
179}
180
181#[cfg(test)]
182mod get_cache_should {
183 use super::*;
184 use anyhow::anyhow;
185 use test_log::test; #[test]
188 fn panic_when_called_with_none() -> Result<()> {
190 match get_cache(None) {
191 Ok(_) => Err(anyhow!("Expected an error")),
192 Err(e) => {
193 assert_eq!(e.to_string(), "Internal Error, no `state_dir` configured");
194 Ok(())
195 }
196 }
197 }
198}
199
200#[cfg(test)]
201mod prepare_status_should {
202 use super::*;
203 use test_log::test; #[test]
206 fn prepare_expected_status() -> Result<()> {
207 let args = Args {
208 status: vec!["a::b::c", "d::e::f", "::off::off text"]
209 .iter()
210 .map(|s| s.to_string())
211 .collect(),
212 mm_secret: Some("AAA".to_string()),
213 ..Default::default()
214 };
215 let res = prepare_status(&args)?;
216 let mut expected: HashMap<state::Location, mattermost::MMStatus> = HashMap::new();
217 expected.insert(
218 Location::Known("".to_string()),
219 MMStatus::new("off text".to_string(), "off".to_string()),
220 );
221 expected.insert(
222 Location::Known("a".to_string()),
223 MMStatus::new("c".to_string(), "b".to_string()),
224 );
225 expected.insert(
226 Location::Known("d".to_string()),
227 MMStatus::new("f".to_string(), "e".to_string()),
228 );
229 assert_eq!(res, expected);
230 Ok(())
231 }
232}
233
234#[cfg(test)]
235mod create_session_should {
236 use super::*;
237 #[test]
238 #[should_panic(expected = "Mattermost URL is not defined")]
239 fn panic_when_mm_url_is_none() {
240 let args = Args {
241 status: vec!["a::b::c".to_string()],
242 mm_secret: Some("AAA".to_string()),
243 mm_url: None,
244 ..Default::default()
245 };
246 let _res = create_session(&args);
247 }
248}
249
250#[cfg(test)]
251mod main_loop_should {
252 use super::*;
253
254 #[test]
255 #[should_panic(expected = "Internal error: args.delay shouldn't be None")]
256 fn panic_when_args_delay_is_none() {
257 let args = Args {
258 status: vec!["a::b::c".to_string()],
259 delay: None,
260 ..Default::default()
261 };
262 let _res = get_wifi_and_update_status_loop(args, HashMap::new());
263 }
264}