mirror of https://github.com/bobwen-dev/hunter
watch dirs for changes
This commit is contained in:
parent
bdbe8e07e3
commit
fd67621dee
|
@ -19,6 +19,7 @@ chrono = "0.4"
|
|||
libc = "*"
|
||||
failure = "0.1.5"
|
||||
failure_derive = "0.1.1"
|
||||
notify = "4.0.9"
|
||||
|
||||
#[profile.release]
|
||||
#debug = true
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use failure;
|
||||
use failure::Fail;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub type HResult<T> = Result<T, HError>;
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
|
@ -28,7 +30,9 @@ pub enum HError {
|
|||
#[fail(display = "Not ready yet!")]
|
||||
WillBeNotReady,
|
||||
#[fail(display = "No widget found")]
|
||||
NoWidgetError
|
||||
NoWidgetError,
|
||||
#[fail(display = "Path: {:?} not in this directory: {:?}", path, dir)]
|
||||
WrongDirectoryError{ path: PathBuf, dir: PathBuf }
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for HError {
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
use termion::event::Key;
|
||||
use notify::{INotifyWatcher, Watcher, DebouncedEvent, RecursiveMode};
|
||||
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::coordinates::{Coordinates};
|
||||
use crate::files::{File, Files};
|
||||
|
@ -11,12 +15,17 @@ use crate::miller_columns::MillerColumns;
|
|||
use crate::widget::Widget;
|
||||
use crate::tabview::{TabView, Tabbable};
|
||||
use crate::preview::WillBeWidget;
|
||||
use crate::fail::HResult;
|
||||
use crate::fail::{HResult, HError};
|
||||
use crate::window::{Events, send_event};
|
||||
|
||||
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct FileBrowser {
|
||||
pub columns: MillerColumns<WillBeWidget<ListView<Files>>>,
|
||||
pub cwd: File
|
||||
pub cwd: File,
|
||||
watcher: INotifyWatcher,
|
||||
watches: Vec<PathBuf>,
|
||||
dir_events: Arc<Mutex<Vec<DebouncedEvent>>>
|
||||
}
|
||||
|
||||
impl Tabbable for TabView<FileBrowser> {
|
||||
|
@ -67,6 +76,23 @@ impl Tabbable for TabView<FileBrowser> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
fn watch_dir(rx: Receiver<DebouncedEvent>, dir_events: Arc<Mutex<Vec<DebouncedEvent>>>) {
|
||||
std::thread::spawn(move || {
|
||||
for event in rx.iter() {
|
||||
dir_events.lock().unwrap().push(event);
|
||||
send_event(Events::WidgetReady).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
impl FileBrowser {
|
||||
pub fn new() -> Result<FileBrowser, Box<Error>> {
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
|
@ -93,9 +119,17 @@ impl FileBrowser {
|
|||
|
||||
|
||||
let cwd = File::new_from_path(&cwd).unwrap();
|
||||
let dir_events = Arc::new(Mutex::new(vec![]));
|
||||
|
||||
let (tx_watch, rx_watch) = channel();
|
||||
let watcher = INotifyWatcher::new(tx_watch, Duration::from_secs(2)).unwrap();
|
||||
watch_dir(rx_watch, dir_events.clone());
|
||||
|
||||
Ok(FileBrowser { columns: miller,
|
||||
cwd: cwd })
|
||||
cwd: cwd,
|
||||
watcher: watcher,
|
||||
watches: vec![],
|
||||
dir_events: dir_events })
|
||||
}
|
||||
|
||||
pub fn enter_dir(&mut self) -> HResult<()> {
|
||||
|
@ -188,6 +222,70 @@ impl FileBrowser {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn left_dir(&self) -> HResult<File> {
|
||||
let widget = self.columns.get_left_widget()?.widget()?;
|
||||
let dir = (*widget.lock()?).as_ref()?.content.directory.clone();
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
fn update_watches(&mut self) -> HResult<()> {
|
||||
let watched_dirs = self.watches.clone();
|
||||
let cwd = self.cwd()?;
|
||||
let left_dir = self.left_dir()?;
|
||||
let preview_dir = self.selected_file().ok().map(|f| f.path);
|
||||
|
||||
for watched_dir in watched_dirs.iter() {
|
||||
if watched_dir != &cwd.path && watched_dir != &left_dir.path &&
|
||||
Some(watched_dir.clone()) != preview_dir {
|
||||
self.watcher.unwatch(&watched_dir).unwrap();
|
||||
self.watches.remove_item(&watched_dir);
|
||||
}
|
||||
}
|
||||
if !watched_dirs.contains(&cwd.path) {
|
||||
self.watcher.watch(&cwd.path, RecursiveMode::NonRecursive).unwrap();
|
||||
self.watches.push(cwd.path);
|
||||
}
|
||||
if !watched_dirs.contains(&left_dir.path) {
|
||||
self.watcher.watch(&left_dir.path, RecursiveMode::NonRecursive).unwrap();
|
||||
self.watches.push(left_dir.path);
|
||||
}
|
||||
if let Some(preview_dir) = preview_dir {
|
||||
if !watched_dirs.contains(&preview_dir) {
|
||||
self.watcher.watch(&preview_dir, RecursiveMode::NonRecursive).unwrap();
|
||||
self.watches.push(preview_dir);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_dir_events(&mut self) -> HResult<()> {
|
||||
let mut dir_events = self.dir_events.lock()?;
|
||||
for event in dir_events.iter() {
|
||||
let main_widget = self.columns.get_main_widget()?.widget()?;
|
||||
let main_files = &mut (*main_widget.lock()?);
|
||||
let main_files = &mut main_files.as_mut()?.content;
|
||||
let main_result = main_files.handle_event(event);
|
||||
|
||||
let left_widget = self.columns.get_left_widget()?.widget()?;
|
||||
let left_files = &mut (*left_widget.lock()?);
|
||||
let left_files = &mut left_files.as_mut()?.content;
|
||||
let left_result = left_files.handle_event(event);
|
||||
|
||||
match main_result {
|
||||
Err(HError::WrongDirectoryError { .. }) => {
|
||||
match left_result {
|
||||
Err(HError::WrongDirectoryError { .. }) => {
|
||||
let preview = &mut self.columns.preview;
|
||||
preview.reload();
|
||||
}, _ => {}
|
||||
}
|
||||
}, _ => {}
|
||||
}
|
||||
}
|
||||
dir_events.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn selected_file(&self) -> HResult<File> {
|
||||
let widget = self.main_widget()?;
|
||||
let file = widget.lock()?.as_ref()?.selected_file().clone();
|
||||
|
@ -352,11 +450,13 @@ impl Widget for FileBrowser {
|
|||
crate::term::goto_xy(count_xpos, count_ypos), file_count)
|
||||
}
|
||||
fn refresh(&mut self) {
|
||||
self.update_preview().ok();
|
||||
self.handle_dir_events().ok();
|
||||
self.columns.refresh();
|
||||
self.fix_left().ok();
|
||||
self.fix_selection().ok();
|
||||
self.set_cwd().ok();
|
||||
self.columns.refresh();
|
||||
self.update_watches().ok();
|
||||
self.update_preview().ok();
|
||||
}
|
||||
|
||||
fn get_drawlist(&self) -> String {
|
||||
|
@ -378,3 +478,14 @@ impl Widget for FileBrowser {
|
|||
self.update_preview().ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FileBrowser {
|
||||
fn eq(&self, other: &FileBrowser) -> bool {
|
||||
if self.columns == other.columns && self.cwd == other.cwd {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
55
src/files.rs
55
src/files.rs
|
@ -8,8 +8,9 @@ use mime_detective;
|
|||
use users;
|
||||
use chrono::TimeZone;
|
||||
use failure::Error;
|
||||
use notify::{INotifyWatcher, Watcher, DebouncedEvent, RecursiveMode};
|
||||
|
||||
use crate::fail::HResult;
|
||||
use crate::fail::{HResult, HError};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
@ -189,6 +190,53 @@ impl Files {
|
|||
self.files = files;
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: &DebouncedEvent) -> HResult<()> {
|
||||
match event {
|
||||
DebouncedEvent::Create(path) => {
|
||||
self.path_in_here(&path)?;
|
||||
let file = File::new_from_path(&path)?;
|
||||
self.files.push(file);
|
||||
},
|
||||
DebouncedEvent::Write(path) | DebouncedEvent::Chmod(path) => {
|
||||
self.path_in_here(&path)?;
|
||||
let file = self.find_file_with_path(&path)?;
|
||||
file.reload_meta();
|
||||
},
|
||||
DebouncedEvent::Remove(path) => {
|
||||
self.path_in_here(&path)?;
|
||||
let file = self.find_file_with_path(&path)?.clone();
|
||||
self.files.remove_item(&file);
|
||||
},
|
||||
DebouncedEvent::Rename(old_path, new_path) => {
|
||||
self.path_in_here(&new_path)?;
|
||||
let mut file = self.find_file_with_path(&old_path)?;
|
||||
file.name = new_path.file_name()?.to_string_lossy().to_string();
|
||||
file.path = new_path.into();
|
||||
},
|
||||
DebouncedEvent::Error(err, path) => {
|
||||
dbg!(err);
|
||||
dbg!(path);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn path_in_here(&self, path: &Path) -> HResult<bool> {
|
||||
let dir = self.directory.path();
|
||||
let path = if path.is_dir() { path } else { path.parent().unwrap() };
|
||||
if dir == path {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(HError::WrongDirectoryError{path: path.into(),
|
||||
dir: dir})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_file_with_path(&mut self, path: &Path) -> Option<&mut File> {
|
||||
self.files.iter_mut().find(|file| file.path == path)
|
||||
}
|
||||
|
||||
pub fn meta_all(&mut self) {
|
||||
let len = self.files.len();
|
||||
self.meta_upto(len);
|
||||
|
@ -306,6 +354,11 @@ impl File {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reload_meta(&mut self) -> HResult<()> {
|
||||
self.meta = None;
|
||||
self.get_meta()
|
||||
}
|
||||
|
||||
fn get_color(&self, meta: &std::fs::Metadata) -> Option<lscolors::Color> {
|
||||
match COLORS.style_for_path_with_metadata(&self.path, Some(&meta)) {
|
||||
Some(style) => style.clone().foreground,
|
||||
|
|
|
@ -379,6 +379,9 @@ impl<T> Widget for ListView<T> where ListView<T>: Listable {
|
|||
fn refresh(&mut self) {
|
||||
self.on_refresh();
|
||||
self.lines = self.len();
|
||||
if self.selection >= self.lines {
|
||||
self.selection -= 1;
|
||||
}
|
||||
self.buffer = self.render();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ extern crate chrono;
|
|||
extern crate mime_detective;
|
||||
extern crate rayon;
|
||||
extern crate libc;
|
||||
extern crate notify;
|
||||
|
||||
use termion::input::MouseTerminal;
|
||||
use termion::raw::IntoRawMode;
|
||||
|
|
|
@ -265,6 +265,13 @@ impl Previewer {
|
|||
}))));
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
if let Some(file) = self.file.clone() {
|
||||
self.file = None;
|
||||
self.set_file(&file);
|
||||
}
|
||||
}
|
||||
|
||||
fn preview_failed(file: &File) -> HResult<WidgetO> {
|
||||
Err(HError::PreviewFailed { file: file.name.clone() })
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue