use super::{
types::{FileData, FileInfo, Snapshot},
utils::{get_mtime, make_error_mapper},
};
use crate::{Detector, Error, ErrorKind, Platform, State};
use std::{path::PathBuf, time::Duration};
#[allow(async_fn_in_trait)]
pub trait WatcherHandler {
async fn on_snapshot(&self, snapshot: Snapshot, state: State) -> Result<(), Error>;
async fn on_error(&self, error: Error);
}
pub struct Watcher<H: WatcherHandler> {
files: Vec<FileInfo>,
poll_interval: u64,
handler: H,
platform: Platform,
}
impl<H: WatcherHandler> Watcher<H> {
pub async fn new(platform: Platform, poll_interval: u64, handler: H) -> Result<Self, Error> {
let mut files = Vec::new();
for path in get_files_to_monitor(platform) {
let mtime = get_mtime(&path)
.await
.map_err(make_error_mapper(ErrorKind::ErrorInitializingWatcher))?;
files.push(FileInfo { path, mtime });
}
Ok(Self {
files,
poll_interval,
handler,
platform,
})
}
pub async fn watch(&mut self) {
let mut last_state = Detector::check(self.platform).await;
loop {
let mut should_upload = self.check_files_for_changes().await;
let current_state = Detector::check(self.platform).await;
if last_state == State::Draft && current_state == State::Applied {
should_upload = true;
}
last_state = current_state;
if should_upload {
self.handle_snapshot().await;
}
tokio::time::sleep(Duration::from_millis(self.poll_interval)).await;
}
}
pub async fn check_files_for_changes(&mut self) -> bool {
let mut should_upload = false;
for file in &mut self.files {
match get_mtime(&file.path).await {
Ok(current) if current > file.mtime => {
file.mtime = current;
should_upload = true;
}
Err(err) => {
self.handler.on_error(err).await;
}
_ => {}
}
}
should_upload
}
pub async fn handle_snapshot(&mut self) {
match self.snapshot().await {
Ok(snapshot) => {
let state = Detector::check(self.platform).await;
if let Err(err) = self.handler.on_snapshot(snapshot, state).await {
self.handler.on_error(err).await;
}
}
Err(err) => {
self.handler.on_error(err).await;
}
}
}
pub async fn snapshot(&self) -> Result<Snapshot, Error> {
let mut snapshot = Snapshot::new();
for file in &self.files {
let content = tokio::fs::read(&file.path)
.await
.map_err(make_error_mapper(ErrorKind::ErrorReadingFile))?;
let filename = file
.path
.file_name()
.unwrap_or(file.path.as_os_str())
.to_string_lossy()
.into_owned();
snapshot.push(FileData { filename, content });
}
Ok(snapshot)
}
pub async fn force_capture_and_dispatch(&self) -> Result<(), Error> {
let snapshot = self.snapshot().await?;
let state = Detector::check(self.platform).await;
let result = self.handler.on_snapshot(snapshot, state).await;
if result.is_err() {
self.handler
.on_error(result.as_ref().unwrap_err().clone())
.await
}
result
}
}
fn get_files_to_monitor(platform: Platform) -> Vec<PathBuf> {
match platform {
Platform::PfSense | Platform::OPNsense => vec![PathBuf::from("/conf/config.xml")],
}
}