summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/file_browser.rs52
-rw-r--r--src/files.rs297
-rw-r--r--src/fscache.rs268
-rw-r--r--src/listview.rs169
-rw-r--r--src/main.rs2
-rw-r--r--src/preview.rs17
6 files changed, 513 insertions, 292 deletions
diff --git a/src/file_browser.rs b/src/file_browser.rs
index 257ca32..43394ac 100644
--- a/src/file_browser.rs
+++ b/src/file_browser.rs
@@ -168,21 +168,6 @@ impl Tabbable for TabView<FileBrowser> {
}
fn on_refresh(&mut self) -> HResult<()> {
- let fs_changes = self.active_tab_()
- .fs_cache
- .fs_changes
- .write()?
- .drain(..)
- .collect::<Vec<_>>();
-
- 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()
- }
- }
-
let open_dirs = self.widgets
.iter()
.fold(HashSet::new(), |mut dirs, tab| {
@@ -503,7 +488,7 @@ impl FileBrowser {
}
pub fn main_widget_goto(&mut self, dir: &File) -> HResult<()> {
- self.cache_files().log();
+ self.save_tab_settings().log();
let dir = dir.clone();
let cache = self.fs_cache.clone();
@@ -733,22 +718,15 @@ impl FileBrowser {
Ok(&self.left_widget()?.content)
}
- pub fn cache_files(&mut self) -> HResult<()> {
- if self.main_widget().is_ok() {
+ pub fn save_tab_settings(&mut self) -> HResult<()> {
+ if !self.main_async_widget_mut()?.ready() { return Ok(()) }
+
+ if self.main_widget()?.content.len() > 0 {
let files = self.get_files()?;
let selected_file = self.selected_file().ok();
- self.fs_cache.put_files(files, selected_file).log();
- self.main_widget_mut()?.content.meta_updated = false;
+ self.fs_cache.save_settings(files, selected_file).log();
}
-
- // if self.cwd.parent().is_some() {
- // 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();
- // self.left_widget_mut()?.content.meta_updated = false;
- // }
-
Ok(())
}
@@ -769,22 +747,6 @@ impl FileBrowser {
Ok(dir)
}
- 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();
- }
-
- self.preview_widget_mut()?.replace_file(dir, old, new).ok();
-
- if &self.left_dir()? == &dir {
- self.left_widget_mut()?.content.replace_file(old, new.cloned()).log();
- }
- Ok(())
- }
-
pub fn selected_file(&self) -> HResult<File> {
let widget = self.main_widget()?;
let file = widget.selected_file().clone();
@@ -1288,7 +1250,7 @@ impl Widget for FileBrowser {
self.set_cwd().log();
if !self.columns.zoom_active { self.update_preview().log(); }
self.columns.refresh().log();
- self.cache_files().log();
+ self.save_tab_settings().log();
Ok(())
}
diff --git a/src/files.rs b/src/files.rs
index d8558af..bafffc9 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -1,4 +1,5 @@
use std::cmp::{Ord, Ordering};
+use std::collections::{HashMap, HashSet};
use std::ops::Index;
use std::fs::Metadata;
use std::os::unix::fs::MetadataExt;
@@ -16,7 +17,6 @@ use users::{get_current_username,
get_group_by_gid};
use chrono::TimeZone;
use failure::Error;
-use notify::DebouncedEvent;
use rayon::{ThreadPool, ThreadPoolBuilder};
use alphanumeric_sort::compare_str;
use mime_guess;
@@ -28,6 +28,7 @@ use crate::fail::{HResult, HError, ErrorLog};
use crate::dirty::{AsyncDirtyBit, DirtyBit, Dirtyable};
use crate::widget::Events;
use crate::icon::Icons;
+use crate::fscache::FsEvent;
lazy_static! {
@@ -99,11 +100,147 @@ pub fn tags_loaded() -> HResult<()> {
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
+pub struct RefreshPackage {
+ pub new_files: Option<Vec<File>>,
+ pub new_buffer: Option<Vec<String>>,
+}
+
+
+
+
+impl RefreshPackage {
+ fn new(mut files: Files,
+ old_buffer: Vec<String>,
+ events: Vec<FsEvent>,
+ render_fn: impl Fn(&File) -> String) -> RefreshPackage {
+ use FsEvent::*;
+
+ // If there is only a placeholder at this point, remove it now
+ if files.len() == 1 {
+ files.remove_placeholder();
+ }
+
+ //To preallocate collections
+ let event_count = events.len();
+
+ // Need at least one copy for the hashmaps
+ let static_files = files.clone();
+
+ // Optimization to speed up access to array
+ let file_pos_map: HashMap<&File, usize> = static_files
+ .files
+ .iter()
+ .enumerate()
+ .map(|(i, file)| (file, i))
+ .collect();
+
+
+ // Need to know which line of the ListView buffer belongs to which file
+ let list_pos_map: HashMap<&File, usize> = static_files
+ .iter_files()
+ .enumerate()
+ .take_while(|&(i, _)| i < old_buffer.len())
+ .map(|(i, file)| (file, i))
+ .collect();
+
+ // Save new files to add them all at once later
+ let mut new_files = Vec::with_capacity(event_count);
+
+ // Files that need rerendering to make all changes visible (size, etc.)
+ let mut changed_files = HashSet::with_capacity(event_count);
+
+ // Save deletions to delete them efficiently later
+ let mut deleted_files = HashSet::with_capacity(event_count);
+
+ for event in events.into_iter() {
+ match event {
+ Create(mut file) => {
+ let dirty_meta = files.dirty_meta.clone();
+ file.dirty_meta = Some(dirty_meta);
+ file.meta_sync().log();
+ new_files.push(file);
+ }
+ Change(file) => {
+ if let Some(&fpos) = file_pos_map.get(&file) {
+ files.files[fpos].meta_sync().log();
+ changed_files.insert(file);
+ }
+ }
+ Rename(old, new) => {
+ if let Some(&fpos) = file_pos_map.get(&old) {
+ files.files[fpos].rename(&new.path).log();
+ files.files[fpos].meta_sync().log();
+ }
+ }
+ Remove(file) => {
+ if let Some(_) = file_pos_map.get(&file) {
+ deleted_files.insert(file);
+ }
+ }
+ }
+ }
+
+ if deleted_files.len() > 0 {
+ files.files.retain(|file| !deleted_files.contains(file));
+ }
+
+ // Finally add all new files
+ files.files.extend(new_files);
+
+ // Need to unpack this to prevent issue with recursive Files type
+ // Also, if no files remain add placeholder
+ let files = if files.len() > 0 {
+ // Sort again to make sure new/changed files are in correct order
+ files.sort();
+ files.files
+ } else {
+ let placeholder = File::new_placeholder(&files.directory.path).unwrap();
+ files.files.push(placeholder);
+
+ // Need to sort because of possible hidden files
+ files.sort();
+ files.files
+ };
+
+ let mut old_buffer = old_buffer;
+
+ // Prerender new buffer in current thread
+ let new_buffer = files.iter()
+ .map(|file| {
+ match list_pos_map.get(&file) {
+ Some(&old_pos) =>
+ match changed_files.contains(&file) {
+ true => render_fn(&file),
+ false => std::mem::take(&mut old_buffer[old_pos])
+ }
+ None => render_fn(&file)
+ }
+ }).collect();
+
+
+
+ RefreshPackage {
+ new_files: Some(files),
+ new_buffer: Some(new_buffer),
+ }
+ }
+}
+
+
+#[derive(Derivative)]
+#[derivative(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Files {
pub directory: File,
pub files: Vec<File>,
+ #[derivative(Debug="ignore")]
+ #[derivative(PartialEq="ignore")]
+ #[derivative(Hash="ignore")]
+ pub pending_events: Arc<RwLock<Vec<FsEvent>>>,
+ #[derivative(Debug="ignore")]
+ #[derivative(PartialEq="ignore")]
+ #[derivative(Hash="ignore")]
+ pub refresh: Option<Async<RefreshPackage>>,
pub meta_upto: Option<usize>,
- pub meta_updated: bool,
pub sort: SortBy,
pub dirs_first: bool,
pub reverse: bool,
@@ -158,8 +295,9 @@ impl Files {
let mut files = Files {
directory: File::new_from_path(&path, None)?,
files: files,
+ pending_events: Arc::new(RwLock::new(vec![])),
+ refresh: None,
meta_upto: None,
- meta_updated: false,
sort: SortBy::Name,
dirs_first: true,
reverse: false,
@@ -216,8 +354,9 @@ impl Files {
let mut files = Files {
directory: File::new_from_path(&path, None)?,
files: files,
+ pending_events: Arc::new(RwLock::new(vec![])),
+ refresh: None,
meta_upto: None,
- meta_updated: false,
sort: SortBy::Name,
dirs_first: true,
reverse: false,
@@ -375,77 +514,69 @@ impl Files {
}
}
- pub fn replace_file(&mut self,
- old: Option<&File>,
- new: Option<File>) -> HResult<()> {
- let (tag, selected) = if let Some(old) = old {
- if let Some(old) = self.find_file_with_path(&old.path) {
- (old.tag, old.selected)
- } else {
- (None, false)
- }
- } else {
- (None, false)
- };
- old.map(|old| self.files.remove_item(old));
- new.map(|mut new| {
- new.tag = tag;
- new.selected = selected;
- self.files.push(new);
- });
+ fn remove_placeholder(&mut self) {
+ let dirpath = self.directory.path.clone();
+ self.find_file_with_path(&dirpath).cloned()
+ .map(|placeholder| self.files.remove_item(&placeholder));
+ }
- self.sort();
+ pub fn ready_to_refresh(&self) -> HResult<bool> {
+ let pending = self.pending_events.read()?.len();
+ let running = self.refresh.is_some();
+ Ok(pending > 0 && !running)
+ }
- if self.len() == 0 {
- let placeholder = File::new_placeholder(&self.directory.path)?;
- self.files.push(placeholder);
- } else {
- self.remove_placeholder();
+ pub fn get_refresh(&mut self) -> HResult<Option<RefreshPackage>> {
+ if let Some(mut refresh) = self.refresh.take() {
+ if refresh.is_ready() {
+ refresh.pull_async()?;
+ let mut refresh = refresh.value?;
+ self.files = refresh.new_files.take()?;
+ return Ok(Some(refresh));
+ } else {
+ self.refresh.replace(refresh);
+ }
}
- Ok(())
+ return Ok(None)
}
- fn remove_placeholder(&mut self) {
- let dirpath = self.directory.path.clone();
- self.find_file_with_path(&dirpath).cloned()
- .map(|placeholder| self.files.remove_item(&placeholder));
- }
+ pub fn process_fs_events(&mut self,
+ buffer: Vec<String>,
+ sender: Sender<Events>,
+ render_fn: impl Fn(&File) -> String + Send + 'static)
+ -> HResult<()> {
+ let pending = self.pending_events.read()?.len();
+ if pending > 0 {
+ let pending = if pending >= 1000 {
+ 1000
+ } else {
+ pending
+ };
+
+ let events = self.pending_events
+ .write()?
+ .drain(0..pending)
+ .collect::<Vec<_>>();
+ let files = self.clone();
+
+ let mut refresh = Async::new(move |_| {
+ let refresh = RefreshPackage::new(files,
+ buffer,
+ events,
+ render_fn);
+ Ok(refresh)
+ });
+
+ refresh.on_ready(move |_,_| {
+ Ok(sender.send(Events::WidgetReady)?)
+ })?;
+
+ refresh.run()?;
- 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,
- Some(self.dirty_meta.clone()))?;
- self.files.push(file);
- self.sort();
- },
- 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();
- file.reload_meta()?;
- },
- DebouncedEvent::Error(err, _path) => {
- // Never seen this happen. Should reload affected dirs
- HError::log::<()>(&format!("{}", err))?;
- },
- _ => {},
+ self.refresh = Some(refresh);
}
- self.set_dirty();
+
Ok(())
}
@@ -465,17 +596,16 @@ impl Files {
}
pub fn find_file_with_path(&mut self, path: &Path) -> Option<&mut File> {
- self.files.iter_mut().find(|file| file.path == path)
+ self.iter_files_mut().find(|file| file.path == path)
}
pub fn meta_all_sync(&mut self) -> HResult<()> {
- for file in self.files.iter_mut() {
+ for file in self.iter_files_mut() {
if !file.meta_processed {
file.meta_sync().log();
}
}
self.set_dirty();
- self.meta_updated = true;
Ok(())
}
@@ -508,10 +638,10 @@ impl Files {
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
.take(meta_files) {
if !file.meta_processed {
- file.take_meta(&meta_pool, &mut self.meta_updated).ok();
+ file.take_meta(&meta_pool).ok();
}
if file.is_dir() {
- file.take_dirsize(&meta_pool, &mut self.meta_updated).ok();
+ file.take_dirsize(&meta_pool).ok();
}
}
@@ -519,7 +649,7 @@ impl Files {
}
pub fn meta_set_fresh(&self) -> HResult<()> {
- self.files.get(0)?.meta.set_fresh()?;
+ self.iter_files().nth(0)?.meta.set_fresh()?;
Ok(())
}
@@ -697,6 +827,12 @@ impl File {
Ok(file)
}
+ pub fn rename(&mut self, new_path: &Path) -> HResult<()> {
+ self.name = new_path.file_name()?.to_string_lossy().to_string();
+ self.path = new_path.into();
+ Ok(())
+ }
+
pub fn meta_sync(&mut self) -> HResult<()> {
let stale = self.meta.get_stale();
let meta = std::fs::metadata(&self.path)?;
@@ -754,36 +890,32 @@ impl File {
}
fn take_dirsize(&mut self,
- pool: &ThreadPool,
- meta_updated: &mut bool) -> HResult<()> {
+ pool: &ThreadPool) -> HResult<()> {
let dirsize = self.dirsize.as_mut()?;
if let Ok(_) = dirsize.value { return Ok(()) }
- if !dirsize.is_running() {
+ if !dirsize.is_running() && !dirsize.is_ready() {
dirsize.run_pooled(Some(&*pool))?;
}
if dirsize.is_ready() {
dirsize.pull_async()?;
- *meta_updated = true;
}
Ok(())
}
pub fn take_meta(&mut self,
- pool: &ThreadPool,
- meta_updated: &mut bool) -> HResult<()> {
+ pool: &ThreadPool) -> HResult<()> {
if self.meta_processed { return Ok(()) }
- if !self.meta.is_running() {
+ if !self.meta.is_running() && !self.meta.is_ready() {
self.meta.run_pooled(Some(&*pool))?;
}
if self.meta.is_ready() {
self.meta.pull_async()?;
self.process_meta()?;
- *meta_updated = true;
}
Ok(())
@@ -857,10 +989,11 @@ impl File {
mime
} else {
// Fix crash in tree_magic when called on non-regular file
+ // Also fix crash when a file doesn't exist any more
self.meta()
.ok()
.and_then(|meta| {
- if meta.is_file() {
+ if meta.is_file() && self.path.exists() {
let mime = tree_magic::from_filepath(&self.path);
mime::Mime::from_str(&mime).ok()
} else { None }
diff --git a/src/fscache.rs b/src/fscache.rs
index 8fb991e..7f4ce24 100644
--- a/src/fscache.rs
+++ b/src/fscache.rs
@@ -2,7 +2,7 @@ use notify::{RecommendedWatcher, Watcher, DebouncedEvent, RecursiveMode};
use async_value::{Async, Stale};
-use std::sync::{Arc, RwLock};
+use std::sync::{Arc, RwLock, Weak};
use std::sync::mpsc::{channel, Sender, Receiver};
use std::collections::{HashMap, HashSet};
use std::time::Duration;
@@ -12,6 +12,8 @@ use crate::files::{Files, File, SortBy};
use crate::widget::Events;
use crate::fail::{HResult, HError, ErrorLog, Backtrace, ArcBacktrace};
+pub type CachedFiles = (Option<File>, Async<Files>);
+
#[derive(Debug, Clone)]
pub struct DirSettings {
@@ -64,6 +66,63 @@ impl std::fmt::Debug for FsCache {
}
}
+#[derive(Clone)]
+struct FsEventDispatcher {
+ targets: Arc<RwLock<HashMap<File, Vec<Weak<RwLock<Vec<FsEvent>>>>>>>
+}
+
+impl FsEventDispatcher {
+ fn new() -> Self {
+ FsEventDispatcher {
+ targets: Arc::new(RwLock::new(HashMap::new()))
+ }
+ }
+
+ fn add_target(&self,
+ dir: &File,
+ target: &Arc<RwLock<Vec<FsEvent>>>) -> HResult<()> {
+ let target = Arc::downgrade(target);
+
+ self.targets
+ .write()
+ .map(|mut targets| {
+ match targets.get_mut(dir) {
+ Some(targets) => targets.push(target),
+ None => { targets.insert(dir.clone(), vec![target]); }
+ }
+ })?;
+ Ok(())
+ }
+
+ fn remove_target(&self, dir: &File) -> HResult<()> {
+ self.targets
+ .write()?
+ .get_mut(dir)
+ .map(|targets| {
+ targets.retain(|t| t.upgrade().is_some());
+ });
+ Ok(())
+ }
+
+ fn dispatch(&self, events: HashMap<File, Vec<FsEvent>>) -> HResult<()> {
+ for (dir, events) in events {
+ for target_dirs in self.targets
+ .read()?
+ .get(&dir) {
+ for target in target_dirs {
+ if let Some(target) = target.upgrade() {
+ let events = events.clone();
+
+ target.write()?.extend(events)
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ // fn remove_unnecessary
+}
#[derive(Clone)]
pub struct FsCache {
@@ -71,14 +130,14 @@ pub struct FsCache {
pub tab_settings: Arc<RwLock<HashMap<File, TabSettings>>>,
watched_dirs: Arc<RwLock<HashSet<File>>>,
watcher: Arc<RwLock<RecommendedWatcher>>,
- pub fs_changes: Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
+ fs_event_dispatcher: FsEventDispatcher
}
impl FsCache {
pub fn new(sender: Sender<Events>) -> FsCache {
let (tx_fs_event, rx_fs_event) = channel();
let watcher = RecommendedWatcher::new(tx_fs_event,
- Duration::from_secs(2)).unwrap();
+ Duration::from_secs(2)).unwrap();
let fs_cache = FsCache {
@@ -86,12 +145,11 @@ impl FsCache {
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![])),
+ fs_event_dispatcher: FsEventDispatcher::new()
};
watch_fs(rx_fs_event,
- fs_cache.files.clone(),
- fs_cache.fs_changes.clone(),
+ fs_cache.fs_event_dispatcher.clone(),
sender);
fs_cache
@@ -104,8 +162,6 @@ impl FsCache {
}
}
-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) {
@@ -117,6 +173,8 @@ impl FsCache {
let files = Async::new(move |_| {
let mut files = Files::new_from_path_cancellable(&dir.path, stale)?;
cache.add_watch(&dir).log();
+ cache.fs_event_dispatcher.add_target(&dir,
+ &files.pending_events).log();
FsCache::apply_settingss(&cache, &mut files).ok();
Ok(files)
});
@@ -129,7 +187,6 @@ impl FsCache {
let mut files = files.run_sync()?;
FsCache::apply_settingss(&self, &mut files).ok();
let files = FsCache::ensure_not_empty(files)?;
- self.add_watch(&dir).log();
Ok(files)
}
@@ -144,28 +201,6 @@ impl FsCache {
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) {
- // if files.meta_updated {
- // let mut files = files.clone();
- // files.meta_updated = false;
- // file_cache.insert(dir, files);
- // }
- // } else {
- // file_cache.insert(dir, files.clone());
- // }
-
- Ok(())
- }
-
pub fn is_cached(&self, dir: &File) -> HResult<bool> {
Ok(self.files.read()?.contains_key(dir))
}
@@ -298,65 +333,134 @@ impl FsCache {
}
+#[derive(PartialEq, Eq, Hash, Clone, Debug)]
+pub enum FsEvent {
+ Create(File),
+ Change(File),
+ Rename(File, File),
+ Remove(File)
+}
+
+impl FsEvent {
+ pub fn file(&self) -> &File {
+ use FsEvent::*;
+ match self {
+ Create(event_file) |
+ Change(event_file) |
+ Remove(event_file) |
+ Rename(_, event_file) => &event_file
+ }
+ }
+
+ pub fn for_file(&self, file: &File) -> bool {
+ use FsEvent::*;
+ match self {
+ Create(event_file) |
+ Change(event_file) |
+ Remove(event_file) |
+ Rename(_, event_file) => event_file.path == file.path
+ }
+ }
+}
+
+use std::convert::TryFrom;
+impl TryFrom<DebouncedEvent> for FsEvent {
+ type Error = HError;
+
+ fn try_from(event: DebouncedEvent) -> HResult<Self> {
+ let event = match event {
+ DebouncedEvent::Create(path)
+ => FsEvent::Create(File::new_from_path(&path, None)?),
+
+ DebouncedEvent::Remove(path)
+ => FsEvent::Remove(File::new_from_path(&path, None)?),
+
+ DebouncedEvent::Write(path) |
+ DebouncedEvent::Chmod(path)
+ => FsEvent::Change(File::new_from_path(&path, None)?),
+
+ DebouncedEvent::Rename(old_path, new_path)
+ => FsEvent::Rename(File::new_from_path(&old_path, None)?,
+ File::new_from_path(&new_path, None)?),
+
+ DebouncedEvent::Error(err, path)
+ => Err(HError::INotifyError(format!("{}, {:?}", err, path),
+ Backtrace::new_arced()))?,
+ DebouncedEvent::Rescan
+ => Err(HError::INotifyError("Need to rescan".to_string(),
+ Backtrace::new_arced()))?,
+ // Ignore NoticeRemove/NoticeWrite
+ _ => None?,
+ };
+
+ Ok(event)
+ }
+}
+
+
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>)>>>,
+ fs_event_dispatcher: FsEventDispatcher,
sender: Sender<Events>) {
std::thread::spawn(move || -> HResult<()> {
- for event in rx_fs_events.iter() {
- apply_event(&fs_cache, &fs_changes, event).log();
+ let transform_event =
+ move |event: DebouncedEvent| -> HResult<(File, FsEvent)> {
+ let path = event.get_source_path()?;
+ let dirpath = path.parent()
+ .map(|path| path)
+ .unwrap_or(std::path::Path::new("/"));
+ let dir = File::new_from_path(&dirpath, None)?;
+ let event = FsEvent::try_from(event)?;
+ Ok((dir, event))
+ };
+
+ let collect_events =
+ move || -> HResult<HashMap<File, Vec<FsEvent>>> {
+ let event = loop {
+ use DebouncedEvent::*;
+
+ let event = rx_fs_events.recv()?;
+ match event {
+ NoticeWrite(_) => continue,
+ NoticeRemove(_) => continue,
+ _ => break std::iter::once(event)
+ }
+ };
+
+ // Wait a bit to batch up more events
+ std::thread::sleep(std::time::Duration::from_millis(100));
- sender.send(Events::WidgetReady).ok();
+ // Batch up all other remaining events received so far
+ let events = event.chain(rx_fs_events.try_iter())
+ .map(transform_event)
+ .flatten()
+ .fold(HashMap::with_capacity(1000), |mut events, (dir, event)| {
+ events.entry(dir)
+ .or_insert(vec![])
+ .push(event);
+
+ events
+ });
+
+ Ok(events)
+ };
+
+
+ let dispatch_events =
+ move |events| -> HResult<()> {
+ fs_event_dispatcher.dispatch(events)?;
+ sender.send(Events::WidgetReady)?;
+ Ok(())
+ };
+
+ loop {
+ if let Ok(events) = collect_events().log_and() {
+ dispatch_events(events).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()?;
-
- let dirpath = path.parent()
- .map(|path| path.to_path_buf())
- .unwrap_or_else(|| PathBuf::from("/"));
- let dir = File::new_from_path(&dirpath, None)?;
-
- let old_file = File::new_from_path(&path, None)?;
- let mut new_file = match event {
- DebouncedEvent::Remove(_) => None,
- _ => Some(File::new_from_path(&path, None)?)
- };
-
- new_file.as_mut().map(|file| file.meta_sync());
-
- fs_changes.write()?.push((dir,
- Some(old_file),
- new_file));
-
- // 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>;
diff --git a/src/listview.rs b/src/listview.rs
index f44a705..c744f4f 100644
--- a/src/listview.rs
+++ b/src/listview.rs
@@ -97,6 +97,8 @@ impl Listable for ListView<Files> {
self.content.meta_upto(visible_files, Some(sender.clone()));
+ self.refresh_files().log();
+
if self.content.is_dirty() {
self.content.set_clean();
self.core.set_dirty();
@@ -239,7 +241,7 @@ impl ListView<Files>
// Work around annoying restriction until polonius borrow checker becomes default
- // Since we only ever return one mutable borrow this is perfectly safe
+ // Since only ever one mutable borrow is returned this is perfectly safe
// See also: https://github.com/rust-lang/rust/issues/21906
match file {
Some(file) => unsafe { return file.as_mut().unwrap() },
@@ -581,77 +583,109 @@ impl ListView<Files>
}
fn render_line(&self, file: &File) -> String {
- let icon = if self.core.config().icons {
- file.icon()
- } else { "" };
-
- let name = String::from(icon) + &file.name;
- let (size, unit) = file.calculate_size().unwrap_or((0, "".to_string()));
-
-
-
- let tag = match file.is_tagged() {
- Ok(true) => term::color_red() + "*",
- _ => "".to_string()
- };
- let tag_len = if tag != "" { 1 } else { 0 };
-
- let selection_gap = " ".to_string();
- let (name, selection_color) = if file.is_selected() {
- (selection_gap + &name, crate::term::color_yellow())
- } else { (name.clone(), "".to_string()) };
-
- let (link_indicator, link_indicator_len) = if file.target.is_some() {
- (format!("{}{}{}",
- term::color_yellow(),
- "--> ".to_string(),
- term::highlight_color()),
- 4)
- } else { ("".to_string(), 0) };
+ let render_fn = self.render_line_fn();
+ render_fn(file)
+ }
+ #[allow(trivial_bounds)]
+ fn render_line_fn(&self) -> impl Fn(&File) -> String {
let xsize = self.get_coordinates().unwrap().xsize();
- let sized_string = term::sized_string(&name, xsize);
- let size_pos = xsize - (size.to_string().len() as u16
- + unit.to_string().len() as u16
- + link_indicator_len);
- let padding = sized_string.len() - sized_string.width_cjk();
- let padding = xsize - padding as u16;
- let padding = padding - tag_len;
-
- format!(
- "{}{}{}{}{}{}{}{}",
- termion::cursor::Save,
- match &file.color {
- Some(color) => format!("{}{}{}{:padding$}{}",
- tag,
- term::from_lscolor(color),
- selection_color,
- &sized_string,
- term::normal_color(),
- padding = padding as usize),
- _ => format!("{}{}{}{:padding$}{}",
- tag,
- term::normal_color(),
- selection_color,
- &sized_string,
- term::normal_color(),
- padding = padding as usize),
- } ,
- termion::cursor::Restore,
- termion::cursor::Right(size_pos),
- link_indicator,
- term::highlight_color(),
- size,
- unit
- )
+ let icons = self.core.config().icons;
+
+ move |file| -> String {
+ let icon = if icons {
+ file.icon()
+ } else { "" };
+
+ let name = String::from(icon) + &file.name;
+ let (size, unit) = file.calculate_size().unwrap_