diff options
author | rabite <rabite@posteo.de> | 2019-03-29 02:53:21 +0100 |
---|---|---|
committer | rabite <rabite@posteo.de> | 2019-03-29 02:53:21 +0100 |
commit | b52e63fd8d74ed7587db6d37aca585f6185c77a3 (patch) | |
tree | 2e4a24610b1ab090dcb133ca542f39595e831436 | |
parent | 8d8d9631b5fce48e2e65410aa4d0df64948f5ad8 (diff) |
global file cache/event dispatch
-rw-r--r-- | src/fail.rs | 2 | ||||
-rw-r--r-- | src/file_browser.rs | 277 | ||||
-rw-r--r-- | src/files.rs | 56 | ||||
-rw-r--r-- | src/fscache.rs | 294 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/preview.rs | 351 | ||||
-rw-r--r-- | src/tabview.rs | 8 | ||||
-rw-r--r-- | src/widget.rs | 24 |
8 files changed, 529 insertions, 489 deletions
diff --git a/src/fail.rs b/src/fail.rs index c85028c..ea5a4a7 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -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<()> { diff --git a/src/file_browser.rs b/src/file_browser.rs index 3cf05e7..469b2d8 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -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,77 +497,25 @@ 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(()) - } - 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 !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); + 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(); } - 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? - } - } + if &self.left_dir()? == &dir { + self.left_widget_mut()?.content.replace_file(old, new.cloned()).log(); } 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(()) - } - pub fn selected_file(&self) -> HResult<File> { let widget = self.main_widget()?; let file = widget.selected_file().clone(); @@ -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(()) } diff --git a/src/files.rs b/src/files.rs index 8b3265c..7af7551 100644 --- a/src/files.rs +++ b/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("")) diff --git a/src/fscache.rs b/src/fscache.rs new file mode 100644 index 0000000..c650dff --- /dev/null +++ b/src/fscache.rs @@ -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<& |