Completed all steps except the monitoring step.

Looking at switching the monitored files into a hashmap.
This commit is contained in:
2025-03-19 13:17:26 -04:00
parent 5c98c8a608
commit 52c5d40420
6 changed files with 618 additions and 31 deletions

View File

@ -1,5 +1,7 @@
use std::path::{Path, PathBuf};
use notify::{EventKind, RecommendedWatcher, Watcher};
use crate::monitored_files::MonitoredFiles;
@ -11,9 +13,9 @@ use crate::monitored_files::MonitoredFiles;
pub struct DirMonitor
{
/// The directory to monitor.
dir: std::path::PathBuf,
dir: PathBuf,
monitored_files: MonitoredFiles
monitored_files: MonitoredFiles,
}
@ -31,12 +33,9 @@ impl DirMonitor
}
}
pub fn monitor(&mut self)
{
self.build_file_list();
}
fn build_file_list(&mut self)
/// 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
@ -49,8 +48,93 @@ impl DirMonitor
Err(e) =>
{
println!("There was an issue during directory scanning.");
println!("{}", 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)
{
self.monitored_files.print(&self.dir);
}
///
pub async fn monitor(&mut self, mut term_receiver: tokio::sync::watch::Receiver<bool>)
{
let mut running: bool = true;
//let (notify_sender, notify_receiver) = std::sync::mpsc::channel::<Result<notify::Event, notify::Error>>();
let (notify_sender, notify_receiver) = tokio::sync::mpsc::channel::<Result<notify::Event, notify::Error>>(100);
let mut fs_watcher: RecommendedWatcher = match notify::recommended_watcher(notify_sender)
{
Ok(watcher) => { watcher }
Err(e) => { eprintln!("Unable to create watcher: {}", e); panic!(); }
};
fs_watcher.watch(&self.dir, notify::RecursiveMode::Recursive).unwrap();
while running
{
// Listen for file changes until termination signal is received.
match notify_receiver.recv().await
{
Ok(msg) =>
{
match msg
{
Ok(event) =>
{
match event.kind
{
EventKind::Create(_) =>
{
println!("File Created: {:?}", event.paths);
}
EventKind::Modify(_) =>
{
println!("File Modified: {:?}", event.paths);
}
EventKind::Remove(_) =>
{
println!("File Removed: {:?}", event.paths);
}
_ =>
{
}
}
}
Err(e) =>
{
eprintln!("{}", e);
}
}
}
Err(e) =>
{
eprintln!("Error receiving notify event: {}", e);
}
}
// Handle listening for the termination message, the boolean value will
// be changed to false when we are meant to terminate.
match term_receiver.has_changed()
{
Ok(_) =>
{
running = *term_receiver.borrow_and_update();
}
Err(e) =>
{
eprintln!("Unable to receive: {}", e);
}
}
}
}
@ -58,6 +142,8 @@ impl DirMonitor
/// 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(monitored_files: &mut MonitoredFiles, dir: &Path) -> std::io::Result<()>
{
let mut dir_list: Vec<PathBuf> = Vec::new();
@ -65,12 +151,29 @@ fn scan_dir(monitored_files: &mut MonitoredFiles, dir: &Path) -> std::io::Result
for file in std::fs::read_dir(dir)?
{
let file = file?;
if file.file_type()?.is_dir()
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(&file.path(), last_mod_time);
}
}
// Recursively scan the sub directories.
for sub_dir in dir_list
{
scan_dir(monitored_files, &sub_dir)?;

View File

@ -1,8 +1,36 @@
enum Event
use std::time::SystemTime;
///
pub enum Event
{
New,
Modify,
///
New
{
///
path: String,
///
mod_time: SystemTime
},
///
Modify
{
///
path: String,
///
mod_time: SystemTime
},
///
Delete
{
///
path: String
}
}
@ -12,17 +40,17 @@ impl std::fmt::Display for Event
{
match self
{
Event::New =>
Event::New(_) =>
{
write!(f, "NEW")
write!(f, "[NEW] {}", self.path)
}
Event::Modify =>
Event::Modify(_) =>
{
write!(f, "MOD")
write!(f, "[MOD] {}", self.path)
}
Event::Delete =>
{
write!(f, "DEL")
write!(f, "[DEL] {}", self.path)
}
}
}

View File

@ -1,4 +1,4 @@
//! Rust challenge
//! Rust challenge: FileMonitor
@ -10,6 +10,8 @@ mod monitored_files;
use clap::Parser;
use tokio::sync::watch;
use crate::dir_monitor::DirMonitor;
@ -30,13 +32,94 @@ struct Options
/// Creates the given directory, and its parents, if it does not exist.
fn create_directory(dir: &str) -> bool
{
match std::fs::create_dir_all(dir)
{
Ok(_) =>
{
true
}
Err(e) =>
{
eprintln!("Creating directory error: {}", e);
false
}
}
}
/// Asynchronously listen for CTRL-C to be pressed.
///
/// This will send a false message across the channel
/// when it detects the termination signal.
async fn listen_for_termination(sender: watch::Sender<bool>)
{
tokio::signal::ctrl_c().await.unwrap();
// CTRL-C was pressed so send a message to everyone that is
// watching that the program is to be terminated. Then close the
// sender.
sender.send_replace(false);
sender.closed().await;
}
/// The usual starting point of your project.
fn main()
#[tokio::main]
async fn main()
{
let options: Options = Options::parse();
println!("Inbox: `{}`", options.inbox_dir);
if create_directory(&options.inbox_dir)
{
// This is our keep running channel. False will be sent
// when the application is meant to be closed.
let (sender, receiver) = watch::channel(true);
let mut directory_monitor: DirMonitor = DirMonitor::new(&options.inbox_dir);
directory_monitor.monitor();
let mut directory_monitor: DirMonitor = DirMonitor::new(&options.inbox_dir);
// Program workflow step 1.
// Async is not needed here. This can be done syncronously because
// file systems are not async and the spawn_blocking blocking overhead
// isn't needed.
directory_monitor.scan();
directory_monitor.print_monitored_files();
// Run our directory monitor.
let mon_task = tokio::spawn(async move
{
// Program workflow step 2 (terminated by step 3).
// This can be done async because we are waiting on
// events to be loaded from the notify library and
// we want this to run continously and not block the
// program. This could also be done on a seperate thread,
// preferably a green thread, but a fiber is sufficient
// and less resource intensive.
directory_monitor.monitor(receiver).await;
});
// Run until Ctrl-C is pressed.
// Once it is pressed it will send a message to stop.
let term_task = tokio::spawn(async move
{
// Program workflow step 3.
// Async with message passing so it can run and listen
// for the termination signal. It uses a watch channel
// because any task that will need to know when the
// program is to end can then catch the change.
listen_for_termination(sender).await;
});
// Only need to wait for the monitor task to finish because it will only
// finish once the termination task has finished.
mon_task.await;
// Program workflow step 4.
// Async is not needed here. This can be done syncronously because it
// is the final/clean up step of the program.
directory_monitor.print_monitored_files();
}
}

View File

@ -1,14 +1,24 @@
use std::path::{PathBuf, Path};
use std::time::SystemTime;
use chrono::{DateTime, Local};
use crate::event::Event;
///
pub struct MonitoredFiles
{
///
mod_dates: Vec<Option<std::time::SystemTime>>,
mod_dates: Vec<SystemTime>,
///
paths: Vec<Option<std::path::PathBuf>>
paths: Vec<PathBuf>
}
impl MonitoredFiles
{
pub fn new() -> Self
@ -19,13 +29,82 @@ impl MonitoredFiles
paths: Vec::new()
}
}
}
impl std::fmt::Display for Point
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
pub fn process_event(&mut self, path: &Path, event: Event)
{
write!(f, "({}, {})", self.x, self.y)
match event
{
Event::New(creation_time) =>
{
self.add(path, creation_time);
}
Event::Modify(mod_time) =>
{
self.modify(&path, mod_time);
}
Event::Delete =>
{
self.remove(&path);
}
}
}
pub fn add(&mut self, path: &Path, time: SystemTime)
{
self.paths.push(PathBuf::from(path));
self.mod_dates.push(time);
}
pub fn modify(&mut self, path: &Path, time: SystemTime)
{
if let Some(index) = self.get_path_index(path)
{
self.mod_dates[index] = time;
}
}
pub fn remove(&mut self, path: &Path)
{
if let Some(index) = self.get_path_index(path)
{
self.mod_dates.remove(index);
self.paths.remove(index);
}
}
pub fn print(&self, base: &Path)
{
for file in 0..self.paths.len()
{
let date_time: DateTime<Local> = DateTime::from(self.mod_dates[file]);
match &self.paths[file].strip_prefix(base)
{
Ok(rel_path) =>
{
println!("[{}] {}", date_time.format("%d/%m/%Y %H:%M"), rel_path.display());
}
Err(e) =>
{
eprintln!("Unable to strip base directory: {}", e);
}
}
}
}
fn get_path_index(&self, path: &Path) -> Option<usize>
{
for (index, stored_path) in self.paths.iter().enumerate()
{
if path == stored_path
{
return Some(index);
}
}
None
}
}