Compare commits
2 Commits
84be854186
...
3a0684c33e
Author | SHA1 | Date | |
---|---|---|---|
3a0684c33e | |||
fe7537f238 |
38
README.md
38
README.md
@ -1,33 +1,17 @@
|
|||||||
# FileMonitor
|
# FileMonitor #
|
||||||
|
|
||||||
This was a Rust Challenge that I completed in 17 hours.
|
This was a Rust Challenge that I completed in 17 hours.
|
||||||
|
|
||||||
## Review ##
|
The [document](./docs/rust_coding_challenge_filemonitor.pdf)
|
||||||
I thought I solved the challenge pretty well. I took my time and went back
|
describing the challenge can be found in the docs directory.
|
||||||
and refactored and commented well.
|
|
||||||
|
|
||||||
Notify was a crate I hadn't used before so that took some time to understand
|
|
||||||
and then determine how I wanted to wrap it up. I like the solution that I
|
|
||||||
came up with because it keeps most of the notify mess contained in one file
|
|
||||||
with just a little seepage out when it has to be used for monitoring.
|
|
||||||
|
|
||||||
One thing I was not happy about was that I had very poor git commiting during
|
## Platform ##
|
||||||
the challenge. I think it was because I was trying to keep my time lower, but
|
This was created on Ubuntu and was only tested there. It should
|
||||||
would it really have hurt to spend a little bit of time to commit and give
|
work on most other platforms, but without testing it, I do not
|
||||||
myself better backups incase of a big problem? Probably not.
|
know for certain.
|
||||||
|
|
||||||
This let me work on and understand rust async. I had read the rust book and
|
|
||||||
the tokio tutorial completely before doing this challenge. It wasn't that
|
|
||||||
bad. It reminds me of coding for threads a lot, which is good, but sometimes
|
|
||||||
the await gets me because I don't understand where it is blocked when it is
|
|
||||||
running sometimes, tokio-console helps here, and why calls block or what they
|
|
||||||
are blocking for. For example, why does tokio::io::stdout().write_all() seem
|
|
||||||
to block forever? It is given a few bytes to write to out, it should be able
|
|
||||||
to dump those to the console quickly enough.
|
|
||||||
|
|
||||||
Really, i think my problem with the async IO calls was that they needed to
|
## Build ##
|
||||||
happen in a seperate task and be listening for messages on what to print.
|
To build it just use a standard Cargo install with the current
|
||||||
That way they are never able to block a different task.
|
Rust compiler. Make sure your Cargo is set for Crates.IO as
|
||||||
|
your registry because it uses several crates from there.
|
||||||
Next I think I should checkout the rayon crate, but we'll see what the next
|
|
||||||
challenge I get is.
|
|
||||||
|
33
docs/personal_review.md
Normal file
33
docs/personal_review.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Review #
|
||||||
|
I thought I solved the challenge pretty well. I took my time and went back
|
||||||
|
and refactored and commented well. The overall amount of time I spent solving
|
||||||
|
it, 17 hours, may be rather problematic. I did have to learn some things
|
||||||
|
while I was working on it, but it took what it took.
|
||||||
|
|
||||||
|
Notify was a crate I hadn't used before so that took some time to understand
|
||||||
|
and then determine how I wanted to wrap it up. I like the solution that I
|
||||||
|
came up with because it keeps most of the notify mess contained in one file
|
||||||
|
with just a little seepage out when it has to be used for monitoring. Perhaps
|
||||||
|
I should have fully wrapped the library? It wouldn't have taken much more to
|
||||||
|
do so, it just didn't seem necessary to solving the problem.
|
||||||
|
|
||||||
|
One thing I was not happy about was that I had very poor git commiting during
|
||||||
|
the challenge. I think it was because I was trying to keep my time lower, but
|
||||||
|
would it really have hurt to spend a little bit of time to commit and give
|
||||||
|
myself better backups incase of a big problem? Probably not.
|
||||||
|
|
||||||
|
Ultimately, his let me work on and understand rust async. I had read the rust
|
||||||
|
book and the tokio tutorial completely before doing this challenge. It wasn't
|
||||||
|
that bad. It reminds me of coding for threads a lot, which is good, but
|
||||||
|
sometimesthe await gets me because I don't understand where it is blocked when
|
||||||
|
it is running sometimes, tokio-console helps here, and why calls block or what
|
||||||
|
they are blocking for. For example, why does tokio::io::stdout().write_all()
|
||||||
|
seem to block forever? It is given a few bytes to write to out, it should be
|
||||||
|
able to dump those to the console quickly enough.
|
||||||
|
|
||||||
|
Really, i think my problem with the async IO calls was that they needed to
|
||||||
|
happen in a seperate task and be listening for messages on what to print.
|
||||||
|
That way they are never able to block a different task.
|
||||||
|
|
||||||
|
Next I think I should checkout the rayon crate, but we'll see what the next
|
||||||
|
challenge I get is.
|
@ -15,159 +15,161 @@ use crate::util::strip_path_prefix;
|
|||||||
/// of the given directory.
|
/// of the given directory.
|
||||||
pub struct DirMonitor
|
pub struct DirMonitor
|
||||||
{
|
{
|
||||||
/// The directory to monitor.
|
/// The directory to monitor.
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
|
|
||||||
/// The files that are being monitored within the directory.
|
/// The files that are being monitored within the directory.
|
||||||
monitored_files: MonitoredFiles,
|
monitored_files: MonitoredFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl DirMonitor
|
impl DirMonitor
|
||||||
{
|
{
|
||||||
/// Create a new directory monitor for the desired directory.
|
/// Create a new directory monitor for the desired directory.
|
||||||
///
|
///
|
||||||
/// monitor_path: A string representation of directory to monitor.
|
/// monitor_path: A string representation of directory to monitor.
|
||||||
pub fn new(monitor_path: &str) -> Self
|
pub fn new(monitor_path: &str) -> Self
|
||||||
{
|
{
|
||||||
DirMonitor
|
DirMonitor { dir: PathBuf::from(monitor_path),
|
||||||
{
|
monitored_files: MonitoredFiles::new() }
|
||||||
dir: PathBuf::from(monitor_path),
|
}
|
||||||
monitored_files: MonitoredFiles::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scan the monitored directory and all of its sub directories and monitors
|
/// Scan the monitored directory and all of its sub directories and monitors
|
||||||
/// the files found and their last modification time.
|
/// the files found and their last modification time.
|
||||||
pub fn scan(&mut self)
|
pub fn scan(&mut self)
|
||||||
{
|
{
|
||||||
// Start from the directory and add each file to our list,
|
// Start from the directory and add each file to our list,
|
||||||
// then recurse through all sub directories and add them to
|
// then recurse through all sub directories and add them to
|
||||||
// the list and repeat.
|
// the list and repeat.
|
||||||
match scan_dir(&self.dir, &mut self.monitored_files, &self.dir)
|
match scan_dir(&self.dir, &mut self.monitored_files, &self.dir)
|
||||||
{
|
{
|
||||||
Ok(_) =>
|
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<bool>)
|
||||||
|
{
|
||||||
|
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::<Event>(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) =>
|
Err(e) =>
|
||||||
{
|
{
|
||||||
eprintln!("Directory scanning error: {}", 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!();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/// Print out all the monitored files and their last modified date.
|
if let Err(e) =
|
||||||
///
|
fs_watcher.watch(&self.dir, notify::RecursiveMode::Recursive)
|
||||||
/// Each file entry will be printed out as:
|
{
|
||||||
/// ```
|
// Just panic because we cannot watch the directories so the program
|
||||||
/// [Date Time] PATH
|
// would be useless and this saves from having to press CTRL-C.
|
||||||
/// ```
|
eprintln!("Error trying to watch the directory: {}", e);
|
||||||
pub fn print_monitored_files(&self)
|
panic!();
|
||||||
{
|
}
|
||||||
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<bool>)
|
|
||||||
{
|
|
||||||
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::<Event>(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.
|
// Loop until termination processing events from the notify watcher.
|
||||||
while running
|
while running
|
||||||
{
|
{
|
||||||
// We are listening for messages from either the notify receiver or
|
// We are listening for messages from either the notify receiver or
|
||||||
// the termination receiver. When ever one of them comes across we
|
// the termination receiver. When ever one of them comes across we
|
||||||
// will process it.
|
// will process it.
|
||||||
tokio::select!
|
tokio::select! {
|
||||||
{
|
// Handle listening for the notify watcher events.
|
||||||
// Handle listening for the notify watcher events.
|
// These are the changes that we care about from the file system.
|
||||||
// These are the changes that we care about from the file system.
|
Some(mut event) = notify_receiver.recv() =>
|
||||||
Some(mut event) = notify_receiver.recv() =>
|
{
|
||||||
{
|
event.make_paths_relative(&self.dir);
|
||||||
event.make_paths_relative(&self.dir);
|
println!("{}", event);
|
||||||
println!("{}", event);
|
self.monitored_files.process_event(event);
|
||||||
self.monitored_files.process_event(event);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Handle listening for the termination message, the boolean value will
|
// Handle listening for the termination message, the boolean value will
|
||||||
// be changed to false when we are meant to terminate.
|
// be changed to false when we are meant to terminate.
|
||||||
_ = term_receiver.changed() =>
|
_ = term_receiver.changed() =>
|
||||||
{
|
{
|
||||||
running = *term_receiver.borrow_and_update();
|
running = *term_receiver.borrow_and_update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Scans a directory, and all of its sub directories, and creates a list of the files
|
/// Scans a directory, and all of its sub directories, and creates a list of the
|
||||||
/// inside and their last modification time.
|
/// files inside and their last modification time.
|
||||||
fn scan_dir(base_dir: &Path, monitored_files: &mut MonitoredFiles, dir: &Path) -> std::io::Result<()>
|
fn scan_dir(base_dir: &Path, monitored_files: &mut MonitoredFiles, dir: &Path)
|
||||||
|
-> std::io::Result<()>
|
||||||
{
|
{
|
||||||
let mut dir_list: Vec<PathBuf> = Vec::new();
|
let mut dir_list: Vec<PathBuf> = Vec::new();
|
||||||
|
|
||||||
for file in std::fs::read_dir(dir)?
|
for file in std::fs::read_dir(dir)?
|
||||||
{
|
{
|
||||||
let file = file?;
|
let file = file?;
|
||||||
let meta = file.metadata()?;
|
let meta = file.metadata()?;
|
||||||
|
|
||||||
// Handle directory and file types.
|
// Handle directory and file types.
|
||||||
//
|
//
|
||||||
// Directories will be added to the list and later recursively scanned.
|
// Directories will be added to the list and later recursively scanned.
|
||||||
//
|
//
|
||||||
// Files will be added to the list of monitored files and their
|
// Files will be added to the list of monitored files and their
|
||||||
// last modification time will be stored.
|
// last modification time will be stored.
|
||||||
//
|
//
|
||||||
// TODO: Handle symlinks. Symlinks can be either a file or another directory.
|
// TODO: Handle symlinks. Symlinks can be either a file or another
|
||||||
if meta.is_dir()
|
// directory.
|
||||||
{
|
if meta.is_dir()
|
||||||
dir_list.push(file.path().clone());
|
{
|
||||||
}
|
dir_list.push(file.path().clone());
|
||||||
else if meta.is_file()
|
}
|
||||||
{
|
else if meta.is_file()
|
||||||
let last_mod_time: std::time::SystemTime = meta.modified()?;
|
{
|
||||||
|
let last_mod_time: std::time::SystemTime = meta.modified()?;
|
||||||
|
|
||||||
monitored_files.add(&strip_path_prefix(&file.path(), base_dir), last_mod_time);
|
monitored_files.add(&strip_path_prefix(&file.path(), base_dir),
|
||||||
}
|
last_mod_time);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Recursively scan the sub directories.
|
// Recursively scan the sub directories.
|
||||||
for sub_dir in dir_list
|
for sub_dir in dir_list
|
||||||
{
|
{
|
||||||
scan_dir(base_dir, monitored_files, &sub_dir)?;
|
scan_dir(base_dir, monitored_files, &sub_dir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
164
src/event.rs
164
src/event.rs
@ -9,102 +9,106 @@ use crate::util::strip_path_prefix;
|
|||||||
/// These will be sent as messages in a channel.
|
/// These will be sent as messages in a channel.
|
||||||
pub enum Event
|
pub enum Event
|
||||||
{
|
{
|
||||||
/// A file has been created.
|
/// A file has been created.
|
||||||
New
|
New
|
||||||
{
|
{
|
||||||
/// The path to the file.
|
/// The path to the file.
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
|
|
||||||
/// The modification time of the file.
|
/// The modification time of the file.
|
||||||
mod_time: SystemTime
|
mod_time: SystemTime
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A file has been modified.
|
/// A file has been modified.
|
||||||
Modify
|
Modify
|
||||||
{
|
{
|
||||||
/// The path to the file.
|
/// The path to the file.
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
|
|
||||||
/// The modification time of the file.
|
/// The modification time of the file.
|
||||||
mod_time: SystemTime
|
mod_time: SystemTime
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A file has been renamed, and it stayed in a monitored directory.
|
/// A file has been renamed, and it stayed in a monitored directory.
|
||||||
Rename
|
Rename
|
||||||
{
|
{
|
||||||
/// The path of the file that was renamed.
|
/// The path of the file that was renamed.
|
||||||
from: std::path::PathBuf,
|
from: std::path::PathBuf,
|
||||||
|
|
||||||
/// The path that the file was renamed to.
|
/// The path that the file was renamed to.
|
||||||
to: std::path::PathBuf,
|
to: std::path::PathBuf,
|
||||||
|
|
||||||
/// The modification time of the file.
|
/// The modification time of the file.
|
||||||
mod_time: SystemTime
|
mod_time: SystemTime
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A file has been removed.
|
/// A file has been removed.
|
||||||
Delete
|
Delete
|
||||||
{
|
{
|
||||||
/// The path of the removed file.
|
/// The path of the removed file.
|
||||||
path: std::path::PathBuf
|
path: std::path::PathBuf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl Event
|
impl Event
|
||||||
{
|
{
|
||||||
/// Take all the paths in the Event and makes them relative
|
/// Take all the paths in the Event and makes them relative
|
||||||
/// to the base_dir directory.
|
/// to the base_dir directory.
|
||||||
pub fn make_paths_relative(&mut self, base_dir: &std::path::Path)
|
pub fn make_paths_relative(&mut self, base_dir: &std::path::Path)
|
||||||
{
|
{
|
||||||
match self
|
match self
|
||||||
{
|
{
|
||||||
Event::New { path, mod_time: _ } =>
|
Event::New { path, mod_time: _ } =>
|
||||||
{
|
{
|
||||||
*path = strip_path_prefix(&path, base_dir);
|
*path = strip_path_prefix(&path, base_dir);
|
||||||
}
|
}
|
||||||
Event::Modify { path, mod_time: _ } =>
|
Event::Modify { path, mod_time: _ } =>
|
||||||
{
|
{
|
||||||
*path = strip_path_prefix(&path, base_dir);
|
*path = strip_path_prefix(&path, base_dir);
|
||||||
}
|
}
|
||||||
Event::Rename { from, to, mod_time: _ } =>
|
Event::Rename { from,
|
||||||
{
|
to,
|
||||||
*from = strip_path_prefix(&from, base_dir);
|
mod_time: _ } =>
|
||||||
*to = strip_path_prefix(&to, base_dir);
|
{
|
||||||
}
|
*from = strip_path_prefix(&from, base_dir);
|
||||||
Event::Delete { path } =>
|
*to = strip_path_prefix(&to, base_dir);
|
||||||
{
|
}
|
||||||
*path = strip_path_prefix(&path, base_dir);
|
Event::Delete { path } =>
|
||||||
}
|
{
|
||||||
}
|
*path = strip_path_prefix(&path, base_dir);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl std::fmt::Display for Event
|
impl std::fmt::Display for Event
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
||||||
{
|
{
|
||||||
match self
|
match self
|
||||||
{
|
{
|
||||||
Event::New { path, mod_time: _ } =>
|
Event::New { path, mod_time: _ } =>
|
||||||
{
|
{
|
||||||
write!(f, "[NEW] {}", path.display())
|
write!(f, "[NEW] {}", path.display())
|
||||||
}
|
}
|
||||||
Event::Modify { path, mod_time: _ } =>
|
Event::Modify { path, mod_time: _ } =>
|
||||||
{
|
{
|
||||||
write!(f, "[MOD] {}", path.display())
|
write!(f, "[MOD] {}", path.display())
|
||||||
}
|
}
|
||||||
Event::Rename { from, to, mod_time: _ } =>
|
Event::Rename { from,
|
||||||
{
|
to,
|
||||||
writeln!(f, "[DEL] {}", from.display())?;
|
mod_time: _ } =>
|
||||||
write!(f, "[NEW] {}", to.display())
|
{
|
||||||
}
|
writeln!(f, "[DEL] {}", from.display())?;
|
||||||
Event::Delete { path } =>
|
write!(f, "[NEW] {}", to.display())
|
||||||
{
|
}
|
||||||
write!(f, "[DEL] {}", path.display())
|
Event::Delete { path } =>
|
||||||
}
|
{
|
||||||
}
|
write!(f, "[DEL] {}", path.display())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
162
src/main.rs
162
src/main.rs
@ -11,7 +11,6 @@ mod util;
|
|||||||
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
use crate::dir_monitor::DirMonitor;
|
use crate::dir_monitor::DirMonitor;
|
||||||
@ -27,9 +26,9 @@ use crate::dir_monitor::DirMonitor;
|
|||||||
/// files.
|
/// files.
|
||||||
struct Options
|
struct Options
|
||||||
{
|
{
|
||||||
#[arg(short = 'd', default_value = "~/inbox")]
|
#[arg(short = 'd', default_value = "~/inbox")]
|
||||||
/// The path for the inbox to monitor.
|
/// The path for the inbox to monitor.
|
||||||
inbox_dir: String
|
inbox_dir: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -37,28 +36,25 @@ struct Options
|
|||||||
/// Creates the given directory, and its parents, if it does not exist.
|
/// Creates the given directory, and its parents, if it does not exist.
|
||||||
fn create_directory(dir: &str) -> Option<String>
|
fn create_directory(dir: &str) -> Option<String>
|
||||||
{
|
{
|
||||||
let mut inbox_dir = String::from(dir);
|
let mut inbox_dir = String::from(dir);
|
||||||
if inbox_dir.starts_with("~")
|
if inbox_dir.starts_with("~")
|
||||||
{
|
{
|
||||||
if let Some(path) = home::home_dir()
|
if let Some(path) = home::home_dir()
|
||||||
{
|
{
|
||||||
inbox_dir = inbox_dir.replace("~", &path.display().to_string());
|
inbox_dir = inbox_dir.replace("~", &path.display().to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match std::fs::create_dir_all(inbox_dir.clone())
|
match std::fs::create_dir_all(inbox_dir.clone())
|
||||||
{
|
{
|
||||||
Ok(_) =>
|
Ok(_) => Some(inbox_dir),
|
||||||
{
|
|
||||||
Some(inbox_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e) =>
|
Err(e) =>
|
||||||
{
|
{
|
||||||
eprintln!("Creating directory error: {}", e);
|
eprintln!("Creating directory error: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asynchronously listen for CTRL-C to be pressed.
|
/// Asynchronously listen for CTRL-C to be pressed.
|
||||||
@ -67,13 +63,13 @@ fn create_directory(dir: &str) -> Option<String>
|
|||||||
/// when it detects the termination signal.
|
/// when it detects the termination signal.
|
||||||
async fn listen_for_termination(sender: watch::Sender<bool>)
|
async fn listen_for_termination(sender: watch::Sender<bool>)
|
||||||
{
|
{
|
||||||
tokio::signal::ctrl_c().await.unwrap();
|
tokio::signal::ctrl_c().await.unwrap();
|
||||||
|
|
||||||
// CTRL-C was pressed so send a message to everyone that is
|
// CTRL-C was pressed so send a message to everyone that is
|
||||||
// watching that the program is to be terminated. Then close the
|
// watching that the program is to be terminated. Then close the
|
||||||
// sender.
|
// sender.
|
||||||
sender.send_replace(false);
|
sender.send_replace(false);
|
||||||
sender.closed().await;
|
sender.closed().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -81,63 +77,65 @@ async fn listen_for_termination(sender: watch::Sender<bool>)
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main()
|
async fn main()
|
||||||
{
|
{
|
||||||
let options: Options = Options::parse();
|
let options: Options = Options::parse();
|
||||||
|
|
||||||
if let Some(inbox_dir) = create_directory(&options.inbox_dir)
|
if let Some(inbox_dir) = create_directory(&options.inbox_dir)
|
||||||
{
|
{
|
||||||
// This is our keep running channel. False will be sent
|
// This is our keep running channel. False will be sent
|
||||||
// when the application is meant to be closed.
|
// when the application is meant to be closed.
|
||||||
let (sender, receiver) = watch::channel(true);
|
let (sender, receiver) = watch::channel(true);
|
||||||
|
|
||||||
let mut directory_monitor: DirMonitor = DirMonitor::new(&inbox_dir);
|
let mut directory_monitor: DirMonitor = DirMonitor::new(&inbox_dir);
|
||||||
|
|
||||||
// Program workflow step 1.
|
// Program workflow step 1.
|
||||||
// Async is not needed here. This can be done syncronously because
|
// Async is not needed here. This can be done syncronously because
|
||||||
// file systems are not async and the spawn_blocking blocking overhead
|
// file systems are not async and the spawn_blocking blocking overhead
|
||||||
// isn't needed.
|
// isn't needed.
|
||||||
directory_monitor.scan();
|
directory_monitor.scan();
|
||||||
directory_monitor.print_monitored_files();
|
directory_monitor.print_monitored_files();
|
||||||
|
|
||||||
// Run our directory monitor.
|
// Run our directory monitor.
|
||||||
let mon_task = tokio::spawn(async move
|
let mon_task = tokio::spawn(async move {
|
||||||
{
|
// Program workflow step 2 (terminated by step 3).
|
||||||
// Program workflow step 2 (terminated by step 3).
|
// This can be done async because we are waiting on
|
||||||
// This can be done async because we are waiting on
|
// events to be loaded from the notify library and
|
||||||
// events to be loaded from the notify library and
|
// we want this to run continously and not block the
|
||||||
// we want this to run continously and not block the
|
// program. This could also be done on a seperate thread,
|
||||||
// program. This could also be done on a seperate thread,
|
// preferably a green thread, but a fiber is sufficient
|
||||||
// preferably a green thread, but a fiber is sufficient
|
// and less resource intensive.
|
||||||
// and less resource intensive.
|
directory_monitor.monitor(receiver).await;
|
||||||
directory_monitor.monitor(receiver).await;
|
|
||||||
|
|
||||||
// Program workflow step 4.
|
// Program workflow step 4.
|
||||||
// Async is not needed here. This can be done
|
// Async is not needed here. This can be done
|
||||||
// syncronously because it is the final/clean up
|
// syncronously because it is the final/clean up
|
||||||
// step of the program.
|
// step of the program.
|
||||||
//
|
//
|
||||||
// This is done here because we have moved the directory
|
// This is done here because we have moved the directory
|
||||||
// monitor inside this task.
|
// monitor inside this task.
|
||||||
directory_monitor.print_monitored_files();
|
directory_monitor.print_monitored_files();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run until Ctrl-C is pressed.
|
// Run until Ctrl-C is pressed.
|
||||||
// Once it is pressed it will send a message to stop.
|
// Once it is pressed it will send a message to stop.
|
||||||
let _term_task = tokio::spawn(async move
|
let _term_task = tokio::spawn(async move {
|
||||||
{
|
// Program workflow step 3.
|
||||||
// Program workflow step 3.
|
// Async with message passing so it can run and listen
|
||||||
// Async with message passing so it can run and listen
|
// for the termination signal. It uses a watch channel
|
||||||
// for the termination signal. It uses a watch channel
|
// because any task that will need to know when the
|
||||||
// because any task that will need to know when the
|
// program is to end can then catch the change.
|
||||||
// program is to end can then catch the change.
|
listen_for_termination(sender).await;
|
||||||
listen_for_termination(sender).await;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Only need to wait for the monitor task to finish because it will only
|
// Only need to wait for the monitor task to finish because it will only
|
||||||
// finish once the termination task has finished.
|
// finish once the termination task has finished.
|
||||||
match mon_task.await
|
match mon_task.await
|
||||||
{
|
{
|
||||||
Ok(_) => {}
|
Ok(_) =>
|
||||||
Err(e) => { eprintln!("Error during monitoring task: {}", e); }
|
{}
|
||||||
}
|
Err(e) =>
|
||||||
}
|
{
|
||||||
|
eprintln!("Error during monitoring task: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
@ -11,93 +11,96 @@ use crate::event::Event;
|
|||||||
/// The files currently being monitored and their last modification time.
|
/// The files currently being monitored and their last modification time.
|
||||||
pub struct MonitoredFiles
|
pub struct MonitoredFiles
|
||||||
{
|
{
|
||||||
/// A mapping of a Path to the last known modification time.
|
/// A mapping of a Path to the last known modification time.
|
||||||
files: HashMap<PathBuf, SystemTime>
|
files: HashMap<PathBuf, SystemTime>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl MonitoredFiles
|
impl MonitoredFiles
|
||||||
{
|
{
|
||||||
/// Create a new empty set of monitored files.
|
/// Create a new empty set of monitored files.
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
MonitoredFiles
|
MonitoredFiles { files: HashMap::new() }
|
||||||
{
|
}
|
||||||
files: HashMap::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process an event and change the monitored files.
|
/// Process an event and change the monitored files.
|
||||||
pub fn process_event(&mut self, event: Event)
|
pub fn process_event(&mut self, event: Event)
|
||||||
{
|
{
|
||||||
match event
|
match event
|
||||||
{
|
{
|
||||||
Event::New { path, mod_time } =>
|
Event::New { path, mod_time } =>
|
||||||
{
|
{
|
||||||
self.add(&path, mod_time);
|
self.add(&path, mod_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Modify { path, mod_time} =>
|
Event::Modify { path, mod_time } =>
|
||||||
{
|
{
|
||||||
self.modify(&path, mod_time);
|
self.modify(&path, mod_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Rename { from, to, mod_time } =>
|
Event::Rename { from, to, mod_time } =>
|
||||||
{
|
{
|
||||||
self.remove(&from);
|
self.remove(&from);
|
||||||
self.add(&to, mod_time);
|
self.add(&to, mod_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Delete { path } =>
|
Event::Delete { path } =>
|
||||||
{
|
{
|
||||||
self.remove(&path);
|
self.remove(&path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a file that is to be monitored..
|
/// Add a file that is to be monitored..
|
||||||
pub fn add(&mut self, path: &Path, mod_time: SystemTime)
|
pub fn add(&mut self, path: &Path, mod_time: SystemTime)
|
||||||
{
|
{
|
||||||
self.files.insert(PathBuf::from(path), mod_time);
|
self.files.insert(PathBuf::from(path), mod_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modify the stored modifcation time of a monitored file.
|
/// Modify the stored modifcation time of a monitored file.
|
||||||
pub fn modify(&mut self, path: &Path, time: SystemTime)
|
pub fn modify(&mut self, path: &Path, time: SystemTime)
|
||||||
{
|
{
|
||||||
if let Some(entry) = self.files.get_mut(path)
|
if let Some(entry) = self.files.get_mut(path)
|
||||||
{
|
{
|
||||||
*entry = time;
|
*entry = time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a file from being monitored.
|
/// Remove a file from being monitored.
|
||||||
pub fn remove(&mut self, path: &Path)
|
pub fn remove(&mut self, path: &Path)
|
||||||
{
|
{
|
||||||
self.files.remove(path);
|
self.files.remove(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl std::fmt::Display for MonitoredFiles
|
impl std::fmt::Display for MonitoredFiles
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
||||||
{
|
{
|
||||||
let mut it = self.files.iter().peekable();
|
let mut it = self.files.iter().peekable();
|
||||||
while let Some((path, mod_time)) = it.next()
|
while let Some((path, mod_time)) = it.next()
|
||||||
{
|
{
|
||||||
let date_time: DateTime<Local> = DateTime::from(*mod_time);
|
let date_time: DateTime<Local> = DateTime::from(*mod_time);
|
||||||
|
|
||||||
if !it.peek().is_none()
|
if !it.peek().is_none()
|
||||||
{
|
{
|
||||||
writeln!(f, "[{}] {}", date_time.format("%m/%d/%Y %H:%M"), path.display())?;
|
writeln!(f,
|
||||||
}
|
"[{}] {}",
|
||||||
else
|
date_time.format("%m/%d/%Y %H:%M"),
|
||||||
{
|
path.display())?;
|
||||||
write!(f, "[{}] {}", date_time.format("%m/%d/%Y %H:%M"), path.display())?;
|
}
|
||||||
}
|
else
|
||||||
}
|
{
|
||||||
|
write!(f,
|
||||||
|
"[{}] {}",
|
||||||
|
date_time.format("%m/%d/%Y %H:%M"),
|
||||||
|
path.display())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,236 +10,215 @@ use crate::util::get_file_mod_time;
|
|||||||
/// EventHandler.
|
/// EventHandler.
|
||||||
pub struct NotifySender
|
pub struct NotifySender
|
||||||
{
|
{
|
||||||
/// The tokio runtime to spawn event handling tasks on.
|
/// The tokio runtime to spawn event handling tasks on.
|
||||||
tokio_runtime: tokio::runtime::Handle,
|
tokio_runtime: tokio::runtime::Handle,
|
||||||
|
|
||||||
/// The channel to send event messages on.
|
/// The channel to send event messages on.
|
||||||
sender: tokio::sync::mpsc::Sender<crate::event::Event>
|
sender: tokio::sync::mpsc::Sender<crate::event::Event>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl NotifySender
|
impl NotifySender
|
||||||
{
|
{
|
||||||
/// Create a new type that wraps the tokio mpsc Sender.
|
/// Create a new type that wraps the tokio mpsc Sender.
|
||||||
pub fn new(tokio_runtime: tokio::runtime::Handle,
|
pub fn new(tokio_runtime: tokio::runtime::Handle,
|
||||||
sender: tokio::sync::mpsc::Sender<crate::event::Event>)
|
sender: tokio::sync::mpsc::Sender<crate::event::Event>)
|
||||||
-> Self
|
-> Self
|
||||||
{
|
{
|
||||||
NotifySender
|
NotifySender { tokio_runtime,
|
||||||
{
|
sender }
|
||||||
tokio_runtime,
|
}
|
||||||
sender
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl notify::EventHandler for NotifySender
|
impl notify::EventHandler for NotifySender
|
||||||
{
|
{
|
||||||
fn handle_event(&mut self, event: notify::Result<notify::Event>)
|
fn handle_event(&mut self, event: notify::Result<notify::Event>)
|
||||||
{
|
{
|
||||||
match event
|
match event
|
||||||
{
|
{
|
||||||
Ok(notify_event) =>
|
Ok(notify_event) =>
|
||||||
|
{
|
||||||
|
// Process the notify event and turn it into an event
|
||||||
|
// our crate is interested in, one of our Events. If
|
||||||
|
// we get a valid one then create a tokio task that will
|
||||||
|
// send the event out as a message.
|
||||||
|
if let Some(monitor_event) = process_notify_event(notify_event)
|
||||||
{
|
{
|
||||||
// Process the notify event and turn it into an event
|
let notify_sender = self.sender.clone();
|
||||||
// our crate is interested in, one of our Events. If
|
|
||||||
// we get a valid one then create a tokio task that will
|
|
||||||
// send the event out as a message.
|
|
||||||
if let Some(monitor_event) = process_notify_event(notify_event)
|
|
||||||
{
|
|
||||||
let notify_sender = self.sender.clone();
|
|
||||||
|
|
||||||
self.tokio_runtime.spawn(async move
|
self.tokio_runtime.spawn(async move {
|
||||||
{
|
if let Err(e) =
|
||||||
if let Err(e) = notify_sender.send(monitor_event).await
|
notify_sender.send(monitor_event).await
|
||||||
{
|
{
|
||||||
eprintln!("Notify EventHandler: {}", e);
|
eprintln!("Notify EventHandler: {}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Err(e) =>
|
Err(e) =>
|
||||||
{
|
{
|
||||||
eprintln!("Notify crate error: {}", e);
|
eprintln!("Notify crate error: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the notify crate events into events that our program cares about.
|
/// Processes the notify crate events into events that our program cares about.
|
||||||
fn process_notify_event(event: notify::Event) -> Option<crate::event::Event>
|
fn process_notify_event(event: notify::Event) -> Option<crate::event::Event>
|
||||||
{
|
{
|
||||||
match event.kind
|
match event.kind
|
||||||
{
|
{
|
||||||
notify::EventKind::Create(kind) =>
|
notify::EventKind::Create(kind) =>
|
||||||
{
|
{
|
||||||
process_create_event(kind, event.paths)
|
process_create_event(kind, event.paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
notify::EventKind::Modify(kind) =>
|
notify::EventKind::Modify(kind) =>
|
||||||
{
|
{
|
||||||
process_modify_event(kind, event.paths)
|
process_modify_event(kind, event.paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
notify::EventKind::Remove(kind) =>
|
notify::EventKind::Remove(kind) =>
|
||||||
{
|
{
|
||||||
process_remove_event(kind, event.paths)
|
process_remove_event(kind, event.paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => { None }
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process the Create event into a New event.
|
/// Process the Create event into a New event.
|
||||||
fn process_create_event(event: notify::event::CreateKind,
|
fn process_create_event(event: notify::event::CreateKind, paths: Vec<PathBuf>)
|
||||||
paths: Vec<PathBuf>)
|
-> Option<crate::event::Event>
|
||||||
-> Option<crate::event::Event>
|
|
||||||
{
|
{
|
||||||
if event == notify::event::CreateKind::File
|
if event == notify::event::CreateKind::File
|
||||||
{
|
{
|
||||||
let mod_time: std::time::SystemTime =
|
let mod_time: std::time::SystemTime = match get_file_mod_time(&paths[0])
|
||||||
match get_file_mod_time(&paths[0])
|
{
|
||||||
{
|
Ok(time) => time,
|
||||||
Ok(time) => { time }
|
|
||||||
|
|
||||||
Err(e) =>
|
Err(e) =>
|
||||||
{
|
{
|
||||||
eprintln!("Unable to open file to retrieve modification time: {}",
|
eprintln!("Unable to open file to retrieve modification time: {}",
|
||||||
e);
|
e);
|
||||||
std::time::SystemTime::now()
|
std::time::SystemTime::now()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return Some(crate::event::Event::New
|
return Some(crate::event::Event::New { path: paths[0].clone(),
|
||||||
{
|
mod_time: mod_time });
|
||||||
path: paths[0].clone(),
|
}
|
||||||
mod_time: mod_time
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process the modify event into either a Modify event or a Rename event.
|
/// Process the modify event into either a Modify event or a Rename event.
|
||||||
fn process_modify_event(event: notify::event::ModifyKind,
|
fn process_modify_event(event: notify::event::ModifyKind, paths: Vec<PathBuf>)
|
||||||
paths: Vec<PathBuf>)
|
-> Option<crate::event::Event>
|
||||||
-> Option<crate::event::Event>
|
|
||||||
{
|
{
|
||||||
match event
|
match event
|
||||||
{
|
{
|
||||||
// This is just handling the events. On my system they came across as Any for
|
// This is just handling the events. On my system they came across as Any
|
||||||
// the MetadataKind, but the documentation seemed to hint that modification
|
// for the MetadataKind, but the documentation seemed to hint that
|
||||||
// times should be on write events. So for the sake of being done without testing
|
// modification times should be on write events. So for the sake of
|
||||||
// this on different platforms the Any modification event will trigger a time lookup.
|
// being done without testing this on different platforms the Any
|
||||||
notify::event::ModifyKind::Any | notify::event::ModifyKind::Metadata(_) =>
|
// modification event will trigger a time lookup.
|
||||||
{
|
notify::event::ModifyKind::Any |
|
||||||
let mod_time: std::time::SystemTime =
|
notify::event::ModifyKind::Metadata(_) =>
|
||||||
match get_file_mod_time(&paths[0])
|
{
|
||||||
{
|
let mod_time: std::time::SystemTime = match get_file_mod_time(&paths
|
||||||
Ok(time) => { time }
|
[0])
|
||||||
|
{
|
||||||
|
Ok(time) => time,
|
||||||
|
|
||||||
Err(e) =>
|
Err(e) =>
|
||||||
{
|
{
|
||||||
eprintln!("Unable to open file to retrieve modification time: {}",
|
eprintln!("Unable to open file to retrieve modification time: {}",
|
||||||
e);
|
e);
|
||||||
std::time::SystemTime::now()
|
std::time::SystemTime::now()
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(crate::event::Event::Modify
|
|
||||||
{
|
|
||||||
path: paths[0].clone(),
|
|
||||||
mod_time: mod_time
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handling a rename event so that during testing when a file is
|
|
||||||
// moved it is still tracked.
|
|
||||||
notify::event::ModifyKind::Name(mode) =>
|
|
||||||
{
|
|
||||||
match mode
|
|
||||||
{
|
|
||||||
// This is for when a file is renamed and both the to and from
|
|
||||||
// are in the monitored directories.
|
|
||||||
notify::event::RenameMode::Both =>
|
|
||||||
{
|
|
||||||
let mod_time: std::time::SystemTime =
|
|
||||||
match get_file_mod_time(&paths[1])
|
|
||||||
{
|
|
||||||
Ok(time) => { time }
|
|
||||||
|
|
||||||
Err(e) =>
|
|
||||||
{
|
|
||||||
eprintln!("Unable to open file to retrieve modification time: {}",
|
|
||||||
e);
|
|
||||||
std::time::SystemTime::now()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(crate::event::Event::Rename
|
|
||||||
{
|
|
||||||
from: paths[0].clone(),
|
|
||||||
to: paths[1].clone(),
|
|
||||||
mod_time: mod_time
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is for when a file is renamed and only the from
|
|
||||||
// file is in the monitored directories.
|
|
||||||
notify::event::RenameMode::From =>
|
|
||||||
{
|
|
||||||
Some(crate::event::Event::Delete
|
|
||||||
{
|
|
||||||
path: paths[0].clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is for when a file is renamed and both the to file
|
|
||||||
// is in the monitored directories.
|
|
||||||
notify::event::RenameMode::To =>
|
|
||||||
{
|
|
||||||
let mod_time: std::time::SystemTime =
|
|
||||||
match get_file_mod_time(&paths[0])
|
|
||||||
{
|
|
||||||
Ok(time) => { time }
|
|
||||||
|
|
||||||
Err(e) =>
|
|
||||||
{
|
|
||||||
eprintln!("Unable to open file to retrieve modification time: {}",
|
|
||||||
e);
|
|
||||||
std::time::SystemTime::now()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(crate::event::Event::New
|
|
||||||
{
|
|
||||||
path: paths[0].clone(),
|
|
||||||
mod_time: mod_time
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => { None }
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_ => { None }
|
Some(crate::event::Event::Modify { path: paths[0].clone(),
|
||||||
}
|
mod_time: mod_time })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handling a rename event so that during testing when a file is
|
||||||
|
// moved it is still tracked.
|
||||||
|
notify::event::ModifyKind::Name(mode) =>
|
||||||
|
{
|
||||||
|
match mode
|
||||||
|
{
|
||||||
|
// This is for when a file is renamed and both the to and from
|
||||||
|
// are in the monitored directories.
|
||||||
|
notify::event::RenameMode::Both =>
|
||||||
|
{
|
||||||
|
let mod_time: std::time::SystemTime =
|
||||||
|
match get_file_mod_time(&paths[1])
|
||||||
|
{
|
||||||
|
Ok(time) => time,
|
||||||
|
|
||||||
|
Err(e) =>
|
||||||
|
{
|
||||||
|
eprintln!("Unable to open file to retrieve \
|
||||||
|
modification time: {}",
|
||||||
|
e);
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(crate::event::Event::Rename { from: paths[0].clone(),
|
||||||
|
to: paths[1].clone(),
|
||||||
|
mod_time: mod_time })
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for when a file is renamed and only the from
|
||||||
|
// file is in the monitored directories.
|
||||||
|
notify::event::RenameMode::From =>
|
||||||
|
{
|
||||||
|
Some(crate::event::Event::Delete { path: paths[0].clone() })
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for when a file is renamed and both the to file
|
||||||
|
// is in the monitored directories.
|
||||||
|
notify::event::RenameMode::To =>
|
||||||
|
{
|
||||||
|
let mod_time: std::time::SystemTime =
|
||||||
|
match get_file_mod_time(&paths[0])
|
||||||
|
{
|
||||||
|
Ok(time) => time,
|
||||||
|
|
||||||
|
Err(e) =>
|
||||||
|
{
|
||||||
|
eprintln!("Unable to open file to retrieve \
|
||||||
|
modification time: {}",
|
||||||
|
e);
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(crate::event::Event::New { path: paths[0].clone(),
|
||||||
|
mod_time: mod_time })
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process the remove event into a Delete event.
|
/// Process the remove event into a Delete event.
|
||||||
fn process_remove_event(event: notify::event::RemoveKind,
|
fn process_remove_event(event: notify::event::RemoveKind, paths: Vec<PathBuf>)
|
||||||
paths: Vec<PathBuf>)
|
-> Option<crate::event::Event>
|
||||||
-> Option<crate::event::Event>
|
|
||||||
{
|
{
|
||||||
if event == notify::event::RemoveKind::File
|
if event == notify::event::RemoveKind::File
|
||||||
{
|
{
|
||||||
return Some(crate::event::Event::Delete
|
return Some(crate::event::Event::Delete { path: paths[0].clone() });
|
||||||
{
|
}
|
||||||
path: paths[0].clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
48
src/util.rs
48
src/util.rs
@ -2,35 +2,35 @@
|
|||||||
///
|
///
|
||||||
/// If it is unable to get the modification time from the file system
|
/// If it is unable to get the modification time from the file system
|
||||||
/// it will default to the current system time.
|
/// it will default to the current system time.
|
||||||
pub fn get_file_mod_time(path: &std::path::Path) -> std::io::Result<std::time::SystemTime>
|
pub fn get_file_mod_time(path: &std::path::Path)
|
||||||
|
-> std::io::Result<std::time::SystemTime>
|
||||||
{
|
{
|
||||||
let file = std::fs::File::open(path)?;
|
let file = std::fs::File::open(path)?;
|
||||||
let meta = file.metadata()?;
|
let meta = file.metadata()?;
|
||||||
|
|
||||||
if meta.is_file()
|
if meta.is_file()
|
||||||
{
|
{
|
||||||
return Ok(meta.modified()?);
|
return Ok(meta.modified()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(std::time::SystemTime::now())
|
Ok(std::time::SystemTime::now())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new PathBuf that is the same as path but has the base removed if it can.
|
/// Create a new PathBuf that is the same as path but has the base removed if it
|
||||||
pub fn strip_path_prefix(path: &std::path::Path, base: &std::path::Path) -> std::path::PathBuf
|
/// can.
|
||||||
|
pub fn strip_path_prefix(path: &std::path::Path, base: &std::path::Path)
|
||||||
|
-> std::path::PathBuf
|
||||||
{
|
{
|
||||||
match &path.strip_prefix(base)
|
match &path.strip_prefix(base)
|
||||||
{
|
{
|
||||||
Ok(rel_path) =>
|
Ok(rel_path) =>
|
||||||
{
|
{
|
||||||
let mut final_path = std::path::PathBuf::new();
|
let mut final_path = std::path::PathBuf::new();
|
||||||
final_path.push("./");
|
final_path.push("./");
|
||||||
final_path.push(rel_path);
|
final_path.push(rel_path);
|
||||||
final_path
|
final_path
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(_) =>
|
Err(_) => std::path::PathBuf::from(path)
|
||||||
{
|
}
|
||||||
std::path::PathBuf::from(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user