exa/src/fs/dir.rs

212 lines
6.4 KiB
Rust
Raw Normal View History

use crate::fs::feature::git::GitCache;
use crate::fs::fields::GitStatus;
2015-12-15 21:47:37 +00:00
use std::io::{self, Result as IOResult};
use std::fs;
use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;
use log::*;
2018-12-07 23:43:31 +00:00
use crate::fs::File;
2015-06-08 20:33:39 +00:00
/// A **Dir** provides a cached list of the file paths in a directory thats
2015-01-24 12:38:05 +00:00
/// being listed.
///
/// This object gets passed to the Files themselves, in order for them to
/// check the existence of surrounding files, then highlight themselves
/// accordingly. (See `File#get_source_files`)
2014-11-26 07:40:52 +00:00
pub struct Dir {
2015-09-03 17:48:53 +00:00
/// A vector of the files that have been read from this directory.
contents: Vec<PathBuf>,
2015-09-03 17:48:53 +00:00
/// The path that was read.
Parallelise the details view! This commit removes the threadpool in `main.rs` that stats each command-line argument separately, and replaces it with a *scoped* threadpool in `options/details.rs` that builds the table in parallel! Running this on my machine halves the execution time when tree-ing my entire home directory (which isn't exactly a common occurrence, but it's the only way to give exa a large running time) The statting will be added back in parallel at a later stage. This was facilitated by the previous changes to recursion that made it easier to deal with. There's a lot of large sweeping architectural changes. Here's a smattering of them: - In `main.rs`, the files are now passed around as vectors of files rather than array slices of files. This is because `File`s aren't `Clone`, and the `Vec` is necessary to give away ownership of the files at the appropriate point. - In the details view, files are now sorted *all* the time, rather than obeying the command-line order. As they're run in parallel, they have no guaranteed order anyway, so we *have* to sort them again. (I'm not sure if this should be the intended behaviour or not!) This means that the `Details` struct has to have the filter *all* the time, not only while recursing, so it's been moved out of the `recurse` field. - We use `scoped_threadpool` over `threadpool`, a recent addition. It's only safely used on Nightly, which we're using anyway, so that's OK! - Removed a bunch of out-of-date comments. This also fixes #77, mainly by accident :)
2015-09-02 22:19:10 +00:00
pub path: PathBuf,
}
2014-11-26 07:40:52 +00:00
impl Dir {
2015-01-24 12:38:05 +00:00
/// Create a new Dir object filled with all the files in the directory
/// pointed to by the given path. Fails if the directory cant be read, or
/// isnt actually a directory, or if theres an IO error that occurs at
/// any point.
///
/// The `read_dir` iterator doesnt actually yield the `.` and `..`
/// entries, so if the user wants to see them, well have to add them
/// ourselves after the files have been read.
pub fn read_dir(path: PathBuf) -> IOResult<Self> {
2017-08-20 17:14:40 +00:00
info!("Reading directory {:?}", &path);
2017-08-26 22:53:47 +00:00
2018-04-18 00:16:32 +00:00
let contents = fs::read_dir(&path)?
.map(|result| result.map(|entry| entry.path()))
.collect::<Result<_, _>>()?;
Ok(Self { contents, path })
}
/// Produce an iterator of IO results of trying to read all the files in
/// this directory.
pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool) -> Files<'dir, 'ig> {
Files {
inner: self.contents.iter(),
dir: self,
dotfiles: dots.shows_dotfiles(),
dots: dots.dots(),
git,
git_ignoring,
}
}
2015-01-24 12:38:05 +00:00
/// Whether this directory contains a file with the given path.
pub fn contains(&self, path: &Path) -> bool {
2017-03-31 16:09:32 +00:00
self.contents.iter().any(|p| p.as_path() == path)
}
2015-01-26 17:26:11 +00:00
/// Append a path onto the path specified by this directory.
pub fn join(&self, child: &Path) -> PathBuf {
2015-01-26 17:26:11 +00:00
self.path.join(child)
}
}
2015-09-03 17:48:53 +00:00
/// Iterator over reading the contents of a directory as `File` objects.
pub struct Files<'dir, 'ig> {
/// The internal iterator over the paths that have been read already.
inner: SliceIter<'dir, PathBuf>,
/// The directory that begat those paths.
dir: &'dir Dir,
/// Whether to include dotfiles in the list.
dotfiles: bool,
/// Whether the `.` or `..` directories should be produced first, before
/// any files have been listed.
dots: DotsNext,
git: Option<&'ig GitCache>,
git_ignoring: bool,
}
impl<'dir, 'ig> Files<'dir, 'ig> {
fn parent(&self) -> PathBuf {
// We cant use `Path#parent` here because all it does is remove the
// last path component, which is no good for us if the path is
// relative. For example, while the parent of `/testcases/files` is
// `/testcases`, the parent of `.` is an empty path. Adding `..` on
// the end is the only way to get to the *actual* parent directory.
self.dir.path.join("..")
}
/// Go through the directory until we encounter a file we can list (which
/// varies depending on the dotfile visibility flag)
fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
loop {
if let Some(path) = self.inner.next() {
2017-06-29 12:17:26 +00:00
let filename = File::filename(path);
if ! self.dotfiles && filename.starts_with('.') {
continue;
}
if self.git_ignoring {
let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
if git_status.unstaged == GitStatus::Ignored {
continue;
}
}
return Some(File::from_args(path.clone(), self.dir, filename)
.map_err(|e| (path.clone(), e)))
}
else {
return None
}
}
}
}
/// The dot directories that need to be listed before actual files, if any.
/// If these arent being printed, then `FilesNext` is used to skip them.
enum DotsNext {
/// List the `.` directory next.
Dot,
/// List the `..` directory next.
DotDot,
/// Forget about the dot directories and just list files.
Files,
}
impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
fn next(&mut self) -> Option<Self::Item> {
match self.dots {
DotsNext::Dot => {
self.dots = DotsNext::DotDot;
Some(File::new_aa_current(self.dir)
.map_err(|e| (Path::new(".").to_path_buf(), e)))
}
DotsNext::DotDot => {
self.dots = DotsNext::Files;
Some(File::new_aa_parent(self.parent(), self.dir)
.map_err(|e| (self.parent(), e)))
}
DotsNext::Files => {
self.next_visible_file()
}
}
}
}
/// Usually files in Unix use a leading dot to be hidden or visible, but two
/// entries in particular are “extra-hidden”: `.` and `..`, which only become
/// visible after an extra `-a` option.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum DotFilter {
/// Shows files, dotfiles, and `.` and `..`.
DotfilesAndDots,
/// Show files and dotfiles, but hide `.` and `..`.
Dotfiles,
/// Just show files, hiding anything beginning with a dot.
JustFiles,
}
impl Default for DotFilter {
fn default() -> Self {
Self::JustFiles
}
}
impl DotFilter {
/// Whether this filter should show dotfiles in a listing.
2018-06-19 12:58:03 +00:00
fn shows_dotfiles(self) -> bool {
match self {
Self::JustFiles => false,
Self::Dotfiles => true,
Self::DotfilesAndDots => true,
}
}
/// Whether this filter should add dot directories to a listing.
fn dots(self) -> DotsNext {
2018-06-19 12:58:03 +00:00
match self {
Self::JustFiles => DotsNext::Files,
Self::Dotfiles => DotsNext::Files,
Self::DotfilesAndDots => DotsNext::Dot,
}
}
}