exa/src/fs/dir.rs

210 lines
6.6 KiB
Rust
Raw Normal View History

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 fs::feature::Git;
use fs::{File, fields};
2015-06-08 20:33:39 +00:00
2015-01-24 12:38:05 +00:00
/// A **Dir** provides a cached list of the file paths in a directory that's
/// 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,
2015-09-03 17:48:53 +00:00
/// Holds a `Git` object if scanning for Git repositories is switched on,
/// and this directory happens to contain one.
git: Option<Git>,
}
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, git: bool) -> IOResult<Dir> {
info!("Reading directory {:?}", &path);
let contents: Vec<PathBuf> = try!(fs::read_dir(&path)?
2017-06-27 17:13:18 +00:00
.map(|result| result.map(|entry| entry.path()))
.collect());
2017-06-27 17:13:18 +00:00
let git = if git { Git::scan(&path).ok() } else { None };
Ok(Dir { contents, path, git })
}
/// Produce an iterator of IO results of trying to read all the files in
/// this directory.
pub fn files(&self, dots: DotFilter) -> Files {
Files {
inner: self.contents.iter(),
dir: self,
dotfiles: dots.shows_dotfiles(),
dots: dots.dots(),
}
}
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)
}
/// Return whether there's a Git repository on or above this directory.
pub fn has_git_repo(&self) -> bool {
self.git.is_some()
}
/// Get a string describing the Git status of the given file.
2015-05-12 02:33:40 +00:00
pub fn git_status(&self, path: &Path, prefix_lookup: bool) -> fields::Git {
2015-01-28 10:43:19 +00:00
match (&self.git, prefix_lookup) {
(&Some(ref git), false) => git.status(path),
(&Some(ref git), true) => git.dir_status(path),
2015-05-12 02:33:40 +00:00
(&None, _) => fields::Git::empty()
}
}
}
2015-09-03 17:48:53 +00:00
/// Iterator over reading the contents of a directory as `File` objects.
pub struct Files<'dir> {
/// 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: Dots,
}
impl<'dir> Files<'dir> {
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 }
2017-06-29 12:17:26 +00:00
return Some(File::new(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 Dots {
/// List the `.` directory next.
DotNext,
/// List the `..` directory next.
DotDotNext,
/// Forget about the dot directories and just list files.
FilesNext,
}
impl<'dir> Iterator for Files<'dir> {
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
fn next(&mut self) -> Option<Self::Item> {
if let Dots::DotNext = self.dots {
self.dots = Dots::DotDotNext;
2017-06-29 12:17:26 +00:00
Some(File::new(self.dir.path.to_path_buf(), self.dir, String::from("."))
.map_err(|e| (Path::new(".").to_path_buf(), e)))
}
else if let Dots::DotDotNext = self.dots {
self.dots = Dots::FilesNext;
2017-06-29 12:17:26 +00:00
Some(File::new(self.parent(), self.dir, String::from(".."))
.map_err(|e| (self.parent(), e)))
}
else {
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() -> DotFilter {
DotFilter::JustFiles
}
}
impl DotFilter {
/// Whether this filter should show dotfiles in a listing.
fn shows_dotfiles(&self) -> bool {
match *self {
DotFilter::JustFiles => false,
DotFilter::Dotfiles => true,
DotFilter::DotfilesAndDots => true,
}
}
/// Whether this filter should add dot directories to a listing.
fn dots(&self) -> Dots {
match *self {
DotFilter::JustFiles => Dots::FilesNext,
DotFilter::Dotfiles => Dots::FilesNext,
DotFilter::DotfilesAndDots => Dots::DotNext,
}
}
}