Skip to content

Commit

Permalink
Fixes for the next release given recent usage
Browse files Browse the repository at this point in the history
* Rework design to not watch ignored directories, improving reliability and resource utilization
* Reduce debouncing period to 125ms
* Mark fsw output with "fsw:"
* Bump notify and transitive dependencies

There's probably some bugs here, but if so I'll do another release.
  • Loading branch information
longshorej committed Mar 27, 2019
1 parent 3a795a1 commit beed578
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 52 deletions.
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ description = "A tool to watch a directory and run a command when its contents c
readme = "README.md"

[dependencies]
notify = "4.0.0"
notify = "4.0.10"
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fsw is a tool for recursively watching the current working directory and running

It's integrated with Git, so it won't rerun the command if an ignored file changes.

Why? Well, I quite like the workflow that sbt's tidle (`~`) operator provides, and I wanted a reliable mechanism to do the same thing with other tools.
Why? Well, I quite like the workflow that sbt's tilde (`~`) operator provides, and I wanted a reliable mechanism to do the same thing with other tools.

## Install

Expand All @@ -28,6 +28,13 @@ fsw <command> [<arg>]...

## Changelog

### 0.1.1 - 2019-03-26

* Rework design to not watch ignored directories, improving reliability and resource utilization
* Reduce debouncing period to 125ms
* Mark fsw output with "fsw:"
* Bump notify and transitive dependencies

### 0.1.0 - 2019-02-26

* Initial release.
109 changes: 78 additions & 31 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
extern crate notify;

const DRAIN_MS: u64 = 250;
const DRAIN_MS: u64 = 125;
const GIT_PATH: &str = ".git";

use notify::{raw_watcher, RecursiveMode, Watcher};
use notify::{raw_watcher, RecommendedWatcher, RecursiveMode, Watcher};
use std::{
env, io, path, process,
collections::HashSet,
env, fs, io, path, process,
sync::mpsc::{channel, Receiver, Sender},
thread, time,
};

enum Msg {
PathEvent(path::PathBuf),
PathEvent,
ThreadFinished,
}

/// Handles events (file notifcations / process notifcations) and forks a new process
/// when required.
fn handle(sender: Sender<Msg>, receiver: Receiver<Msg>, command: String, args: Vec<String>) {
let mut running = false;

Expand All @@ -22,7 +26,7 @@ fn handle(sender: Sender<Msg>, receiver: Receiver<Msg>, command: String, args: V

while let Ok(path) = receiver.recv() {
let run = match path {
Msg::PathEvent(_) => {
Msg::PathEvent => {
if running {
waiting = true;
false
Expand Down Expand Up @@ -57,15 +61,28 @@ fn handle(sender: Sender<Msg>, receiver: Receiver<Msg>, command: String, args: V
let sender = sender.clone();
let command = command.clone();
let args = args.clone();
let handle = process::Command::new(&command)
.args(args)
.stdin(process::Stdio::null())
.stdout(process::Stdio::inherit())
.stderr(process::Stdio::inherit())
.spawn();

thread::spawn(move || {
handle_child(command, handle);
let status = process::Command::new(&command)
.args(args)
.stdin(process::Stdio::null())
.stdout(process::Stdio::inherit())
.stderr(process::Stdio::inherit())
.status();

match status.map(|s| s.code()) {
Ok(Some(c)) => {
println!("fsw: {} exited with {}", command, c);
}

Ok(None) => {
println!("fsw: {} exited with unknown", command);
}

Err(e) => {
println!("fsw: {} failed with {}", command, e);
}
}

let _ = sender.send(Msg::ThreadFinished);
});
Expand All @@ -74,48 +91,78 @@ fn handle(sender: Sender<Msg>, receiver: Receiver<Msg>, command: String, args: V
}
}

fn handle_child(command: String, result: io::Result<process::Child>) {
match result.and_then(|mut s| s.wait()).map(|s| s.code()) {
Ok(Some(c)) => {
println!("{} exited with {}", command, c);
}
/// Recursively walk a directory, watching everything including the specified directory
/// if it isn't already watched.
///
/// @FIXME if directories are removed, they aren't removed from the set (memory leak)
fn watch_dir(
git_dir: &path::Path,
watcher: &mut RecommendedWatcher,
watching: &mut HashSet<path::PathBuf>,
dir: &path::PathBuf,
) -> io::Result<()> {
watcher
.watch(&dir, RecursiveMode::NonRecursive)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

Ok(None) => {
println!("{} exited with unknown", command);
}
for entry in fs::read_dir(dir)? {
let entry_path = entry?.path();

Err(e) => {
println!("{} failed with {}", command, e);
if entry_path.is_dir()
&& !git_ignored(git_dir, &entry_path)
&& !watching.contains(&entry_path)
{
watch_dir(&git_dir, watcher, watching, &entry_path)?;

watching.insert(entry_path);
}
}

Ok(())
}

/// Watches the working directory of the process, and sends a
/// PathEvent to the provided sender if a relevant file or
/// directory changes.
fn watch(sender: Sender<Msg>) -> io::Result<()> {
let (tx, rx) = channel();

let working_dir = env::current_dir()?;
let git_dir = working_dir.join(".git");
let git_dir = working_dir.join(GIT_PATH);

let mut watcher = raw_watcher(tx).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let mut watching = HashSet::new();

watcher
.watch(working_dir, RecursiveMode::Recursive)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
watch_dir(&git_dir, &mut watcher, &mut watching, &working_dir)?;

watching.insert(working_dir);

while let Ok(event) = rx.recv() {
if let Some(path) = event.path {
if !path.starts_with(&git_dir) && !git_ignored(&path) {
if !path.starts_with(&git_dir) && !git_ignored(&git_dir, &path) {
sender
.send(Msg::PathEvent(path))
.send(Msg::PathEvent)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

if !watching.contains(&path) && path.is_dir() {
watch_dir(&git_dir, &mut watcher, &mut watching, &path)?;
}
}
}
}

Ok(())
}

fn git_ignored(path: &path::Path) -> bool {
/// Determines if the provided path is ignored by git,
/// returning true if it is.
///
/// FIXME: link to git or smth instead of forking a process
fn git_ignored(git_dir: &path::Path, path: &path::Path) -> bool {
if path.starts_with(&git_dir) {
return false;
}

if let Some(s) = path.to_str() {
process::Command::new("git")
.args(&["check-ignore", s])
Expand All @@ -135,9 +182,9 @@ fn main() {

if args.len() < 2 {
let args = args.collect::<Vec<String>>();
println!("fsw version: {}", env!("CARGO_PKG_VERSION"));
println!("fsw: version: {}", env!("CARGO_PKG_VERSION"));
println!(
"usage: {} <cmd> [<arg>]...",
"fsw: usage: {} <cmd> [<arg>]...",
args.get(0).map(|s| s.as_str()).unwrap_or("fsw")
);
process::exit(1);
Expand Down

0 comments on commit beed578

Please sign in to comment.