From 74e43ee9659ed08891e5bd14b5be889d0bad5d7e Mon Sep 17 00:00:00 2001 From: rabite Date: Tue, 11 Feb 2020 23:37:38 +0100 Subject: the fastest file manager in the galaxy --- Cargo.lock | 23 +- Cargo.toml | 6 +- src/fail.rs | 10 +- src/file_browser.rs | 79 ++--- src/files.rs | 822 +++++++++++++++++++++++++++++++--------------------- src/fscache.rs | 81 +++++- src/listview.rs | 290 +++++++----------- src/preview.rs | 7 +- src/trait_ext.rs | 5 +- 9 files changed, 740 insertions(+), 583 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3167a0..6884e32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "argon2rs" version = "0.2.5" @@ -63,7 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "async_value" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -580,7 +588,7 @@ name = "hunter" version = "1.3.5" dependencies = [ "alphanumeric-sort 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", - "async_value 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "async_value 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -597,7 +605,7 @@ dependencies = [ "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lscolors 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -726,10 +734,10 @@ dependencies = [ [[package]] name = "lscolors" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1553,11 +1561,12 @@ dependencies = [ "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum alphanumeric-sort 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f37ce94154d73f6961f87571a3ab7814e1608f373bd55a933e3e771b6dd59fc4" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" -"checksum async_value 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "35a62a04436ffd962e78a3301658eac900480d425ea36ec42553aadc572742be" +"checksum async_value 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "82d81baa3badf6d557265cedafc6fc83b4b79567360bbadcfc5ea6eb934885ae" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" @@ -1625,7 +1634,7 @@ dependencies = [ "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9938fd8c379393454f73ec4c9c5b40f3d8332d80b25a29da05e41ee0ecbb559" +"checksum lscolors 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea3b3414b2d015c4fd689815f2551797f3c2296bb241dd709c7da233ec7cba4b" "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" "checksum make-cmd 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8ca8afbe8af1785e09636acb5a41e08a765f5f0340568716c18a8700ba3c0d3" "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" diff --git a/Cargo.toml b/Cargo.toml index f43b1d8..e18845c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ default-run = "hunter" termion = "1.5" unicode-width = "0.1.5" lazy_static = "1" -alphanumeric-sort = "1.0.6" -lscolors = { version = "0.5.0", features = [ "ansi_term" ] } +alphanumeric-sort = "1.0.11" +lscolors = { version = "0.6.0", features = [ "ansi_term" ] } tree_magic = "0.2.1" rayon = "1.3" dirs-2 = "1.1.0" @@ -32,7 +32,7 @@ parse-ansi = "0.1.6" signal-notify = "0.1.3" systemstat = "0.1.5" mime_guess = "2.0" -async_value = "0.2.6" +async_value = "0.2.7" osstrtools = "0.2" pathbuftools = "0.1" clap = "2.33" diff --git a/src/fail.rs b/src/fail.rs index 36a88d4..0f05455 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -103,7 +103,9 @@ pub enum HError { #[fail(display = "{}", _0)] KeyBind(KeyBindError), #[fail(display = "FileBrowser needs to know about all tab's files to run exec!")] - FileBrowserNeedTabFiles + FileBrowserNeedTabFiles, + #[fail(display = "{}", _0)] + FileError(crate::files::FileError) } impl HError { @@ -425,3 +427,9 @@ impl From for KeyBindError { KeyBindError::IniError(Arc::new(err)) } } + +impl From for HError { + fn from(err: crate::files::FileError) -> Self { + HError::FileError(err) + } +} diff --git a/src/file_browser.rs b/src/file_browser.rs index 8c99409..3a84ee1 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -197,7 +197,7 @@ impl Tabbable for TabView { tab.left_async_widget_mut().map(|async_w| { async_w.widget.on_ready(move |mut w, _| { w.as_mut() - .map(|mut w| { + .map(|w| { if w.content.show_hidden != show_hidden { w.content.show_hidden = show_hidden; w.content.recalculate_len(); @@ -211,7 +211,7 @@ impl Tabbable for TabView { tab.main_async_widget_mut().map(|async_w| { async_w.widget.on_ready(move |mut w, _| { w.as_mut() - .map(|mut w| { + .map(|w| { if w.content.show_hidden != show_hidden { w.content.show_hidden = show_hidden; w.content.recalculate_len(); @@ -262,11 +262,9 @@ impl FileBrowser { let cache = fs_cache.clone(); let main_widget = AsyncWidget::new(&core, move |stale| { - let dir = File::new_from_path(&main_path, None)?; + let dir = File::new_from_path(&main_path)?; let source = FileSource::Path(dir); ListView::builder(core_m, source) - .meta_all() - // .prerender() .with_cache(cache) .with_stale(stale.clone()) .build() @@ -275,11 +273,9 @@ impl FileBrowser { let cache = fs_cache.clone(); if let Some(left_path) = left_path { let left_widget = AsyncWidget::new(&core_l.clone(), move |stale| { - let dir = File::new_from_path(&left_path, None)?; + let dir = File::new_from_path(&left_path)?; let source = FileSource::Path(dir); ListView::builder(core_l, source) - // .meta_all() - // .prerender() .with_cache(cache) .with_stale(stale.clone()) .build() @@ -311,7 +307,7 @@ impl FileBrowser { columns.refresh().log(); - let cwd = File::new_from_path(&cwd, None).unwrap(); + let cwd = File::new_from_path(&cwd).unwrap(); let proc_view = ProcView::new(&core); let bookmarks = BMPopup::new(&core); @@ -363,8 +359,6 @@ impl FileBrowser { }; ListView::builder(core, source) - .meta_all() - // .prerender() .with_cache(cache) .with_stale(stale.clone()) .build() @@ -380,7 +374,6 @@ impl FileBrowser { }; ListView::builder(core, source) - .prerender() .with_cache(cache) .with_stale(stale.clone()) .build() @@ -479,6 +472,10 @@ impl FileBrowser { } pub fn main_widget_goto(&mut self, dir: &File) -> HResult<()> { + self.preview_widget_mut() + .map(|p| p.set_stale()) + .ok(); + let dir = dir.clone(); let cache = self.fs_cache.clone(); @@ -489,8 +486,6 @@ impl FileBrowser { let main_async_widget = self.main_async_widget_mut()?; main_async_widget.change_to(move |stale: &Stale, core| { let view = ListView::builder(core, file_source) - .meta_all() - // .prerender() .with_cache(cache) .with_stale(stale.clone()) .build()?; @@ -507,9 +502,7 @@ impl FileBrowser { }).log(); } - self.preview_widget_mut() - .map(|p| p.set_stale()) - .ok(); + Ok(()) } @@ -527,8 +520,6 @@ impl FileBrowser { let left_async_widget = self.left_async_widget_mut()?; left_async_widget.change_to(move |stale, core| { let view = ListView::builder(core, file_source) - // .meta_all() - // .prerender() .with_cache(cache) .with_stale(stale.clone()) .build()?; @@ -559,8 +550,6 @@ impl FileBrowser { self.main_async_widget_mut()?.change_to(move |stale, core| { ListView::builder(core, file_source) .select(main_selection) - .meta_all() - // .prerender() .with_cache(cache) .with_stale(stale.clone()) .build() @@ -571,7 +560,6 @@ impl FileBrowser { let cache = self.fs_cache.clone(); self.left_async_widget_mut()?.change_to(move |stale, core| { ListView::builder(core, file_source) - // .prerender() .with_cache(cache) .with_stale(stale.clone()) .build() @@ -612,7 +600,7 @@ impl FileBrowser { pub fn go_home(&mut self) -> HResult<()> { let home = crate::paths::home_path().unwrap_or(PathBuf::from("~/")); - let home = File::new_from_path(&home, None)?; + let home = File::new_from_path(&home)?; self.main_widget_goto(&home) } @@ -649,7 +637,7 @@ impl FileBrowser { pub fn goto_bookmark(&mut self) -> HResult<()> { let path = self.get_boomark()?; - let path = File::new_from_path(&PathBuf::from(path), None)?; + let path = File::new_from_path(&PathBuf::from(path))?; self.main_widget_goto(&path)?; Ok(()) } @@ -700,23 +688,35 @@ impl FileBrowser { let selection = self.cwd()?.clone(); - self.cwd.parent_as_file() - .map(|dir| self.fs_cache - .set_selection(dir.clone(), selection.clone())).log(); + // Saves doing iteration to find file's position + if let Some(ref current_selection) = self.left_widget()?.current_item { + if current_selection.name == selection.name { + return Ok(()); + } + } + self.left_widget_mut()?.select_file(&selection); + let selected_file = self.left_widget()?.selected_file(); + self.cwd.parent_as_file() + .map(|dir| { + self.fs_cache + .set_selection(dir.clone(), selected_file.clone()) + }).log(); + + Ok(()) } pub fn take_main_files(&mut self) -> HResult { - let mut w = self.main_widget_mut()?; + let w = self.main_widget_mut()?; let files = std::mem::take(&mut w.content); w.content.len = 0; Ok(files) } pub fn take_left_files(&mut self) -> HResult { - let mut w = self.left_widget_mut()?; + let w = self.left_widget_mut()?; let files = std::mem::take(&mut w.content); w.content.len = 0; Ok(files) @@ -879,7 +879,7 @@ impl FileBrowser { let dir = self.core.minibuffer("cd")?; let path = std::path::PathBuf::from(&dir); - let dir = File::new_from_path(&path.canonicalize()?, None)?; + let dir = File::new_from_path(&path.canonicalize()?)?; self.main_widget_goto(&dir)?; Ok(()) @@ -931,11 +931,11 @@ impl FileBrowser { let path = &paths[0]; if path.exists() { if path.is_dir() { - let dir = File::new_from_path(&path, None)?; + let dir = File::new_from_path(&path)?; self.main_widget_goto(&dir).log(); } else if path.is_file() { - let file = File::new_from_path(&path, None)?; + let file = File::new_from_path(&path)?; let dir = file.parent_as_file()?; self.main_widget_goto(&dir).log(); @@ -964,7 +964,7 @@ impl FileBrowser { let dir_path = file_path.parent()?; if self.cwd.path != dir_path { - let file_dir = File::new_from_path(&dir_path, None); + let file_dir = File::new_from_path(&dir_path); self.main_widget_goto_wait(&file_dir?).log(); } @@ -1033,7 +1033,7 @@ impl FileBrowser { if path.exists() { if path.is_dir() { - let dir = File::new_from_path(&path, None)?; + let dir = File::new_from_path(&path)?; self.main_widget_goto(&dir).log(); } else { @@ -1231,12 +1231,15 @@ impl Widget for FileBrowser { let file = self.selected_file()?; let name = &file.name; + let fcolor = file.get_color(); + let color = if file.is_dir() { crate::term::highlight_color() } - else if file.color.is_none() { - crate::term::normal_color() - } else { - crate::term::from_lscolor(file.color.as_ref().unwrap()) + else { + match fcolor { + Some(color) => color, + None => crate::term::normal_color() + } }; let path = self.cwd.short_string(); diff --git a/src/files.rs b/src/files.rs index de8dc29..43c603f 100644 --- a/src/files.rs +++ b/src/files.rs @@ -8,8 +8,10 @@ use std::sync::{Arc, Mutex, RwLock}; use std::sync::mpsc::Sender; use std::hash::{Hash, Hasher}; use std::str::FromStr; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use failure; +use failure::Fail; use lscolors::LsColors; use tree_magic; use users::{get_current_username, @@ -27,33 +29,61 @@ use pathbuftools::PathBufTools; use async_value::{Async, Stale, StopIter}; use crate::fail::{HResult, HError, ErrorLog}; -use crate::dirty::{AsyncDirtyBit, DirtyBit, Dirtyable}; +use crate::dirty::{DirtyBit, Dirtyable}; use crate::widget::Events; use crate::icon::Icons; -use crate::fscache::FsEvent; - +use crate::fscache::{FsCache, FsEvent}; lazy_static! { static ref COLORS: LsColors = LsColors::from_env().unwrap_or_default(); static ref TAGS: RwLock<(bool, Vec)> = RwLock::new((false, vec![])); static ref ICONS: Icons = Icons::new(); + static ref IOPOOL: Mutex> = Mutex::new(None); + static ref IOTICK: AtomicUsize = AtomicUsize::default(); + static ref TICKING: AtomicBool = AtomicBool::new(false); +} + +pub fn tick() { + IOTICK.fetch_add(1, Ordering::Relaxed); +} + +pub fn get_tick() -> usize { + IOTICK.load(Ordering::Relaxed) +} + +pub fn tick_str() -> &'static str { + // Using mod 5 for that nice nonlinear look + match get_tick() % 5 { + 0 => " ", + 1 => ". ", + 2 => ".. ", + _ => "..." + } +} + +pub fn is_ticking() -> bool { + TICKING.load(Ordering::Acquire) +} + +pub fn set_ticking(val: bool) { + TICKING.store(val, Ordering::Release); +} + +#[derive(Fail, Debug, Clone)] +pub enum FileError { + #[fail(display = "Metadata still pending!")] + MetaPending } -fn make_pool(sender: Option>) -> ThreadPool { - let sender = Arc::new(Mutex::new(sender)); +pub fn get_pool() -> ThreadPool { + // Optimal number of threads depends on many things. This is a reasonable default. + const THREAD_NUM: usize = 8; + ThreadPoolBuilder::new() - .num_threads(8) - .exit_handler(move |thread_num| { - if thread_num == 0 { - if let Ok(lock) = sender.lock() { - if let Some(sender) = lock.as_ref() { - sender.send(Events::WidgetReady).ok(); - } - } - } - }) + .num_threads(THREAD_NUM) + .thread_name(|i| format!("hunter_iothread_{}", i)) .build() - .expect("Failed to create thread pool") + .unwrap() } pub fn load_tags() -> HResult<()> { @@ -100,22 +130,22 @@ pub fn tags_loaded() -> HResult<()> { else { HError::tags_not_loaded() } } - -#[derive(PartialEq, Eq, Hash, Clone, Debug)] +#[derive(Derivative)] +#[derivative(PartialEq, Eq, Hash, Clone, Debug)] pub struct RefreshPackage { pub new_files: Option>, - pub new_buffer: Option>, pub new_len: usize, + #[derivative(Debug="ignore")] + #[derivative(PartialEq="ignore")] + #[derivative(Hash="ignore")] + pub jobs: Vec } impl RefreshPackage { - fn new(mut files: Files, - old_buffer: Vec, - events: Vec, - render_fn: impl Fn(&File) -> String) -> RefreshPackage { + fn new(mut files: Files, events: Vec) -> RefreshPackage { use FsEvent::*; // If there is only a placeholder at this point, remove it now @@ -137,15 +167,6 @@ impl RefreshPackage { .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); @@ -155,23 +176,35 @@ impl RefreshPackage { // Save deletions to delete them efficiently later let mut deleted_files = HashSet::with_capacity(event_count); - for event in events.into_iter() { + // Stores jobs to asynchronously fetch metadata + let mut jobs = Vec::with_capacity(event_count); + + let cache = &files.cache.take().unwrap(); + + // Drop would set this stale after the function returns + let stale = files.stale.take().unwrap(); + + + for event in events.into_iter().stop_stale(stale.clone()) { match event { Create(mut file) => { - file.meta_sync().log(); + let job = file.prepare_meta_job(cache); + job.map(|j| jobs.push(j)); new_files.push(file); } Change(file) => { if let Some(&fpos) = file_pos_map.get(&file) { - files.files[fpos].meta_sync().log(); + let job = files.files[fpos].refresh_meta_job(); + jobs.push(job); 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(); - } + let job = files.files[fpos].refresh_meta_job(); + jobs.push(job); + } } Remove(file) => { if let Some(_) = file_pos_map.get(&file) { @@ -181,6 +214,15 @@ impl RefreshPackage { } } + // Bail out without further processing + if stale.is_stale().unwrap_or(true) { + return RefreshPackage { + new_files: None, + new_len: 0, + jobs: jobs + } + } + if deleted_files.len() > 0 { files.files.retain(|file| !deleted_files.contains(file)); } @@ -192,41 +234,28 @@ impl RefreshPackage { files.recalculate_len(); files.sort(); - // Prerender new buffer in current thread - let mut old_buffer = old_buffer; - - let new_buffer = files.iter_files() - .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(); - // Need to unpack this to prevent issue with recursive Files type - // Also, if no files remain add placeholder and set len - let (files, new_len, new_buffer) = if files.len() > 0 { - (files.files, files.len, new_buffer) + // Also, if no files remain add placeholder and set len + let (files, new_len) = if files.len() > 0 { + (std::mem::take(&mut files.files), files.len) } else { let placeholder = File::new_placeholder(&files.directory.path).unwrap(); - let buffer = vec![render_fn(&placeholder)]; files.files.push(placeholder); - (files.files, 1, buffer) + (std::mem::take(&mut files.files), 1) }; - RefreshPackage { new_files: Some(files), - new_buffer: Some(new_buffer), - new_len: new_len + new_len: new_len, + jobs: jobs } } } +// Tuple that stores path and "slots" to store metaadata in +pub type Job = (PathBuf, + Option>>>, + Option>); #[derive(Derivative)] #[derivative(PartialEq, Eq, Hash, Clone, Debug)] @@ -250,6 +279,18 @@ pub struct Files { pub filter: Option, pub filter_selected: bool, pub dirty: DirtyBit, + #[derivative(Debug="ignore")] + #[derivative(PartialEq="ignore")] + #[derivative(Hash="ignore")] + pub jobs: Vec, + #[derivative(Debug="ignore")] + #[derivative(PartialEq="ignore")] + #[derivative(Hash="ignore")] + pub cache: Option, + #[derivative(Debug="ignore")] + #[derivative(PartialEq="ignore")] + #[derivative(Hash="ignore")] + pub stale: Option } impl Index for Files { @@ -292,83 +333,173 @@ impl Default for Files { filter: None, filter_selected: false, dirty: DirtyBit::new(), + jobs: vec![], + cache: None, + stale: None } } } +// Stop processing stuff when Files is dropped +impl Drop for Files { + fn drop(&mut self) { + self.stale + .as_ref() + .map(|s| s.set_stale()); + } +} + impl Files { - pub fn new_from_path(path: &Path) -> HResult { - let direntries: Result, _> = std::fs::read_dir(&path)?.collect(); - let dirty_meta = AsyncDirtyBit::new(); - let tags = &TAGS.read().ok()?.1; + pub fn new_from_path_cancellable(path: &Path, stale: Stale) -> HResult { + let direntries: Vec = std::fs::read_dir(&path)? + .stop_stale(stale.clone()) + .collect::, _>>()?; - let files: Vec<_> = direntries? - .iter() - .map(|file| { - let name = file.file_name(); - let name = name.to_string_lossy(); - let path = file.path(); - let mut file = File::new(&name, - path, - Some(dirty_meta.clone())); - file.set_tag_status(&tags); - Some(file) + let nonhidden = AtomicUsize::default(); + + let direntries: Vec<_> = direntries + .into_par_iter() + .map(|f| { + let f = File::new_from_direntry(f); + // Fast check to avoid iterating twice + if f.name.as_bytes()[0] != b'.' { + nonhidden.fetch_add(1, Ordering::Relaxed); + } + f }) .collect(); - let len = files.len(); + if stale.is_stale()? { + HError::stale()?; + } let mut files = Files::default(); - files.directory = File::new_from_path(&path, None)?; - files.len = len; + files.directory = File::new_from_path(&path)?; + files.files = direntries; + files.len = nonhidden.load(Ordering::Relaxed); + files.stale = Some(stale); Ok(files) } - pub fn new_from_path_cancellable(path: &Path, - stale: Stale) - -> HResult { - let direntries: Result, _> = std::fs::read_dir(&path)?.collect(); - let dirty = DirtyBit::new(); - let dirty_meta = AsyncDirtyBit::new(); + pub fn enqueue_jobs(&mut self, n: usize) { + let pool = get_pool(); + let from = self.meta_upto.unwrap_or(0); + self.meta_upto = Some(from + n); + + let mut jobs = + pool.install(|| { + let c = match self.cache.clone() { + Some(cache) => cache, + None => return vec![] + }; + + self.iter_files_mut() + .skip(from) + .take(n) + // To turn into IndexedParallelIter + .collect::>() + .into_par_iter() + .filter_map(|f| f.prepare_meta_job(&c)) + .collect::>() + }); - let files: Vec<_> = direntries? - .into_iter() - .stop_stale(stale.clone()) - .par_bridge() - .map(|file| { - let file = File::new_from_direntry(file, - Some(dirty_meta.clone())); - file - }) - .collect(); + self.jobs.append(&mut jobs); + } - if stale.is_stale()? { - return Err(crate::fail::HError::StalePreviewError { - file: path.to_string_lossy().to_string() - })?; - } + pub fn run_jobs(&mut self, sender: Sender) { + use std::time::Duration; + let jobs = std::mem::take(&mut self.jobs); + let stale = self.stale.clone() + .unwrap_or(Stale::new()); - let mut files = Files { - directory: File::new_from_path(&path, None)?, - files: files, - len: 0, - pending_events: Arc::new(RwLock::new(vec![])), - refresh: None, - meta_upto: None, - sort: SortBy::Name, - dirs_first: true, - reverse: false, - show_hidden: false, - filter: None, - filter_selected: false, - dirty: dirty, - }; + if jobs.len() == 0 { return; } - files.recalculate_len(); + std::thread::spawn(move || { + let pool = get_pool(); + let jobs_left = AtomicUsize::new(jobs.len()); + let jobs_left = &jobs_left; + let stale = &stale; - Ok(files) + let ticker = move || { + // Gently slow down refreshes + let backoff = Duration::from_millis(10); + let mut cooldown = Duration::from_millis(10); + + loop { + // Send refresh event before sleeping + sender.send(crate::widget::Events::WidgetReady) + .unwrap(); + std::thread::sleep(cooldown); + + // Slow down up to 1 second + if cooldown < Duration::from_secs(1) { + cooldown += backoff; + } + + // All jobs done? + if jobs_left.load(Ordering::Relaxed) == 0 { + // Refresh one last time + sender.send(crate::widget::Events::WidgetReady) + .unwrap(); + crate::files::set_ticking(false); + return; + } + + crate::files::tick(); + } + }; + + // To allow calling without consuming, all while Sender can't be shared + let mut ticker = Some(ticker); + + // Finally this returns the ticker function as an Option + let mut ticker = move || { + // Only return ticker if no one's ticking + match !crate::files::is_ticking() { + true => { + crate::files::set_ticking(true); + ticker.take() + } + false => None + } + }; + + pool.scope_fifo(move |s| { + // Noop with other pool running ticker + ticker().map(|t| s.spawn_fifo(move |_| t())); + + for (path, mslot, dirsize) in jobs.into_iter().stop_stale(stale.clone()) + { + s.spawn_fifo(move |_| { + if let Some(mslot) = mslot { + if let Ok(meta) = std::fs::symlink_metadata(&path) { + *mslot.write().unwrap() = Some(meta); + } + } + + if let Some(dirsize) = dirsize { + std::fs::read_dir(&path) + .map(|dirs| { + let size = dirs.count(); + dirsize.0.store(true, Ordering::Relaxed); + dirsize.1.store(size, Ordering::Relaxed); + }).map_err(|e| { + dirsize.0.store(true, Ordering::Relaxed); + dirsize.1.store(0, Ordering::Relaxed); + HError::from(e) + }).log(); + } + + // Ticker will only stop after this reaches 0 + jobs_left.fetch_sub(1, Ordering::Relaxed); + }); + + ticker().map(|t| s.spawn_fifo(move |_| t())); + } + }); + }); } pub fn recalculate_len(&mut self) { @@ -444,12 +575,14 @@ impl Files { } #[allow(trivial_bounds)] - pub fn into_iter_files(self) -> impl Iterator { - let filter = self.filter; + pub fn into_iter_files(mut self) -> impl Iterator { + let filter = std::mem::take(&mut self.filter); let filter_selected = self.filter_selected; let show_hidden = self.show_hidden; - self.files + let files = std::mem::take(&mut self.files); + + files .into_iter() .filter(move |f| f.kind == Kind::Placeholder || @@ -457,86 +590,122 @@ impl Files { !f.name.contains(filter.as_ref().unwrap())) && (!filter_selected || f.selected)) .filter(move |f| !(!show_hidden && f.name.starts_with("."))) + // Just stuff self in there so drop() doesn't get called immediately + .filter(move |_| { &self; true }) } - pub fn sort(&mut self) { + #[allow(trivial_bounds)] + pub fn take_into_iter_files(&mut self) -> impl Iterator { + let filter = self.filter.clone(); + let filter_selected = self.filter_selected; + let show_hidden = self.show_hidden; + + let files = std::mem::take(&mut self.files); + self.files.clear(); + + files.into_iter() + .filter(move |f| + f.kind == Kind::Placeholder || + !(filter.is_some() && + !f.name.contains(filter.as_ref().unwrap())) && + (!filter_selected || f.selected)) + .filter(move |f| !(!show_hidden && f.name.starts_with("."))) + } + + #[allow(trivial_bounds)] + pub fn sorter(&self) -> impl Fn(&File, &File) -> std::cmp::Ordering { use std::cmp::Ordering::*; - let dirs_first = self.dirs_first; - - match self.sort { - SortBy::Name => self - .files - .par_sort_unstable_by(|a, b| { - if dirs_first { - match (a.is_dir(), b.is_dir()) { - (true, false) => Less, - (false, true) => Greater, - _ => compare_str(&a.name, &b.name), - } - } else { - compare_str(&a.name, &b.name) + let dirs_first = self.dirs_first.clone(); + let sort = self.sort.clone(); + + let dircmp = move |a: &File, b: &File| { + match (a.is_dir(), b.is_dir()) { + (true, false) if dirs_first => Less, + (false, true) if dirs_first => Greater, + _ => Equal + } + }; + + + let reverse = self.reverse; + let namecmp = move |a: &File, b: &File| { + let (a, b) = match reverse { + true => (b, a), + false => (a, b), + }; + + compare_str(&a.name, &b.name) + }; + + let reverse = self.reverse; + let sizecmp = move |a: &File, b: &File| { + let (a, b) = match reverse { + true => (b, a), + false => (a, b), + }; + + match (a.meta(), b.meta()) { + (Some(a_meta), Some(b_meta)) => { + let a_meta = a_meta.as_ref().unwrap(); + let b_meta = b_meta.as_ref().unwrap(); + match a_meta.size() == b_meta.size() { + true => compare_str(&b.name, &a.name), + false => b_meta.size().cmp(&a_meta.size()) } - }), - SortBy::Size => { - if self.meta_upto < Some(self.len()) { - self.meta_all_sync().log(); } + _ => Equal + } + }; - self.files.par_sort_unstable_by(|a, b| { - if dirs_first { - match (a.is_dir(), b.is_dir()) { - (true, false) => return Less, - (false, true) => return Greater, - _ => {} - } - } + let reverse = self.reverse; + let timecmp = move |a: &File, b: &File| { + let (a, b) = match reverse { + true => (b, a), + false => (a, b), + }; - match (a.meta(), b.meta()) { - (Some(a_meta), Some(b_meta)) => { - match a_meta.size() == b_meta.size() { - true => compare_str(&b.name, &a.name), - false => b_meta.size() - .cmp(&a_meta.size()) - } - } - _ => Equal + match (a.meta(), b.meta()) { + (Some(a_meta), Some(b_meta)) => { + let a_meta = a_meta.as_ref().unwrap(); + let b_meta = b_meta.as_ref().unwrap(); + match a_meta.mtime() == b_meta.mtime() { + true => compare_str(&b.name, &a.name), + false => b_meta.mtime().cmp(&a_meta.mtime()) } - }) - } - SortBy::MTime => { - if self.meta_upto < Some(self.len()) { - self.meta_all_sync().log(); } + _ => Equal + } + }; - self.files.par_sort_unstable_by(|a, b| { - if dirs_first { - match (a.is_dir(), b.is_dir()) { - (true, false) => return Less, - (false, true) => return Greater, - _ => {} - } - } - match (a.meta(), b.meta()) { - (Some(a_meta), Some(b_meta)) => { - match a_meta.mtime() == b_meta.mtime() { - true => compare_str(&b.name, &a.name), - false => b_meta.mtime() - .cmp(&a_meta.mtime()) - } - } - _ => Equal - } - }) + move |a, b| match sort { + SortBy::Name => { + match dircmp(a, b) { + Equal => namecmp(a, b), + ord @ _ => ord + } + }, + SortBy::Size => { + match dircmp(a, b) { + Equal => sizecmp(a, b), + ord @ _ => ord + } + } + SortBy::MTime => { + match dircmp(a, b) { + Equal => timecmp(a, b), + ord @ _ => ord + } } } + } - // This could be faster if the sorting itself was reversed - // instead of reversing everything afterwards. - if self.reverse { - self.files.reverse(); - } + pub fn sort(&mut self) { + let sort = self.sorter(); + + self.files + .par_sort_unstable_by(sort); } pub fn cycle_sort(&mut self) { @@ -556,7 +725,10 @@ impl Files { self.set_dirty(); if self.show_hidden == true && self.len() > 1 { - self.remove_placeholder() + self.remove_placeholder(); + + // Need to recheck hidden files + self.meta_upto = None; } self.recalculate_len(); @@ -582,9 +754,11 @@ impl Files { pub fn get_refresh(&mut self) -> HResult> { if let Some(mut refresh) = self.refresh.take() { if refresh.is_ready() { + self.stale.as_ref().map(|s| s.set_fresh()); refresh.pull_async()?; let mut refresh = refresh.value?; self.files = refresh.new_files.take()?; + self.jobs.append(&mut refresh.jobs); if refresh.new_len != self.len() { self.len = refresh.new_len; } @@ -597,25 +771,16 @@ impl Files { return Ok(None) } - pub fn process_fs_events(&mut self, - buffer: Vec, - sender: Sender, - render_fn: impl Fn(&File) -> String + Send + 'static) - -> HResult<()> { + pub fn process_fs_events(&mut self, sender: Sender) -> HResult<()> { let pending = self.pending_events.read()?.len(); if pending > 0 { - let events = self.pending_events - .write()? - .drain(0..pending) - .collect::>(); + let events = std::mem::take(&mut *self.pending_events.write()?); + let files = self.clone(); let mut refresh = Async::new(move |_| { - let refresh = RefreshPackage::new(files, - buffer, - events, - render_fn); + let refresh = RefreshPackage::new(files, events); Ok(refresh) }); @@ -650,28 +815,6 @@ impl Files { self.iter_files_mut().find(|file| file.path == path) } - pub fn meta_all_sync(&mut self) -> HResult<()> { - let same = Mutex::new(true); - - self.iter_files_mut() - .par_bridge() - .for_each(|f| { - if !f.meta_processed { - f.meta_sync().log(); - same.lock() - .map(|mut t| *t = false) - .map_err(HError::from) - .log(); - } - }); - - if !*same.lock()? { - self.set_dirty(); - } - - Ok(()) - } - pub fn set_filter(&mut self, filter: Option) { self.filter = filter; @@ -763,28 +906,23 @@ impl std::default::Default for File { } - #[derive(Clone)] pub struct File { pub name: String, pub path: PathBuf, pub hidden: bool, pub kind: Kind, - pub dirsize: Option>, + pub dirsize: Option>, pub target: Option, - pub color: Option, - pub meta: Option, - pub dirty_meta: Option, - pub meta_processed: bool, + pub meta: Option>>>, pub selected: bool, - pub tag: Option + pub tag: Option, } impl File { pub fn new( name: &str, - path: PathBuf, - dirty_meta: Option) -> File { + path: PathBuf) -> File { let hidden = name.starts_with("."); File { @@ -795,17 +933,13 @@ impl File { dirsize: None, target: None, meta: None, - meta_processed: false, - dirty_meta: dirty_meta, - color: None, selected: false, tag: None, } } pub fn new_with_stale(name: &str, - path: PathBuf, - dirty_meta: Option) -> File { + path: PathBuf) -> File { let hidden = name.starts_with("."); File { @@ -816,26 +950,22 @@ impl File { dirsize: None, target: None, meta: None, - meta_processed: false, - dirty_meta: dirty_meta, - color: None, selected: false, tag: None, } } - pub fn new_from_direntry(direntry: std::fs::DirEntry, - dirty_meta: Option) -> File { + pub fn new_from_direntry(direntry: std::fs::DirEntry) -> File { let path = direntry.path(); - let name = direntry.file_name() - .to_string_lossy() - .to_string(); + let name = direntry.file_name(); + let name = name.to_string_lossy(); + let name = String::from(name); let hidden = name.chars().nth(0) == Some('.'); let kind = match direntry.file_type() { - Ok(ftype) => match ftype.is_file() { - true => Kind::File, - false => Kind::Directory + Ok(ftype) => match ftype.is_dir() { + true => Kind::Directory, + false => Kind::File } _ => Kind::Placeholder }; @@ -848,27 +978,23 @@ impl File { dirsize: None, target: None, meta: None, - meta_processed: false, - dirty_meta: dirty_meta, - color: None, selected: false, tag: None, } } - pub fn new_from_path(path: &Path, - dirty_meta: Option) -> HResult { + pub fn new_from_path(path: &Path) -> HResult { let pathbuf = path.to_path_buf(); let name = path .file_name() .map(|name| name.to_string_lossy().to_string()) .unwrap_or("/".to_string()); - Ok(File::new(&name, pathbuf, dirty_meta)) + Ok(File::new(&name, pathbuf)) } pub fn new_placeholder(path: &Path) -> Result { - let mut file = File::new_from_path(path, None)?; + let mut file = File::new_from_path(path)?; file.name = "".to_string(); file.kind = Kind::Placeholder; Ok(file) @@ -880,67 +1006,93 @@ impl File { Ok(()) } - pub fn meta_sync(&mut self) -> HResult<()> { - let meta = std::fs::symlink_metadata(&self.path)?; - self.meta = Some(meta); - self.process_meta().log(); + pub fn set_dirsize(&mut self, dirsize: Arc<(AtomicBool, AtomicUsize)>) { + self.dirsize = Some(dirsize); + } - // if self.is_dir() { - // let dirsize = std::fs::read_dir(&self.path)?.count(); - // self.dirsize = Some(dirsize); - // } + pub fn refresh_meta_job(&mut self) -> Job { + let meta = self.meta + .as_ref() + .map_or_else(|| Arc::default(), + |m| { + *m.write().unwrap() = None; + m.clone() + }); - Ok(()) - } - pub fn run_dirsize(&mut self) { - let dirsize = Arc::new(AtomicU32::new(0)); - self.dirsize = Some(dirsize.clone()); - let path = self.path.clone(); - rayon::spawn(move || { - std::fs::read_dir(&path) - .map(|dirs| { - let size = dirs.count(); - dirsize.store(size as u32, Ordering::Release); - }).map_err(HError::from) - .log(); - }); + (self.path.clone(), Some(meta), None) } - pub fn meta(&self) -> Option<&Metadata> { - self.meta.as_ref() - } + pub fn prepare_meta_job(&mut self, cache: &FsCache) -> Option { + let mslot = match self.meta { + Some(_) => None, + None => { + let meta: Arc>> = Arc::default(); + self.meta = Some(meta.clone()); + Some(meta) + } + }; - pub fn process_meta(&mut self) -> HResult<()> { - if let Some(ref meta) = self.meta { - let color = self.get_color(&meta); - let target = if meta.file_type().is_symlink() { - self.path.read_link().ok() - } else { None }; + let dslot = match self.dirsize { + None if self.is_dir() => { + let dslot = match cache.get_dirsize(self) { + Some(dslot) => dslot, + None => cache.make_dirsize(self) + }; + self.set_dirsize(dslot.clone()); + Some(dslot) + } + _ => None + }; - self.color = color; - self.target = target; - self.meta_processed = true; + if mslot.is_some() || dslot.is_some() { + let path = self.path.clone(); + Some((path, mslot, dslot)) + } else { + None } - Ok(()) } - pub fn reload_meta(&mut self) -> HResult<()> { - self.meta_processed = false; - self.meta_sync() + pub fn meta(&self) -> Option>> { + let meta = self.meta + .as_ref()? + .read() + .ok(); + + match meta { + Some(meta) => + if meta.is_some() { + Some(meta) + } else { + None + }, + None => None + } } - fn get_color(&self, meta: &std::fs::Metadata) -> Option { + pub fn get_color(&self) -> Option { + let meta = self.meta()?; + let meta = meta.as_ref()?; match COLORS.style_for_path_with_metadata(&self.path, Some(&meta)) { - Some(style) => style.clone().foreground, + // TODO: Also handle bg color, bold, etc.? + Some(style) => style.foreground + .as_ref() + .map(|c| crate::term::from_lscolor(&c)), None => None, } } - pub fn calculate_size(&self) -> HResult<(u32, &str)> { + pub fn calculate_size(&self) -> HResult<(usize, &str)> { if self.is_dir() { let size = match self.dirsize { - Some(ref size) => (size.load(Ordering::Acquire), ""), + Some(ref dirsize) => { + let (ref ready, ref size) = **dirsize; + if ready.load(Ordering::Acquire) == true { + (size.load(Ordering::Acquire), "") + } else { + return Err(FileError::MetaPending)?; + } + }, None => (0, ""), }; @@ -949,7 +1101,10 @@ impl File { let mut unit = 0; - let mut size = self.meta()?.size(); + let mut size = match self.meta() { + Some(meta) => meta.as_ref().unwrap().size(), + None => return Err(FileError::MetaPending)? + }; while size > 1024 { size /= 1024; unit += 1; @@ -964,7 +1119,7 @@ impl File { _ => "", }; - Ok((size as u32, unit)) + Ok((size as usize, unit)) } // Sadly tree_magic tends to panic (in unwraps a None) when called @@ -983,33 +1138,24 @@ impl File { } } - self.meta() - .map(|meta| { - // Take and replace panic handler which does nothing - let panic_hook = panic::take_hook(); - panic::set_hook(Box::new(|_| {} )); - - // Catch possible panic caused by tree_magic - let mime = panic::catch_unwind(|| { - match meta.is_file() && self.path.exists() { - true => { - let mime = tree_magic::from_filepath(&self.path); - mime::Mime::from_str(&mime).ok() - } - false => None - } - }); + // Take and replace panic handler which does nothing + let panic_hook = panic::take_hook(); + panic::set_hook(Box::new(|_| {} )); - // Restore previous panic handler - panic::set_hook(panic_hook); + // Catch possible panic caused by tree_magic + let mime = panic::catch_unwind(|| { + let mime = tree_magic::from_filepath(&self.path); + mime::Mime::from_str(&mime).ok() + }); + + // Restore previous panic handler + panic::set_hook(panic_hook); - mime - }).unwrap_or(Ok(None)) - .unwrap_or(None) - .ok_or_else(|| { - let file = self.name.clone(); - HError::Mime(MimeError::Panic(file)) - }) + mime.unwrap_or(None) + .ok_or_else(|| { + let file = self.name.clone(); + HError::Mime(MimeError::Panic(file)) + }) } @@ -1030,13 +1176,13 @@ impl File { } - pub fn parent(&self) -> Option { - Some(self.path.parent()?.to_path_buf()) + pub fn parent(&self) -> Option<&Path> { + self.path.parent() } pub fn parent_as_file(&self) -> HResult { let pathbuf = self.parent()?; - File::new_from_path(&pathbuf, None) + File::new_from_path(&pathbuf) } pub fn grand_parent(&self) -> Option { @@ -1045,7 +1191,7 @@ impl File { pub fn grand_parent_as_file(&self) -> HResult { let pathbuf = self.grand_parent()?; - File::new_from_path(&pathbuf, None) + File::new_from_path(&pathbuf) } pub fn is_dir(&self) -> bool { @@ -1053,7 +1199,7 @@ impl File { } pub fn read_dir(&self) -> HResult { - Files::new_from_path(&self.path) + Files::new_from_path_cancellable(&self.path, Stale::new()) } pub fn strip_prefix(&self, base: &File) -> PathBuf { @@ -1129,6 +1275,7 @@ impl File { pub fn is_readable(&self) -> HResult { let meta = self.meta()?; + let meta = meta.as_ref()?; let current_user = get_current_username()?.to_string_lossy().to_string(); let current_group = get_current_groupname()?.to_string_lossy().to_string(); let file_user = get_user_by_uid(meta.uid())? @@ -1157,7 +1304,10 @@ impl File { } pub fn pretty_print_permissions(&self) -> HResult { - let perms: usize = format!("{:o}", self.meta()?.mode()).parse().unwrap(); + let meta = self.meta()?; + let meta = meta.as_ref()?; + + let perms: usize = format!("{:o}", meta.mode()).parse().unwrap(); let perms: usize = perms % 800; let perms = format!("{}", perms); @@ -1181,8 +1331,9 @@ impl File { } pub fn pretty_user(&self) -> Option { - if self.meta().is_none() { return None } - let uid = self.meta().unwrap().uid(); + let meta = self.meta()?; + let meta = meta.as_ref()?; + let uid = meta.uid(); let file_user = users::get_user_by_uid(uid)?; let cur_user = users::get_current_username()?; let color = @@ -1194,8 +1345,9 @@ impl File { } pub fn pretty_group(&self) -> Option { - if self.meta().is_none() { return None } - let gid = self.meta().unwrap().gid(); + let meta = self.meta()?; + let meta = meta.as_ref()?; + let gid = meta.gid(); let file_group = users::get_group_by_gid(gid)?; let cur_group = users::get_current_groupname()?; let color = @@ -1207,9 +1359,11 @@ impl File { } pub fn pretty_mtime(&self) -> Option { - if self.meta().is_none() { return None } + let meta = self.meta()?; + let meta = meta.as_ref()?; + let time: chrono::DateTime - = chrono::Local.timestamp(self.meta().unwrap().mtime(), 0); + = chrono::Local.timestamp(meta.mtime(), 0); Some(time.format("%F %R").to_string()) } diff --git a/src/fscache.rs b/src/fscache.rs index 613bd3e..11d30f9 100644 --- a/src/fscache.rs +++ b/src/fscache.rs @@ -6,7 +6,7 @@ use std::sync::{Arc, RwLock, Weak}; use std::sync::mpsc::{channel, Sender, Receiver}; use std::collections::{HashMap, HashSet}; use std::time::Duration; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::files::{Files, File, SortBy}; use crate::widget::Events; @@ -123,10 +123,12 @@ impl FsEventDispatcher { // fn remove_unnecessary } - +use std::sync::atomic::{AtomicBool, AtomicUsize}; #[derive(Clone)] pub struct FsCache { files: Arc>>, + dirsizes: Arc>>>>, pub tab_settings: Arc>>, watched_dirs: Arc>>, watcher: Arc>, @@ -142,6 +144,7 @@ impl FsCache { let fs_cache = FsCache { files: Arc::new(RwLock::new(HashMap::new())), + dirsizes: 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)), @@ -176,7 +179,6 @@ impl FsCache { cache.fs_event_dispatcher.add_target(&dir, &files.pending_events).log(); FsCache::apply_settingss(&cache, &mut files).ok(); - files.sort(); Ok(files) }); Ok((selection, files)) @@ -227,6 +229,67 @@ impl FsCache { Ok(self.files.read()?.contains_key(dir)) } + pub fn get_dirsize(&self, dir: &File) -> Option> { + let parent_dir = dir.parent() + .unwrap_or_else(|| Path::new("/")); + + self.dirsizes + .read() + .unwrap() + .get(parent_dir) + .map(|parent_map| { + parent_map.get(&dir.path) + }) + .flatten() + .cloned() + } + + pub fn make_dirsize(&self, dir: &File) -> Arc<(AtomicBool, AtomicUsize)> { + let parent_dir = dir.parent() + .unwrap_or_else(|| Path::new("/")); + let dir = dir.path.as_path(); + + let mut dirsizes = self.dirsizes + .write() + .unwrap(); + + match dirsizes.contains_key(parent_dir) { + false => { + let mut dir_map = HashMap::new(); + let ready = AtomicBool::new(false); + let size = AtomicUsize::default(); + let dirsize = Arc::new((ready, size)); + dir_map.insert(dir.to_path_buf(), + dirsize); + dirsizes.insert(parent_dir.to_path_buf(), + dir_map); + + return dirsizes.get(parent_dir) + .unwrap() + .get(dir) + .unwrap() + .clone(); + } + true => { + let pmap = dirsizes.get_mut(parent_dir).unwrap(); + + match pmap.get(dir) { + Some(dirsize) => dirsize.clone(), + None => { + let ready = AtomicBool::new(false); + let size = AtomicUsize::default(); + let dirsize = Arc::new((ready, size)); + pmap.insert(dir.to_path_buf(), dirsize); + + return pmap.get(dir) + .unwrap() + .clone() + } + } + } + } + } + pub fn watch_only(&self, open_dirs: HashSet) -> HResult<()> { let removable = self.watched_dirs .read()? @@ -399,18 +462,18 @@ impl TryFrom for FsEvent { fn try_from(event: DebouncedEvent) -> HResult { let event = match event { DebouncedEvent::Create(path) - => FsEvent::Create(File::new_from_path(&path, None)?), + => FsEvent::Create(File::new_from_path(&path)?), DebouncedEvent::Remove(path) - => FsEvent::Remove(File::new_from_path(&path, None)?), + => FsEvent::Remove(File::new_from_path(&path)?), DebouncedEvent::Write(path) | DebouncedEvent::Chmod(path) - => FsEvent::Change(File::new_from_path(&path, None)?), + => FsEvent::Change(File::new_from_path(&path)?), DebouncedEvent::Rename(old_path, new_path) - => FsEvent::Rename(File::new_from_path(&old_path, None)?, - File::new_from_path(&new_path, None)?), + => FsEvent::Rename(File::new_from_path(&old_path)?, + File::new_from_path(&new_path)?), DebouncedEvent::Error(err, path) => Err(HError::INotifyError(format!("{}, {:?}", err, path)))?, @@ -435,7 +498,7 @@ fn watch_fs(rx_fs_events: Receiver, let dirpath = path.parent() .map(|path| path) .unwrap_or(std::path::Path::new("/")); - let dir = File::new_from_path(&dirpath, None)?; + let dir = File::new_from_path(&dirpath)?; let event = FsEvent::try_from(event)?; Ok((dir, event)) }; diff --git a/src/listview.rs b/src/listview.rs index 8913390..13f5ba6 100644 --- a/src/listview.rs +++ b/src/listview.rs @@ -1,11 +1,11 @@ use std::fmt::Debug; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use termion::event::Key; use unicode_width::UnicodeWidthStr; use rayon::prelude::*; -use async_value::{Stale, StopIter}; +use async_value::Stale; use crate::files::{File, Files}; use crate::fail::{HResult, HError, ErrorLog}; @@ -103,17 +103,6 @@ impl Listable for ListView { fn on_new(&mut self) -> HResult<()> { let show_hidden = self.core.config().show_hidden(); self.content.show_hidden = show_hidden; - let mut file = self.content - .iter_files() - .nth(0) - .cloned() - .unwrap_or_default(); - - if file.meta.is_none() { - f