use std::path::{Path, PathBuf}; use notify::{RecommendedWatcher, Watcher}; use crate::event::Event; use crate::monitored_files::MonitoredFiles; use crate::notify_sender::NotifySender; use crate::util::strip_path_prefix; /// A directory monitor. /// /// This will setup the monitoring of all the sub directories and files /// of the given directory. pub struct DirMonitor { /// The directory to monitor. dir: PathBuf, /// The files that are being monitored within the directory. monitored_files: MonitoredFiles, } impl DirMonitor { /// Create a new directory monitor for the desired directory. /// /// monitor_path: A string representation of directory to monitor. pub fn new(monitor_path: &str) -> Self { DirMonitor { dir: PathBuf::from(monitor_path), monitored_files: MonitoredFiles::new() } } /// Scan the monitored directory and all of its sub directories and monitors /// the files found and their last modification time. pub fn scan(&mut self) { // Start from the directory and add each file to our list, // then recurse through all sub directories and add them to // the list and repeat. match scan_dir(&self.dir, &mut self.monitored_files, &self.dir) { Ok(_) => { } Err(e) => { eprintln!("Directory scanning error: {}", e); } } } /// Print out all the monitored files and their last modified date. /// /// Each file entry will be printed out as: /// ``` /// [Date Time] PATH /// ``` pub fn print_monitored_files(&self) { println!("{}", self.monitored_files); } /// Start monitoring the directory asyncronously and process changes within the directory /// until a termination message is received. pub async fn monitor(&mut self, mut term_receiver: tokio::sync::watch::Receiver) { let mut running: bool = true; // Setup the notify crate to watch the INBOX directory. let tokio_runtime = tokio::runtime::Handle::current(); let (notify_sender, mut notify_receiver) = tokio::sync::mpsc::channel::(10); let wrapped_sender = NotifySender::new(tokio_runtime, notify_sender); let mut fs_watcher: RecommendedWatcher = match notify::recommended_watcher(wrapped_sender) { Ok(watcher) => { watcher } Err(e) => { // Just panic because we cannot watch the directories so the program // would be useless and this saves from having to press CTRL-C. eprintln!("Unable to create watcher: {}", e); panic!(); } }; if let Err(e) = fs_watcher.watch(&self.dir, notify::RecursiveMode::Recursive) { // Just panic because we cannot watch the directories so the program // would be useless and this saves from having to press CTRL-C. eprintln!("Error trying to watch the directory: {}", e); panic!(); } // Loop until termination processing events from the notify watcher. while running { // We are listening for messages from either the notify receiver or // the termination receiver. When ever one of them comes across we // will process it. tokio::select! { // Handle listening for the notify watcher events. // These are the changes that we care about from the file system. Some(mut event) = notify_receiver.recv() => { event.make_paths_relative(&self.dir); println!("{}", event); self.monitored_files.process_event(event); } // Handle listening for the termination message, the boolean value will // be changed to false when we are meant to terminate. _ = term_receiver.changed() => { running = *term_receiver.borrow_and_update(); } } } } } /// Scans a directory, and all of its sub directories, and creates a list of the files /// inside and their last modification time. fn scan_dir(base_dir: &Path, monitored_files: &mut MonitoredFiles, dir: &Path) -> std::io::Result<()> { let mut dir_list: Vec = Vec::new(); for file in std::fs::read_dir(dir)? { let file = file?; let meta = file.metadata()?; // Handle directory and file types. // // Directories will be added to the list and later recursively scanned. // // Files will be added to the list of monitored files and their // last modification time will be stored. // // TODO: Handle symlinks. Symlinks can be either a file or another directory. if meta.is_dir() { dir_list.push(file.path().clone()); } else if meta.is_file() { let last_mod_time: std::time::SystemTime = meta.modified()?; monitored_files.add(&strip_path_prefix(&file.path(), base_dir), last_mod_time); } } // Recursively scan the sub directories. for sub_dir in dir_list { scan_dir(base_dir, monitored_files, &sub_dir)?; } Ok(()) }