mirror of https://github.com/bobwen-dev/hunter
global file cache/event dispatch
This commit is contained in:
parent
8d8d9631b5
commit
b52e63fd8d
|
@ -88,7 +88,7 @@ pub enum HError {
|
|||
}
|
||||
|
||||
impl HError {
|
||||
pub fn log(log: String) -> HResult<()> {
|
||||
pub fn log<T>(log: String) -> HResult<T> {
|
||||
Err(HError::Log(log))
|
||||
}
|
||||
pub fn quit() -> HResult<()> {
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
use termion::event::Key;
|
||||
use notify::{INotifyWatcher, Watcher, DebouncedEvent, RecursiveMode};
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
|
||||
use crate::files::{File, Files, PathBufExt};
|
||||
use crate::fscache::FsCache;
|
||||
use crate::listview::ListView;
|
||||
use crate::hbox::HBox;
|
||||
use crate::widget::Widget;
|
||||
use crate::dirty::Dirtyable;
|
||||
use crate::tabview::{TabView, Tabbable};
|
||||
use crate::preview::{Previewer, AsyncWidget};
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
|
@ -68,35 +64,37 @@ pub struct FileBrowser {
|
|||
pub columns: HBox<FileBrowserWidgets>,
|
||||
pub cwd: File,
|
||||
pub prev_cwd: Option<File>,
|
||||
selections: HashMap<File, File>,
|
||||
cached_files: HashMap<File, Files>,
|
||||
core: WidgetCore,
|
||||
watcher: INotifyWatcher,
|
||||
watches: Vec<PathBuf>,
|
||||
dir_events: Arc<Mutex<Vec<DebouncedEvent>>>,
|
||||
proc_view: Arc<Mutex<ProcView>>,
|
||||
bookmarks: Arc<Mutex<BMPopup>>,
|
||||
log_view: Arc<Mutex<LogView>>
|
||||
log_view: Arc<Mutex<LogView>>,
|
||||
fs_cache: FsCache,
|
||||
}
|
||||
|
||||
impl Tabbable for TabView<FileBrowser> {
|
||||
fn new_tab(&mut self) -> HResult<()> {
|
||||
let mut tab = FileBrowser::new_cored(&self.active_tab_().core)?;
|
||||
let cur_tab = self.active_tab_();
|
||||
|
||||
let proc_view = self.active_tab_().proc_view.clone();
|
||||
let bookmarks = self.active_tab_().bookmarks.clone();
|
||||
let log_view = self.active_tab_().log_view.clone();
|
||||
let settings = cur_tab.fs_cache.tab_settings.read()?.clone();
|
||||
let cache = cur_tab.fs_cache.new_client(settings).ok();
|
||||
|
||||
let mut tab = FileBrowser::new(&self.active_tab_().core, cache)?;
|
||||
|
||||
let proc_view = cur_tab.proc_view.clone();
|
||||
let bookmarks = cur_tab.bookmarks.clone();
|
||||
let log_view = cur_tab.log_view.clone();
|
||||
tab.proc_view = proc_view;
|
||||
tab.bookmarks = bookmarks;
|
||||
tab.log_view = log_view;
|
||||
|
||||
self.push_widget(tab)?;
|
||||
self.active += 1;
|
||||
self.active = self.widgets.len() - 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close_tab(&mut self) -> HResult<()> {
|
||||
self.close_tab_()
|
||||
self.close_tab_().log();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_tab(&mut self) -> HResult<()> {
|
||||
|
@ -148,29 +146,36 @@ impl Tabbable for TabView<FileBrowser> {
|
|||
_ => { self.active_tab_mut().on_key(key) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_refresh(&mut self) -> HResult<()> {
|
||||
let fs_changes = self.active_tab_()
|
||||
.fs_cache
|
||||
.fs_changes
|
||||
.write()?
|
||||
.drain(..)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
|
||||
|
||||
fn watch_dir(rx: Receiver<DebouncedEvent>,
|
||||
dir_events: Arc<Mutex<Vec<DebouncedEvent>>>,
|
||||
sender: Sender<Events>) {
|
||||
std::thread::spawn(move || {
|
||||
for event in rx.iter() {
|
||||
dir_events.lock().unwrap().push(event);
|
||||
sender.send(Events::WidgetReady).unwrap();
|
||||
for tab in &mut self.widgets {
|
||||
for (dir, old_file, new_file) in fs_changes.iter() {
|
||||
tab.replace_file(&dir,
|
||||
old_file.as_ref(),
|
||||
new_file.as_ref()).log()
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
impl FileBrowser {
|
||||
pub fn new_cored(core: &WidgetCore) -> HResult<FileBrowser> {
|
||||
pub fn new(core: &WidgetCore, cache: Option<FsCache>) -> HResult<FileBrowser> {
|
||||
let fs_cache = cache.unwrap_or_else(|| FsCache::new(core.get_sender()));
|
||||
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let mut core_m = core.clone();
|
||||
let mut core_l = core.clone();
|
||||
|
@ -191,18 +196,40 @@ impl FileBrowser {
|
|||
}).last()?;
|
||||
let left_path = main_path.parent().map(|p| p.to_path_buf());
|
||||
|
||||
let cache = fs_cache.clone();
|
||||
let main_widget = AsyncWidget::new(&core, Box::new(move |_| {
|
||||
let mut list = ListView::new(&core_m,
|
||||
Files::new_from_path(&main_path)?);
|
||||
let main_dir = File::new(&main_path.file_name()?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
main_path.clone(),
|
||||
None);
|
||||
let files = cache.get_files_sync(&main_dir)?;
|
||||
let selection = cache.get_selection(&main_dir).ok();
|
||||
let mut list = ListView::new(&core_m.clone(),
|
||||
files);
|
||||
if let Some(file) = selection {
|
||||
list.select_file(&file);
|
||||
}
|
||||
list.animate_slide_up().log();
|
||||
list.content.meta_all();
|
||||
Ok(list)
|
||||
}));
|
||||
|
||||
let cache = fs_cache.clone();
|
||||
if let Some(left_path) = left_path {
|
||||
let left_widget = AsyncWidget::new(&core, Box::new(move |_| {
|
||||
let left_dir = File::new(&left_path.file_name()?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
left_path.clone(),
|
||||
None);
|
||||
let files = cache.get_files_sync(&left_dir)?;
|
||||
let selection = cache.get_selection(&left_dir).ok();
|
||||
let mut list = ListView::new(&core_l,
|
||||
Files::new_from_path(&left_path)?);
|
||||
files);
|
||||
if let Some(file) = selection {
|
||||
list.select_file(&file);
|
||||
}
|
||||
list.animate_slide_up().log();
|
||||
Ok(list)
|
||||
}));
|
||||
|
@ -210,7 +237,7 @@ impl FileBrowser {
|
|||
columns.push_widget(left_widget);
|
||||
}
|
||||
|
||||
let previewer = Previewer::new(&core_p);
|
||||
let previewer = Previewer::new(&core_p, fs_cache.clone());
|
||||
|
||||
columns.push_widget(FileBrowserWidgets::FileList(main_widget));
|
||||
columns.push_widget(FileBrowserWidgets::Previewer(previewer));
|
||||
|
@ -219,11 +246,6 @@ impl FileBrowser {
|
|||
|
||||
|
||||
let cwd = File::new_from_path(&cwd, None).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(), core.get_sender());
|
||||
|
||||
let proc_view = ProcView::new(&core);
|
||||
let bookmarks = BMPopup::new(&core);
|
||||
|
@ -234,15 +256,12 @@ impl FileBrowser {
|
|||
Ok(FileBrowser { columns: columns,
|
||||
cwd: cwd,
|
||||
prev_cwd: None,
|
||||
selections: HashMap::new(),
|
||||
cached_files: HashMap::new(),
|
||||
core: core.clone(),
|
||||
watcher: watcher,
|
||||
watches: vec![],
|
||||
dir_events: dir_events,
|
||||
proc_view: Arc::new(Mutex::new(proc_view)),
|
||||
bookmarks: Arc::new(Mutex::new(bookmarks)),
|
||||
log_view: Arc::new(Mutex::new(log_view)) })
|
||||
log_view: Arc::new(Mutex::new(log_view)),
|
||||
fs_cache: fs_cache,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enter_dir(&mut self) -> HResult<()> {
|
||||
|
@ -306,33 +325,25 @@ impl FileBrowser {
|
|||
}
|
||||
|
||||
pub fn main_widget_goto(&mut self, dir: &File) -> HResult<()> {
|
||||
|
||||
self.cache_files().log();
|
||||
|
||||
let dir = dir.clone();
|
||||
let selected_file = self.get_selection(&dir).ok().cloned();
|
||||
|
||||
self.get_files().and_then(|files| self.cache_files(files)).log();
|
||||
self.get_left_files().and_then(|files| self.cache_files(files)).log();
|
||||
let cached_files = self.get_cached_files(&dir).ok();
|
||||
let cache = self.fs_cache.clone();
|
||||
|
||||
self.prev_cwd = Some(self.cwd.clone());
|
||||
self.cwd = dir.clone();
|
||||
|
||||
let main_async_widget = self.main_async_widget_mut()?;
|
||||
main_async_widget.change_to(Box::new(move |stale, core| {
|
||||
let path = dir.path();
|
||||
let cached_files = cached_files.clone();
|
||||
|
||||
let files = cached_files.or_else(|| {
|
||||
Files::new_from_path_cancellable(&path, stale.clone()).ok()
|
||||
})?;
|
||||
let (selected_file, files) = cache.get_files(&dir, stale)?;
|
||||
let files = files.wait()?;
|
||||
|
||||
let mut list = ListView::new(&core, files);
|
||||
|
||||
list.content.meta_set_fresh().log();
|
||||
|
||||
if let Some(file) = &selected_file {
|
||||
list.select_file(file);
|
||||
if let Some(file) = selected_file {
|
||||
list.select_file(&file);
|
||||
}
|
||||
Ok(list)
|
||||
})).log();
|
||||
|
@ -341,7 +352,7 @@ impl FileBrowser {
|
|||
self.left_widget_goto(&grand_parent).log();
|
||||
} else {
|
||||
self.left_async_widget_mut()?.clear().log();
|
||||
self.screen()?.flush();
|
||||
Ok(self.screen()?.flush()?).log();
|
||||
self.left_async_widget_mut()?.set_stale().log();
|
||||
}
|
||||
|
||||
|
@ -349,17 +360,15 @@ impl FileBrowser {
|
|||
}
|
||||
|
||||
pub fn left_widget_goto(&mut self, dir: &File) -> HResult<()> {
|
||||
let cached_files = self.get_cached_files(&dir).ok();
|
||||
let cache = self.fs_cache.clone();
|
||||
let dir = dir.clone();
|
||||
|
||||
let left_async_widget = self.left_async_widget_mut()?;
|
||||
left_async_widget.change_to(Box::new(move |stale, core| {
|
||||
let path = dir.path();
|
||||
let cached_files = cached_files.clone();
|
||||
let cached_files = cache.get_files(&dir, stale)?;
|
||||
let (_, files) = cached_files;
|
||||
|
||||
let files = cached_files.or_else(|| {
|
||||
Files::new_from_path_cancellable(&path, stale).ok()
|
||||
})?;
|
||||
let files = files.wait()?;
|
||||
|
||||
let list = ListView::new(&core, files);
|
||||
Ok(list)
|
||||
|
@ -430,10 +439,8 @@ impl FileBrowser {
|
|||
return Ok(());
|
||||
}
|
||||
let file = self.selected_file()?.clone();
|
||||
let selection = self.get_selection(&file).ok().cloned();
|
||||
let cached_files = self.get_cached_files(&file).ok();
|
||||
let preview = self.preview_widget_mut()?;
|
||||
preview.set_file(&file, selection, cached_files).log();
|
||||
preview.set_file(&file).log();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -441,47 +448,45 @@ impl FileBrowser {
|
|||
if !self.left_async_widget_mut()?.ready() { return Ok(()) }
|
||||
if self.cwd.parent().is_none() { return Ok(()) }
|
||||
|
||||
let parent = self.cwd()?.parent_as_file();
|
||||
let selection = self.cwd()?.clone();
|
||||
|
||||
let left_selection = self.get_selection(&parent?)?.clone();
|
||||
self.left_widget_mut()?.select_file(&left_selection);
|
||||
self.left_widget_mut()?.select_file(&selection);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_selection(&self, dir: &File) -> HResult<&File> {
|
||||
Ok(self.selections.get(dir)?)
|
||||
}
|
||||
|
||||
pub fn get_files(&mut self) -> HResult<Files> {
|
||||
pub fn get_files(&self) -> HResult<Files> {
|
||||
Ok(self.main_widget()?.content.clone())
|
||||
}
|
||||
|
||||
pub fn get_left_files(&mut self) -> HResult<Files> {
|
||||
pub fn get_left_files(&self) -> HResult<Files> {
|
||||
Ok(self.left_widget()?.content.clone())
|
||||
}
|
||||
|
||||
pub fn cache_files(&mut self, files: Files) -> HResult<()> {
|
||||
let dir = files.directory.clone();
|
||||
self.cached_files.insert(dir, files);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_cached_files(&mut self, dir: &File) -> HResult<Files> {
|
||||
Ok(self.cached_files.get(dir)?.clone())
|
||||
}
|
||||
|
||||
pub fn save_selection(&mut self) -> HResult<()> {
|
||||
let cwd = self.cwd()?.clone();
|
||||
if let Ok(main_selection) = self.selected_file() {
|
||||
self.selections.insert(cwd.clone(), main_selection);
|
||||
pub fn cache_files(&self) -> HResult<()> {
|
||||
if !self.fs_cache.is_cached(&self.cwd)? {
|
||||
let files = self.get_files()?;
|
||||
let selected_file = self.selected_file().ok();
|
||||
self.fs_cache.put_files(files, selected_file).log();
|
||||
} else {
|
||||
let files = &self.main_widget()?.content;
|
||||
let selected_file = self.selected_file().ok();
|
||||
self.fs_cache.save_settings(&files, selected_file).log();
|
||||
}
|
||||
if let Ok(left_dir) = self.cwd()?.parent_as_file() {
|
||||
self.selections.insert(left_dir, cwd);
|
||||
|
||||
if !self.fs_cache.is_cached(&self.left_widget()?.content.directory)? {
|
||||
let left_selection = self.left_widget()?.clone_selected_file();
|
||||
let left_files = self.get_left_files()?;
|
||||
self.fs_cache.put_files(left_files, Some(left_selection)).log();
|
||||
} else {
|
||||
let files = &self.left_widget()?.content;
|
||||
let selected_file = self.left_widget()?.clone_selected_file();
|
||||
self.fs_cache.save_settings(&files, Some(selected_file)).log();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn cwd(&self) -> HResult<&File> {
|
||||
Ok(&self.cwd)
|
||||
}
|
||||
|
@ -492,74 +497,22 @@ impl FileBrowser {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn left_dir(&self) -> HResult<File> {
|
||||
pub fn left_dir(&self) -> HResult<&File> {
|
||||
let widget = self.left_widget()?;
|
||||
let dir = widget.content.directory.clone();
|
||||
let dir = &widget.content.directory;
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
fn update_watches(&mut self) -> HResult<()> {
|
||||
if !self.left_async_widget_mut()?.ready() ||
|
||||
!self.main_async_widget_mut()?.ready() {
|
||||
return Ok(())
|
||||
fn replace_file(&mut self,
|
||||
dir: &File,
|
||||
old: Option<&File>,
|
||||
new: Option<&File>) -> HResult<()> {
|
||||
if &self.cwd == dir {
|
||||
self.main_widget_mut()?.content.replace_file(old, new.cloned()).log();
|
||||
}
|
||||
let watched_dirs = self.watches.clone();
|
||||
let cwd = self.cwd()?.clone();
|
||||
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).ok();
|
||||
self.watches.remove_item(&watched_dir);
|
||||
}
|
||||
if &self.left_dir()? == &dir {
|
||||
self.left_widget_mut()?.content.replace_file(old, new.cloned()).log();
|
||||
}
|
||||
if !watched_dirs.contains(&cwd.path) {
|
||||
self.watcher.watch(&cwd.path, RecursiveMode::NonRecursive)?;
|
||||
self.watches.push(cwd.path);
|
||||
}
|
||||
if !watched_dirs.contains(&left_dir.path) {
|
||||
self.watcher.watch(&left_dir.path, RecursiveMode::NonRecursive)?;
|
||||
self.watches.push(left_dir.path);
|
||||
}
|
||||
if let Some(preview_dir) = preview_dir {
|
||||
if !watched_dirs.contains(&preview_dir) && preview_dir.is_dir() {
|
||||
match self.watcher.watch(&preview_dir, RecursiveMode::NonRecursive) {
|
||||
Ok(_) => self.watches.push(preview_dir),
|
||||
Err(notify::Error::Io(ioerr)) => {
|
||||
if ioerr.kind() != std::io::ErrorKind::PermissionDenied {
|
||||
Err(ioerr)?
|
||||
}
|
||||
}
|
||||
err @ _ => err?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_dir_events(&mut self) -> HResult<()> {
|
||||
let dir_events = self.dir_events.clone();
|
||||
for event in dir_events.lock()?.iter() {
|
||||
let main_widget = self.main_widget_mut()?;
|
||||
let main_result = main_widget.content.handle_event(event);
|
||||
|
||||
let left_widget = self.left_widget_mut()?;
|
||||
let left_result = left_widget.content.handle_event(event);
|
||||
|
||||
match main_result {
|
||||
Err(HError::WrongDirectoryError { .. }) => {
|
||||
match left_result {
|
||||
Err(HError::WrongDirectoryError { .. }) => {
|
||||
let preview = self.preview_widget_mut()?;
|
||||
preview.reload();
|
||||
}, _ => {}
|
||||
}
|
||||
}, _ => {}
|
||||
}
|
||||
}
|
||||
dir_events.lock()?.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -631,13 +584,6 @@ impl FileBrowser {
|
|||
widget
|
||||
}
|
||||
|
||||
// pub fn preview_widget(&self) -> HResult<&Previewer> {
|
||||
// match self.columns.widgets.get(2)? {
|
||||
// FileBrowserWidgets::Previewer(previewer) => Ok(previewer),
|
||||
// _ => { return HError::wrong_widget("filelist", "previewer"); }
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn preview_widget_mut(&mut self) -> HResult<&mut Previewer> {
|
||||
match self.columns.widgets.get_mut(2)? {
|
||||
FileBrowserWidgets::Previewer(previewer) => Ok(previewer),
|
||||
|
@ -843,16 +789,13 @@ impl Widget for FileBrowser {
|
|||
}
|
||||
}
|
||||
fn refresh(&mut self) -> HResult<()> {
|
||||
//self.proc_view.lock()?.set_coordinates(self.get_coordinates()?);
|
||||
self.set_title().log();
|
||||
self.handle_dir_events().log();
|
||||
self.columns.refresh().log();
|
||||
self.set_left_selection().log();
|
||||
self.save_selection().log();
|
||||
self.set_cwd().log();
|
||||
self.update_watches().log();
|
||||
if !self.columns.zoom_active { self.update_preview().log(); }
|
||||
self.columns.refresh().log();
|
||||
self.cache_files().log();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
56
src/files.rs
56
src/files.rs
|
@ -18,7 +18,6 @@ use users::{get_current_username,
|
|||
use chrono::TimeZone;
|
||||
use failure::Error;
|
||||
use notify::DebouncedEvent;
|
||||
use rayon::prelude::*;
|
||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
|
@ -234,7 +233,7 @@ impl Files {
|
|||
.files
|
||||
.sort_by(|a, b| alphanumeric_sort::compare_str(&a.name, &b.name)),
|
||||
SortBy::Size => {
|
||||
self.meta_all_sync();
|
||||
self.meta_all_sync().log();
|
||||
self.files.sort_by(|a, b| {
|
||||
if a.meta().unwrap().size() == b.meta().unwrap().size() {
|
||||
return alphanumeric_sort::compare_str(&b.name, &a.name);
|
||||
|
@ -243,7 +242,7 @@ impl Files {
|
|||
});
|
||||
}
|
||||
SortBy::MTime => {
|
||||
self.meta_all_sync();
|
||||
self.meta_all_sync().log();
|
||||
self.files.sort_by(|a, b| {
|
||||
if a.meta().unwrap().mtime() == b.meta().unwrap().mtime() {
|
||||
return alphanumeric_sort::compare_str(&a.name, &b.name);
|
||||
|
@ -292,7 +291,17 @@ impl Files {
|
|||
self.show_hidden = !self.show_hidden
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: &DebouncedEvent) -> HResult<()> {
|
||||
pub fn replace_file(&mut self,
|
||||
old: Option<&File>,
|
||||
new: Option<File>) -> HResult<()> {
|
||||
old.map(|old| self.files.remove_item(old));
|
||||
new.map(|new| self.files.push(new));
|
||||
self.sort();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self,
|
||||
event: &DebouncedEvent) -> HResult<()> {
|
||||
match event {
|
||||
DebouncedEvent::Create(path) => {
|
||||
self.path_in_here(&path)?;
|
||||
|
@ -329,12 +338,12 @@ impl Files {
|
|||
}
|
||||
|
||||
pub fn path_in_here(&self, path: &Path) -> HResult<bool> {
|
||||
let dir = self.directory.path();
|
||||
let dir = &self.directory.path;
|
||||
let path = if path.is_dir() { path } else { path.parent().unwrap() };
|
||||
if dir == path {
|
||||
Ok(true)
|
||||
} else {
|
||||
HError::wrong_directory(path.into(), dir)?
|
||||
HError::wrong_directory(path.into(), dir.to_path_buf())?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,15 +354,10 @@ impl Files {
|
|||
pub fn meta_all_sync(&mut self) -> HResult<()> {
|
||||
for file in self.files.iter_mut() {
|
||||
if !file.meta_processed {
|
||||
let path = file.path.clone();
|
||||
file.meta = Async::new(Box::new(move|_| {
|
||||
let meta = std::fs::metadata(&path)?;
|
||||
Ok(meta)
|
||||
}));
|
||||
file.meta.wait()?;
|
||||
file.meta_sync().log();
|
||||
}
|
||||
}
|
||||
self.dirty_meta.set_dirty();
|
||||
self.set_dirty();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -476,7 +480,6 @@ pub struct File {
|
|||
pub meta_processed: bool,
|
||||
pub selected: bool,
|
||||
pub tag: Option<bool>
|
||||
// flags: Option<String>,
|
||||
}
|
||||
|
||||
impl File {
|
||||
|
@ -552,6 +555,14 @@ impl File {
|
|||
Ok(file)
|
||||
}
|
||||
|
||||
pub fn meta_sync(&mut self) -> HResult<()> {
|
||||
let stale = self.meta.get_stale();
|
||||
let meta = std::fs::metadata(&self.path)?;
|
||||
self.meta = Async::new_with_value(meta);
|
||||
self.meta.put_stale(stale);
|
||||
self.process_meta()
|
||||
}
|
||||
|
||||
pub fn make_async_meta(path: &PathBuf,
|
||||
dirty_meta: Option<AsyncDirtyBit>,
|
||||
stale_preview: Option<Stale>) -> Async<Metadata> {
|
||||
|
@ -632,6 +643,12 @@ impl File {
|
|||
err @ Err(_) => { err?; }
|
||||
}
|
||||
|
||||
self.process_meta()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_meta(&mut self) -> HResult<()> {
|
||||
if let Ok(meta) = self.meta.get() {
|
||||
let color = self.get_color(&meta);
|
||||
let target = if meta.file_type().is_symlink() {
|
||||
|
@ -641,10 +658,7 @@ impl File {
|
|||
self.color = color;
|
||||
self.target = target;
|
||||
self.meta_processed = true;
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -653,11 +667,12 @@ impl File {
|
|||
self.meta = File::make_async_meta(&self.path,
|
||||
self.dirty_meta.clone(),
|
||||
None);
|
||||
self.meta.run();
|
||||
self.meta.run().log();
|
||||
|
||||
if self.dirsize.is_some() {
|
||||
self.dirsize
|
||||
= Some(File::make_async_dirsize(&self.path, self.dirty_meta.clone(), None));
|
||||
self.dirsize.as_mut()?.run();
|
||||
self.dirsize.as_mut()?.run().log();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -872,7 +887,6 @@ impl File {
|
|||
|
||||
pub fn pretty_mtime(&self) -> Option<String> {
|
||||
if self.meta().is_err() { return None }
|
||||
//let time = chrono::DateTime::from_timestamp(self.mtime, 0);
|
||||
let time: chrono::DateTime<chrono::Local>
|
||||
= chrono::Local.timestamp(self.meta().unwrap().mtime(), 0);
|
||||
Some(time.format("%F %R").to_string())
|
||||
|
@ -930,7 +944,6 @@ impl PathBufExt for PathBuf {
|
|||
if let Some(name) = self.file_name() {
|
||||
let mut name = name.as_bytes().to_vec();
|
||||
let mut quote = "\"".as_bytes().to_vec();
|
||||
//let mut quote_after = "\"".as_bytes().to_vec();
|
||||
let mut quoted = vec![];
|
||||
quoted.append(&mut quote.clone());
|
||||
quoted.append(&mut name);
|
||||
|
@ -988,7 +1001,6 @@ impl OsStrTools for OsStr {
|
|||
split_pos
|
||||
}).iter()
|
||||
.map(|(start, end)| {
|
||||
//let orig_string = orig_string.clone();
|
||||
OsString::from_vec(orig_string[*start..*end]
|
||||
.to_vec()).replace(&OsString::from_vec(pat.clone()),
|
||||
&OsString::from(""))
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
use notify::{INotifyWatcher, Watcher, DebouncedEvent, RecursiveMode};
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::mpsc::{channel, Sender, Receiver};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::preview::{Async, Stale};
|
||||
use crate::files::{Files, File, SortBy};
|
||||
use crate::dirty::*;
|
||||
use crate::widget::Events;
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirSettings {
|
||||
sort: SortBy,
|
||||
dirs_first: bool,
|
||||
reverse: bool,
|
||||
show_hidden: bool,
|
||||
filter: Option<String>,
|
||||
}
|
||||
|
||||
impl DirSettings {
|
||||
fn new() -> DirSettings {
|
||||
DirSettings {
|
||||
sort: SortBy::Name,
|
||||
dirs_first: true,
|
||||
reverse: false,
|
||||
show_hidden: true,
|
||||
filter: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TabSettings {
|
||||
selection: Option<File>,
|
||||
multi_selections: Vec<File>,
|
||||
dir_settings: DirSettings,
|
||||
}
|
||||
|
||||
impl TabSettings {
|
||||
fn new() -> TabSettings {
|
||||
TabSettings {
|
||||
selection: None,
|
||||
multi_selections: vec![],
|
||||
dir_settings: DirSettings::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl std::fmt::Debug for FsCache {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(formatter,
|
||||
"{:?}\n{:?}\n{:?}",
|
||||
self.tab_settings,
|
||||
self.watched_dirs,
|
||||
self.files)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for FsCache {}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FsCache {
|
||||
files: Arc<RwLock<HashMap<File, Files>>>,
|
||||
pub tab_settings: Arc<RwLock<HashMap<File, TabSettings>>>,
|
||||
watched_dirs: Arc<RwLock<HashSet<File>>>,
|
||||
watcher: Arc<RwLock<INotifyWatcher>>,
|
||||
pub fs_changes: Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
|
||||
sender: Sender<Events>,
|
||||
}
|
||||
|
||||
impl FsCache {
|
||||
pub fn new(sender: Sender<Events>) -> FsCache {
|
||||
let (tx_fs_event, rx_fs_event) = channel();
|
||||
let watcher = INotifyWatcher::new(tx_fs_event,
|
||||
Duration::from_secs(2)).unwrap();
|
||||
|
||||
|
||||
let fs_cache = FsCache {
|
||||
files: Arc::new(RwLock::new(HashMap::new())),
|
||||
tab_settings: Arc::new(RwLock::new(HashMap::new())),
|
||||
watched_dirs: Arc::new(RwLock::new(HashSet::new())),
|
||||
watcher: Arc::new(RwLock::new(watcher)),
|
||||
fs_changes: Arc::new(RwLock::new(vec![])),
|
||||
sender: sender.clone(),
|
||||
};
|
||||
|
||||
watch_fs(rx_fs_event,
|
||||
fs_cache.files.clone(),
|
||||
fs_cache.fs_changes.clone(),
|
||||
sender.clone());
|
||||
|
||||
fs_cache
|
||||
}
|
||||
|
||||
pub fn new_client(&self, settings: HashMap<File, TabSettings>) -> HResult<FsCache> {
|
||||
let mut cache = self.clone();
|
||||
cache.tab_settings = Arc::new(RwLock::new(settings));
|
||||
Ok(cache)
|
||||
}
|
||||
}
|
||||
|
||||
pub type CachedFiles = (Option<File>, Async<Files>);
|
||||
|
||||
impl FsCache {
|
||||
pub fn get_files(&self, dir: &File, stale: Stale) -> HResult<CachedFiles> {
|
||||
if self.files.read()?.contains_key(dir) {
|
||||
self.get_cached_files(dir)
|
||||
} else {
|
||||
self.add_watch(&dir).log();
|
||||
let dir = dir.clone();
|
||||
let cache = self.files.clone();
|
||||
let files = Async::new(Box::new(move |_| {
|
||||
let files = Files::new_from_path_cancellable(&dir.path, stale)?;
|
||||
Ok(files)
|
||||
}));
|
||||
Ok((None, files))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_files_sync(&self, dir: &File) -> HResult<Files> {
|
||||
let mut files = self.get_files(&dir, Stale::new())?.1;
|
||||
files.wait()
|
||||
}
|
||||
|
||||
pub fn get_selection(&self, dir: &File) -> HResult<File> {
|
||||
Ok(self.tab_settings.read()?.get(&dir).as_ref()?.selection.as_ref()?.clone())
|
||||
}
|
||||
|
||||
pub fn save_settings(&self, files: &Files, selection: Option<File>) -> HResult<()> {
|
||||
let dir = files.directory.clone();
|
||||
let tab_settings = FsCache::extract_tab_settings(&files, selection);
|
||||
self.tab_settings.write()?.insert(dir, tab_settings);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn put_files(&self, files: Files, selection: Option<File>) -> HResult<()> {
|
||||
let dir = files.directory.clone();
|
||||
|
||||
let tab_settings = FsCache::extract_tab_settings(&files, selection);
|
||||
|
||||
self.tab_settings.write()?.insert(dir.clone(), tab_settings);
|
||||
|
||||
let mut file_cache = self.files.write()?;
|
||||
|
||||
if !file_cache.contains_key(&files.directory) {
|
||||
file_cache.insert(dir, files);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_cached(&self, dir: &File) -> HResult<bool> {
|
||||
Ok(self.files.read()?.contains_key(dir))
|
||||
}
|
||||
|
||||
fn add_watch(&self, dir: &File) -> HResult<()> {
|
||||
if !self.watched_dirs.read()?.contains(&dir) {
|
||||
self.watcher.write()?.watch(&dir.path, RecursiveMode::NonRecursive)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_watch(&self, dir: &File) -> HResult<()> {
|
||||
if self.watched_dirs.read()?.contains(&dir) {
|
||||
self.watched_dirs.write()?.remove(dir);
|
||||
self.watcher.write()?.unwatch(&dir.path)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_files(&self, dir: &File) -> HResult<CachedFiles> {
|
||||
let tab_settings = match self.tab_settings.read()?.get(&dir) {
|
||||
Some(tab_settings) => tab_settings.clone(),
|
||||
None => TabSettings::new()
|
||||
};
|
||||
let selection = tab_settings.selection.clone();
|
||||
let file_cache = self.files.clone();
|
||||
let dir = dir.clone();
|
||||
|
||||
let files = Async::new(Box::new(move |_| {
|
||||
let mut files = file_cache.read()?.get(&dir)?.clone();
|
||||
let tab_settings = &tab_settings;
|
||||
|
||||
files.sort = tab_settings.dir_settings.sort;
|
||||
files.dirs_first = tab_settings.dir_settings.dirs_first;
|
||||
files.reverse = tab_settings.dir_settings.reverse;
|
||||
files.show_hidden = tab_settings.dir_settings.show_hidden;
|
||||
files.filter = tab_settings.dir_settings.filter.clone();
|
||||
|
||||
if tab_settings.multi_selections.len() > 0 {
|
||||
for file in &mut files.files {
|
||||
for selected_files in &tab_settings.multi_selections {
|
||||
if file.path == selected_files.path {
|
||||
file.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files.sort();
|
||||
Ok(files)
|
||||
}));
|
||||
|
||||
Ok((selection, files))
|
||||
}
|
||||
|
||||
fn extract_tab_settings(files: &Files, selection: Option<File>) -> TabSettings {
|
||||
TabSettings {
|
||||
selection: selection,
|
||||
multi_selections: files.get_selected().into_iter().cloned().collect(),
|
||||
dir_settings: DirSettings {
|
||||
sort: files.sort,
|
||||
dirs_first: files.dirs_first,
|
||||
reverse: files.reverse,
|
||||
show_hidden: files.show_hidden,
|
||||
filter: files.filter.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn watch_fs(rx_fs_events: Receiver<DebouncedEvent>,
|
||||
fs_cache: Arc<RwLock<HashMap<File, Files>>>,
|
||||
fs_changes: Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
|
||||
sender: Sender<Events>) {
|
||||
std::thread::spawn(move || -> HResult<()> {
|
||||
for event in rx_fs_events.iter() {
|
||||
apply_event(&fs_cache, &fs_changes, event).log();
|
||||
|
||||
Ok(sender.send(Events::WidgetReady)?).log();
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
fn apply_event(fs_cache: &Arc<RwLock<HashMap<File, Files>>>,
|
||||
fs_changes: &Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
|
||||
event: DebouncedEvent)
|
||||
-> HResult<()> {
|
||||
let path = &event.get_source_path()?;
|
||||
|
||||
for dir in fs_cache.write()?.values_mut() {
|
||||
if dir.path_in_here(&path).unwrap_or(false) {
|
||||
let old_file = dir.find_file_with_path(&path).cloned();
|
||||
let dirty_meta = old_file
|
||||
.as_ref()
|
||||
.map(|f| f.dirty_meta.clone())
|
||||
.unwrap_or(None);
|
||||
let mut new_file = match event {
|
||||
DebouncedEvent::Remove(_) => None,
|
||||
_ => Some(File::new_from_path(&path, dirty_meta)?)
|
||||
};
|
||||
|
||||
new_file.as_mut().map(|file| file.meta_sync());
|
||||
dir.replace_file(old_file.as_ref(), new_file.clone()).log();
|
||||
|
||||
fs_changes.write()?.push((dir.directory.clone(),
|
||||
old_file,
|
||||
new_file));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
trait PathFromEvent {
|
||||
fn get_source_path(&self) -> HResult<&PathBuf>;
|
||||
}
|
||||
|
||||
impl PathFromEvent for DebouncedEvent {
|
||||
fn get_source_path(&self) -> HResult<&PathBuf> {
|
||||
match self {
|
||||
DebouncedEvent::Create(path) |
|
||||
DebouncedEvent::Write(path) |
|
||||
DebouncedEvent::Chmod(path) |
|
||||
DebouncedEvent::Remove(path) |
|
||||
DebouncedEvent::NoticeWrite(path) |
|
||||
DebouncedEvent::NoticeRemove(path) => Ok(path),
|
||||
DebouncedEvent::Rename(old_path, _) => Ok(old_path),
|
||||
DebouncedEvent::Error(err, path)
|
||||
=> Err(HError::INotifyError(format!("{}, {:?}", err, path))),
|
||||
DebouncedEvent::Rescan
|
||||
=> Err(HError::INotifyError("Need to rescan".to_string()))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#![feature(vec_remove_item)]
|
||||
#![feature(trivial_bounds)]
|
||||
#![feature(try_trait)]
|
||||
#![feature(fnbox)]
|
||||
|
||||
extern crate termion;
|
||||
extern crate unicode_width;
|
||||
|
@ -46,6 +47,9 @@ mod bookmarks;
|
|||
mod paths;
|
||||
mod foldview;
|
||||
mod dirty;
|
||||
mod fscache;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -84,7 +88,7 @@ fn main() -> HResult<()> {
|
|||
fn run(mut core: WidgetCore) -> HResult<()> {
|
||||
core.screen.clear()?;
|
||||
|
||||
let filebrowser = FileBrowser::new_cored(&core)?;
|
||||
let filebrowser = FileBrowser::new(&core, None)?;
|
||||
let mut tabview = TabView::new(&core);
|
||||
tabview.push_widget(filebrowser)?;
|
||||
|
||||
|
|
351
src/preview.rs
351
src/preview.rs
|
@ -1,8 +1,9 @@
|
|||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::boxed::FnBox;
|
||||
|
||||
use rayon::ThreadPool;
|
||||
|
||||
use crate::files::{File, Files, Kind};
|
||||
use crate::files::{File, Kind};
|
||||
use crate::listview::ListView;
|
||||
use crate::textview::TextView;
|
||||
use crate::widget::{Widget, WidgetCore};
|
||||
|
@ -10,10 +11,11 @@ use crate::coordinates::Coordinates;
|
|||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
|
||||
|
||||
pub type AsyncValueFn<T> = Box<Fn(Stale) -> HResult<T> + Send>;
|
||||
pub type AsyncValueFn<T> = Box<dyn FnBox(Stale) -> HResult<T> + Send + Sync>;
|
||||
pub type AsyncValue<T> = Arc<Mutex<Option<HResult<T>>>>;
|
||||
pub type AsyncReadyFn = Box<Fn() -> HResult<()> + Send>;
|
||||
pub type AsyncWidgetFn<W> = Box<Fn(Stale, WidgetCore) -> HResult<W> + Send>;
|
||||
pub type AsyncReadyFn = Box<dyn FnBox() -> HResult<()> + Send + Sync>;
|
||||
pub type AsyncWidgetFn<W> = Box<dyn FnBox(Stale, WidgetCore)
|
||||
-> HResult<W> + Send + Sync>;
|
||||
|
||||
|
||||
type WidgetO = Box<dyn Widget + Send>;
|
||||
|
@ -81,6 +83,8 @@ pub struct Async<T: Send> {
|
|||
stale: Stale,
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl<T: Send + 'static> Async<T> {
|
||||
pub fn new(closure: AsyncValueFn<T>)
|
||||
-> Async<T> {
|
||||
|
@ -120,6 +124,17 @@ impl<T: Send + 'static> Async<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn run_async(async_fn: Arc<Mutex<Option<AsyncValueFn<T>>>>,
|
||||
async_value: AsyncValue<T>,
|
||||
on_ready_fn: Arc<Mutex<Option<AsyncReadyFn>>>,
|
||||
stale: Stale) -> HResult<()> {
|
||||
let value_fn = async_fn.lock()?.take()?;
|
||||
let value = value_fn.call_box((stale.clone(),));
|
||||
async_value.lock()?.replace(value);
|
||||
on_ready_fn.lock()?.take()?.call_box(()).log();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> HResult<()> {
|
||||
if self.started {
|
||||
HError::async_started()?
|
||||
|
@ -131,45 +146,11 @@ impl<T: Send + 'static> Async<T> {
|
|||
let on_ready_fn = self.on_ready.clone();
|
||||
self.started = true;
|
||||
|
||||
std::thread::spawn(move|| -> HResult<()> {
|
||||
let value = closure.lock().map(|closure|
|
||||
closure.as_ref().map(|closure|
|
||||
closure(stale)));
|
||||
|
||||
if let Ok(value) = value {
|
||||
if let Some(value) = value {
|
||||
match value {
|
||||
Ok(mut value) => {
|
||||
async_value
|
||||
.lock()
|
||||
.map(|mut async_value|
|
||||
async_value.replace(Ok(value))).ok();
|
||||
|
||||
if let Ok(mut on_ready_fn) = on_ready_fn.lock() {
|
||||
if let Some(on_ready_fn) = on_ready_fn.as_ref() {
|
||||
on_ready_fn().log();
|
||||
}
|
||||
on_ready_fn.take();
|
||||
}
|
||||
|
||||
closure.lock().map(|mut closure|
|
||||
closure.take()).ok();
|
||||
},
|
||||
Err(err) => {
|
||||
async_value
|
||||
.lock()
|
||||
.map(|mut async_value|
|
||||
async_value.replace(Err(err))).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
async_value
|
||||
.lock()
|
||||
.map(|mut async_value|
|
||||
async_value.replace(Err(HError::MutexError))).ok();
|
||||
}
|
||||
Ok(())
|
||||
std::thread::spawn(move || {
|
||||
Async::run_async(closure,
|
||||
async_value,
|
||||
on_ready_fn,
|
||||
stale).log();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
@ -186,58 +167,23 @@ impl<T: Send + 'static> Async<T> {
|
|||
self.started = true;
|
||||
|
||||
pool.spawn(move || {
|
||||
let value = closure.lock().map(|closure|
|
||||
closure.as_ref().map(|closure|
|
||||
closure(stale)));
|
||||
|
||||
if let Ok(value) = value {
|
||||
if let Some(value) = value {
|
||||
match value {
|
||||
Ok(mut value) => {
|
||||
async_value
|
||||
.lock()
|
||||
.map(|mut async_value|
|
||||
async_value.replace(Ok(value))).ok();
|
||||
if let Ok(mut on_ready_fn) = on_ready_fn.lock() {
|
||||
if let Some(on_ready_fn) = on_ready_fn.as_ref() {
|
||||
on_ready_fn().log();
|
||||
}
|
||||
on_ready_fn.take();
|
||||
}
|
||||
closure.lock().map(|mut closure|
|
||||
closure.take()).ok();
|
||||
},
|
||||
Err(err) => {
|
||||
async_value
|
||||
.lock()
|
||||
.map(|mut async_value|
|
||||
async_value.replace(Err(err))).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
async_value
|
||||
.lock()
|
||||
.map(|mut async_value|
|
||||
async_value.replace(Err(HError::MutexError))).ok();
|
||||
}
|
||||
Async::run_async(closure,
|
||||
async_value,
|
||||
on_ready_fn,
|
||||
stale).log();
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn wait(&mut self) -> HResult<()> {
|
||||
let closure = self.async_closure.lock()?.take()?;
|
||||
let mut value = closure(self.stale.clone());
|
||||
let on_ready_fn = self.on_ready.lock()?.take();
|
||||
|
||||
|
||||
if let Ok(ref mut value) = value {
|
||||
on_ready_fn.map(|on_ready_fn| on_ready_fn());
|
||||
}
|
||||
|
||||
self.value = value;
|
||||
Ok(())
|
||||
pub fn wait(self) -> HResult<T> {
|
||||
Async::run_async(self.async_closure,
|
||||
self.async_value.clone(),
|
||||
self.on_ready,
|
||||
self.stale).log();
|
||||
let value = self.async_value.lock()?.take()?;
|
||||
value
|
||||
}
|
||||
|
||||
pub fn set_stale(&mut self) -> HResult<()> {
|
||||
|
@ -258,6 +204,10 @@ impl<T: Send + 'static> Async<T> {
|
|||
self.stale.clone()
|
||||
}
|
||||
|
||||
pub fn put_stale(&mut self, stale: Stale) {
|
||||
self.stale = stale;
|
||||
}
|
||||
|
||||
pub fn is_started(&self) -> bool {
|
||||
self.started
|
||||
}
|
||||
|
@ -326,10 +276,11 @@ pub struct AsyncWidget<W: Widget + Send + 'static> {
|
|||
|
||||
impl<W: Widget + Send + 'static> AsyncWidget<W> {
|
||||
pub fn new(core: &WidgetCore, closure: AsyncValueFn<W>) -> AsyncWidget<W> {
|
||||
let sender = core.get_sender();
|
||||
let mut widget = Async::new(Box::new(move |stale| closure(stale)));
|
||||
let sender = Mutex::new(core.get_sender());
|
||||
let mut widget = Async::new(Box::new(move |stale|
|
||||
closure.call_box((stale,))));
|
||||
widget.on_ready(Box::new(move || {
|
||||
sender.send(crate::widget::Events::WidgetReady)?;
|
||||
sender.lock()?.send(crate::widget::Events::WidgetReady)?;
|
||||
Ok(())
|
||||
}));
|
||||
widget.run().log();
|
||||
|
@ -342,15 +293,15 @@ impl<W: Widget + Send + 'static> AsyncWidget<W> {
|
|||
pub fn change_to(&mut self, closure: AsyncWidgetFn<W>) -> HResult<()> {
|
||||
self.set_stale().log();
|
||||
|
||||
let sender = self.get_core()?.get_sender();
|
||||
let sender = Mutex::new(self.get_core()?.get_sender());
|
||||
let core = self.get_core()?.clone();
|
||||
|
||||
let mut widget = Async::new(Box::new(move |stale| {
|
||||
closure(stale, core.clone())
|
||||
closure.call_box((stale, core.clone(),))
|
||||
}));
|
||||
|
||||
widget.on_ready(Box::new(move || {
|
||||
sender.send(crate::widget::Events::WidgetReady)?;
|
||||
sender.lock()?.send(crate::widget::Events::WidgetReady)?;
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
|
@ -385,16 +336,7 @@ impl<W: Widget + Send + 'static> AsyncWidget<W> {
|
|||
}
|
||||
}
|
||||
|
||||
// impl<T: Widget + Send> WillBeWidget<T> {
|
||||
// fn is_widget(&self) -> bool {
|
||||
// self.willbe.check().is_ok()
|
||||
// }
|
||||
// fn take_widget(self) {
|
||||
// if self.is_widget() {
|
||||
// let widget = self.willbe.take();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
impl<T: Widget + Send + 'static> Widget for AsyncWidget<T> {
|
||||
fn get_core(&self) -> HResult<&WidgetCore> {
|
||||
|
@ -458,17 +400,18 @@ impl PartialEq for Previewer {
|
|||
}
|
||||
}
|
||||
|
||||
use crate::fscache::FsCache;
|
||||
|
||||
pub struct Previewer {
|
||||
widget: AsyncWidget<Box<dyn Widget + Send>>,
|
||||
core: WidgetCore,
|
||||
file: Option<File>,
|
||||
selection: Option<File>,
|
||||
cached_files: Option<Files>,
|
||||
pub cache: FsCache,
|
||||
}
|
||||
|
||||
|
||||
impl Previewer {
|
||||
pub fn new(core: &WidgetCore) -> Previewer {
|
||||
pub fn new(core: &WidgetCore, cache: FsCache) -> Previewer {
|
||||
let core_ = core.clone();
|
||||
let widget = AsyncWidget::new(&core, Box::new(move |_| {
|
||||
Ok(Box::new(TextView::new_blank(&core_)) as Box<dyn Widget + Send>)
|
||||
|
@ -476,8 +419,7 @@ impl Previewer {
|
|||
Previewer { widget: widget,
|
||||
core: core.clone(),
|
||||
file: None,
|
||||
selection: None,
|
||||
cached_files: None }
|
||||
cache: cache }
|
||||
}
|
||||
|
||||
fn become_preview(&mut self,
|
||||
|
@ -493,42 +435,34 @@ impl Previewer {
|
|||
}
|
||||
|
||||
pub fn set_file(&mut self,
|
||||
file: &File,
|
||||
selection: Option<File>,
|
||||
cached_files: Option<Files>) -> HResult<()> {
|
||||
file: &File) -> HResult<()> {
|
||||
if Some(file) == self.file.as_ref() && !self.widget.is_stale()? { return Ok(()) }
|
||||
self.file = Some(file.clone());
|
||||
self.selection = selection.clone();
|
||||
self.cached_files = cached_files.clone();
|
||||
|
||||
let coordinates = self.get_coordinates().unwrap().clone();
|
||||
let file = file.clone();
|
||||
let core = self.core.clone();
|
||||
let cache = self.cache.clone();
|
||||
|
||||
self.widget.set_stale().ok();
|
||||
|
||||
self.become_preview(Ok(AsyncWidget::new(&self.core,
|
||||
Box::new(move |stale| {
|
||||
Box::new(move |stale: Stale| {
|
||||
kill_proc().unwrap();
|
||||
|
||||
let file = file.clone();
|
||||
let selection = selection.clone();
|
||||
let cached_files = cached_files.clone();
|
||||
|
||||
if file.kind == Kind::Directory {
|
||||
let preview = Previewer::preview_dir(&file,
|
||||
selection,
|
||||
cached_files,
|
||||
cache,
|
||||
&core,
|
||||
stale.clone());
|
||||
stale);
|
||||
return preview;
|
||||
}
|
||||
|
||||
if file.get_mime() == Some("text".to_string()) {
|
||||
return Previewer::preview_text(&file, &core, stale.clone())
|
||||
return Previewer::preview_text(&file, &core, stale)
|
||||
}
|
||||
|
||||
let preview = Previewer::preview_external(&file, &core, stale.clone());
|
||||
let preview = Previewer::preview_external(&file, &core, stale);
|
||||
if preview.is_ok() { return preview; }
|
||||
else {
|
||||
let mut blank = Box::new(TextView::new_blank(&core));
|
||||
|
@ -543,8 +477,7 @@ impl Previewer {
|
|||
pub fn reload(&mut self) {
|
||||
if let Some(file) = self.file.clone() {
|
||||
self.file = None;
|
||||
let cache = self.cached_files.take();
|
||||
self.set_file(&file, self.selection.clone(), cache).log();
|
||||
self.set_file(&file).log();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -553,15 +486,14 @@ impl Previewer {
|
|||
}
|
||||
|
||||
fn preview_dir(file: &File,
|
||||
selection: Option<File>,
|
||||
cached_files: Option<Files>,
|
||||
cache: FsCache,
|
||||
core: &WidgetCore,
|
||||
stale: Stale)
|
||||
-> Result<WidgetO, HError> {
|
||||
let files = cached_files.or_else(|| {
|
||||
Files::new_from_path_cancellable(&file.path,
|
||||
stale.clone()).ok()
|
||||
})?;
|
||||
let (selection, cached_files) = cache.get_files(&file, stale.clone())?;
|
||||
|
||||
let files = cached_files.wait()?;
|
||||
|
||||
let len = files.len();
|
||||
|
||||
if len == 0 || is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||
|
@ -675,157 +607,6 @@ impl Widget for Previewer {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// #[derive(PartialEq)]
|
||||
// pub struct AsyncPreviewer {
|
||||
// pub file: Option<File>,
|
||||
// pub buffer: String,
|
||||
// pub coordinates: Coordinates,
|
||||
// pub async_plug: AsyncPlug2<Box<dyn Widget + Send + 'static>>
|
||||
// }
|
||||
|
||||
// impl AsyncPreviewer {
|
||||
// pub fn new() -> AsyncPreviewer {
|
||||
// let closure = Box::new(|| {
|
||||
// Box::new(crate::textview::TextView {
|
||||
// lines: vec![],
|
||||
// buffer: "".to_string(),
|
||||
// coordinates: Coordinates::new()
|
||||
// }) as Box<dyn Widget + Send + 'static>
|
||||
// });
|
||||
|
||||
// AsyncPreviewer {
|
||||
// file: None,
|
||||
// buffer: String::new(),
|
||||
// coordinates: Coordinates::new(),
|
||||
// async_plug: AsyncPlug2::new_from_closure(closure),
|
||||
// }
|
||||
// }
|
||||
// pub fn set_file(&mut self, file: &File) {
|
||||
// let coordinates = self.coordinates.clone();
|
||||
// let file = file.clone();
|
||||
// let redraw = crate::term::reset() + &self.get_redraw_empty_list(0);
|
||||
// //let pids = PIDS.clone();
|
||||
// //kill_procs();
|
||||
|
||||
// self.async_plug.replace_widget(Box::new(move || {
|
||||
// kill_procs();
|
||||
// let mut bufout = std::io::BufWriter::new(std::io::stdout());
|
||||
// match &file.kind {
|
||||
// Kind::Directory => match Files::new_from_path(&file.path) {
|
||||
// Ok(files) => {
|
||||
// //if !is_current(&file) { return }
|
||||
// let len = files.len();
|
||||
// //if len == 0 { return };
|
||||
// let mut file_list = ListView::new(files);
|
||||
// file_list.set_coordinates(&coordinates);
|
||||
// file_list.refresh();
|
||||
// //if !is_current(&file) { return }
|
||||
// file_list.animate_slide_up();
|
||||
// return Box::new(file_list)
|
||||
|
||||
// }
|
||||
// Err(err) => {
|
||||
// write!(bufout, "{}", redraw).unwrap();
|
||||
// let textview = crate::textview::TextView {
|
||||
// lines: vec![],
|
||||
// buffer: "".to_string(),
|
||||
// coordinates: Coordinates::new(),
|
||||
// };
|
||||
// return Box::new(textview)
|
||||
// },
|
||||
// }
|
||||
// _ => {
|
||||
// if file.get_mime() == Some("text".to_string()) {
|
||||
// let lines = coordinates.ysize() as usize;
|
||||
// let mut textview
|
||||
// = TextView::new_from_file_limit_lines(&file,
|
||||
// lines);
|
||||
// //if !is_current(&file) { return }
|
||||
// textview.set_coordinates(&coordinates);
|
||||
// textview.refresh();
|
||||
// //if !is_current(&file) { return }
|
||||
// textview.animate_slide_up();
|
||||
// return Box::new(textview)
|
||||
// } else {
|
||||
// let process =
|
||||
// std::process::Command::new("scope.sh")
|
||||
// .arg(&file.name)
|
||||
// .arg("10".to_string())
|
||||
// .arg("10".to_string())
|
||||
// .arg("".to_string())
|
||||
// .arg("false".to_string())
|
||||
// .stdin(std::process::Stdio::null())
|
||||
// .stdout(std::process::Stdio::piped())
|
||||
// .stderr(std::process::Stdio::null())
|
||||
// .spawn().unwrap();
|
||||
|
||||
// let pid = process.id();
|
||||
// PIDS.lock().unwrap().push(pid);
|
||||
|
||||
// //if !is_current(&file) { return }
|
||||
|
||||
// let output = process.wait_with_output();
|
||||
// match output {
|
||||
// Ok(output) => {
|
||||
// let status = output.status.code();
|
||||
// match status {
|
||||
// Some(status) => {
|
||||
// if status == 0 || status == 5 && is_current(&file) {
|
||||
// let output = std::str::from_utf8(&output.stdout)
|
||||
// .unwrap()
|
||||
// .to_string();
|
||||
// let mut textview = TextView {
|
||||
// lines: output.lines().map(|s| s.to_string()).collect(),
|
||||
// buffer: String::new(),
|
||||
// coordinates: Coordinates::new() };
|
||||
// textview.set_coordinates(&coordinates);
|
||||
// textview.refresh();
|
||||
// textview.animate_slide_up();
|
||||
// return Box::new(textview)
|
||||
// }
|
||||
// }, None => {}
|
||||
// }
|
||||
// }, Err(_) => {}
|
||||
// }
|
||||
|
||||
// write!(bufout, "{}", redraw).unwrap();
|
||||
// //std::io::stdout().flush().unwrap();
|
||||
// let textview = crate::textview::TextView {
|
||||
// lines: vec![],
|
||||
// buffer: "".to_string(),
|
||||
// coordinates: Coordinates::new(),
|
||||
// };
|
||||
// return Box::new(textview)
|
||||
// }
|
||||
// }
|
||||
// }}))
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
impl<T> Widget for Box<T> where T: Widget + ?Sized {
|
||||
fn get_core(&self) -> HResult<&WidgetCore> {
|
||||
Ok((**self).get_core()?)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use termion::event::Key;
|
||||
|
||||
use crate::widget::{Widget, WidgetCore};
|
||||
use crate::widget::{Widget, WidgetCore, Events};
|
||||
use crate::fail::{HResult, ErrorLog};
|
||||
use crate::coordinates::Coordinates;
|
||||
use crate::dirty::Dirtyable;
|
||||
|
||||
pub trait Tabbable {
|
||||
fn new_tab(&mut self) -> HResult<()>;
|
||||
|
@ -26,6 +25,7 @@ pub trait Tabbable {
|
|||
_ => self.on_key_sub(key)
|
||||
}
|
||||
}
|
||||
fn on_refresh(&mut self) -> HResult<()> { Ok(()) }
|
||||
}
|
||||
|
||||
|
||||
|
@ -137,7 +137,8 @@ impl<T> Widget for TabView<T> where T: Widget, TabView<T>: Tabbable {
|
|||
}
|
||||
}).collect::<String>();
|
||||
|
||||
let nums_pos = xsize - nums_length as u16;
|
||||
|
||||
let nums_pos = xsize.saturating_sub(nums_length as u16);
|
||||
|
||||
Ok(format!("{}{}{}{}",
|
||||
header,
|
||||
|
@ -152,6 +153,7 @@ impl<T> Widget for TabView<T> where T: Widget, TabView<T>: Tabbable {
|
|||
}
|
||||
|
||||
fn refresh(&mut self) -> HResult<()> {
|
||||
Tabbable::on_refresh(self).log();
|
||||
self.active_tab_mut().refresh()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::{Sender, Receiver, channel};
|
||||
use std::io::Write;
|
||||
use std::io::{Write, stdin};
|
||||
|
||||
use termion::event::{Event, Key, MouseEvent};
|
||||
use termion::input::TermRead;
|
||||
|
@ -11,17 +11,16 @@ use crate::minibuffer::MiniBuffer;
|
|||
use crate::term;
|
||||
use crate::term::{Screen, ScreenExt};
|
||||
use crate::dirty::{Dirtyable, DirtyBit};
|
||||
use crate::preview::Stale;
|
||||
use crate::signal_notify::{notify, Signal};
|
||||
|
||||
use std::io::stdin;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Events {
|
||||
InputEvent(Event),
|
||||
WidgetReady,
|
||||
TerminalResized,
|
||||
ExclusiveEvent(Option<Sender<Events>>),
|
||||
ExclusiveEvent(Option<Mutex<Option<Sender<Events>>>>),
|
||||
InputEnabled(bool),
|
||||
RequestInput,
|
||||
Status(String)
|
||||
|
@ -52,7 +51,7 @@ pub struct WidgetCore {
|
|||
pub screen: Screen,
|
||||
pub coordinates: Coordinates,
|
||||
pub minibuffer: Arc<Mutex<Option<MiniBuffer>>>,
|
||||
pub event_sender: Sender<Events>,
|
||||
pub event_sender: Arc<Mutex<Sender<Events>>>,
|
||||
event_receiver: Arc<Mutex<Option<Receiver<Events>>>>,
|
||||
pub status_bar_content: Arc<Mutex<Option<String>>>,
|
||||
term_size: (usize, usize),
|
||||
|
@ -74,7 +73,7 @@ impl WidgetCore {
|
|||
screen: screen,
|
||||
coordinates: coords,
|
||||
minibuffer: Arc::new(Mutex::new(None)),
|
||||
event_sender: sender,
|
||||
event_sender: Arc::new(Mutex::new(sender)),
|
||||
event_receiver: Arc::new(Mutex::new(Some(receiver))),
|
||||
status_bar_content: status_bar_content,
|
||||
term_size: (xsize, ysize),
|
||||
|
@ -86,7 +85,7 @@ impl WidgetCore {
|
|||
}
|
||||
|
||||
pub fn get_sender(&self) -> Sender<Events> {
|
||||
self.event_sender.clone()
|
||||
self.event_sender.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,7 +240,9 @@ pub trait Widget {
|
|||
|
||||
fn run_widget(&mut self) -> HResult<()> {
|
||||
let (tx_event, rx_event) = channel();
|
||||
self.get_core()?.get_sender().send(Events::ExclusiveEvent(Some(tx_event)))?;
|
||||
self.get_core()?
|
||||
.get_sender()
|
||||
.send(Events::ExclusiveEvent(Some(Mutex::new(Some(tx_event)))))?;
|
||||
self.get_core()?.get_sender().send(Events::RequestInput)?;
|
||||
|
||||
self.clear()?;
|
||||
|
@ -378,7 +379,7 @@ pub trait Widget {
|
|||
fn show_status(&self, status: &str) -> HResult<()> {
|
||||
let xsize = self.get_core()?.coordinates.xsize_u();
|
||||
let sized_status = term::sized_string_u(status, xsize);
|
||||
HError::log(status.to_string()).log();
|
||||
HError::log::<()>(status.to_string()).log();
|
||||
{
|
||||
let mut status_content = self.get_core()?.status_bar_content.lock()?;
|
||||
*status_content = Some(sized_status);
|
||||
|
@ -437,7 +438,10 @@ fn dispatch_events(tx_internal: Sender<Events>,
|
|||
for event in rx_event.iter() {
|
||||
match &event {
|
||||
Events::ExclusiveEvent(tx_event) => {
|
||||
tx_exclusive_event = tx_event.clone();
|
||||
tx_exclusive_event = match tx_event {
|
||||
Some(locked_sender) => locked_sender.lock().unwrap().take(),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
Events::InputEnabled(state) => {
|
||||
input_enabled = *state;
|
||||
|
|
Loading…
Reference in New Issue