nullnet_libconfmon/watcher/impl.rs
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
use super::{
types::{FileData, FileInfo, Snapshot},
utils::{get_mtime, make_error_mapper},
};
use crate::{Error, ErrorKind};
use std::{future::Future, path::PathBuf, time::Duration};
/// A file watcher that monitors changes in a list of files and triggers a callback when changes are detected.
///
/// # Generics
/// - `F`: A closure or function that takes a `Snapshot` and produces a future.
/// - `Fut`: The type of the future returned by the callback.
///
/// # Fields
/// - `files`: A list of `FileInfo` containing metadata about the monitored files.
/// - `callback`: A closure or function to execute when changes are detected.
/// - `poll_interval`: The interval (in milliseconds) at which files are polled for changes.
pub struct Watcher<F, Fut>
where
F: Fn(Snapshot) -> Fut,
Fut: Future<Output = ()>,
{
files: Vec<FileInfo>,
callback: F,
poll_interval: u64,
}
impl<F, Fut> Watcher<F, Fut>
where
F: Fn(Snapshot) -> Fut,
Fut: Future<Output = ()>,
{
/// Creates a new `Watcher` instance.
///
/// # Parameters
/// - `paths`: A list of paths to the files to monitor.
/// - `poll_interval`: The interval (in milliseconds) at which files are polled for changes.
/// - `callback`: A closure or function to execute when changes are detected.
///
/// # Returns
/// - `Ok(Self)`: A new instance of `Watcher` if all files are successfully initialized.
/// - `Err(Error)`: An error if any file cannot be initialized (e.g., failed to read metadata).
///
/// # Errors
/// - Returns an error with `ErrorKind::ErrorInitializingWatcher` if a file's metadata cannot be read.
pub async fn new(paths: Vec<PathBuf>, poll_interval: u64, callback: F) -> Result<Self, Error> {
let mut files = Vec::new();
for path in paths {
let mtime = get_mtime(&path)
.await
.map_err(make_error_mapper(ErrorKind::ErrorInitializingWatcher))?;
files.push(FileInfo { path, mtime });
}
Ok(Self {
files,
callback,
poll_interval,
})
}
/// Starts monitoring the files for changes.
///
/// # Behavior
/// - Periodically checks the modification time of each file.
/// - If any file has been modified since the last check, triggers the `callback` with a `Snapshot`.
/// - Continues indefinitely until the task is canceled.
///
/// # Returns
/// - `Ok(())`: This function only exits if the task is canceled or an error occurs.
/// - `Err(Error)`: An error if reading a file's metadata or content fails.
///
/// # Errors
/// - Returns an error with `ErrorKind::ErrorReadingFile` if file metadata or content cannot be read.
pub async fn watch(&mut self) -> Result<(), Error> {
loop {
let mut should_upload = false;
for file in &mut self.files {
let current = get_mtime(&file.path).await?;
if current > file.mtime {
file.mtime = current;
should_upload = true;
}
}
if should_upload {
let snapshot = self.snapshot().await?;
(self.callback)(snapshot).await;
}
tokio::time::sleep(Duration::from_millis(self.poll_interval)).await;
}
}
/// Generates a snapshot of the current state of the monitored files.
///
/// # Returns
/// - `Ok(Snapshot)`: A snapshot containing the data of all monitored files.
/// - `Err(Error)`: An error if any file cannot be read.
///
/// # Errors
/// - Returns an error with `ErrorKind::ErrorReadingFile` if a file cannot be read.
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)
}
}