2020-02-06 01:01:44 +01:00
|
|
|
use crate::common::*;
|
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
const JUNK: &[&str] = &["Thumbs.db", "Desktop.ini"];
|
|
|
|
|
2020-02-06 01:01:44 +01:00
|
|
|
pub(crate) struct Walker {
|
2020-02-06 03:32:09 +01:00
|
|
|
follow_symlinks: bool,
|
2020-02-06 01:01:44 +01:00
|
|
|
include_hidden: bool,
|
2020-02-06 03:32:09 +01:00
|
|
|
include_junk: bool,
|
2020-02-06 01:01:44 +01:00
|
|
|
root: PathBuf,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Walker {
|
|
|
|
pub(crate) fn new(root: &Path) -> Walker {
|
|
|
|
Walker {
|
2020-02-06 03:32:09 +01:00
|
|
|
follow_symlinks: false,
|
2020-02-06 01:01:44 +01:00
|
|
|
include_hidden: false,
|
2020-02-06 03:32:09 +01:00
|
|
|
include_junk: false,
|
2020-02-06 01:01:44 +01:00
|
|
|
root: root.to_owned(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
pub(crate) fn include_junk(self, include_junk: bool) -> Self {
|
|
|
|
Walker {
|
|
|
|
include_junk,
|
|
|
|
..self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn include_hidden(self, include_hidden: bool) -> Self {
|
2020-02-06 01:01:44 +01:00
|
|
|
Walker {
|
2020-02-06 03:32:09 +01:00
|
|
|
include_hidden,
|
2020-02-06 01:01:44 +01:00
|
|
|
..self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
pub(crate) fn follow_symlinks(self, follow_symlinks: bool) -> Self {
|
2020-02-06 01:01:44 +01:00
|
|
|
Walker {
|
2020-02-06 03:32:09 +01:00
|
|
|
follow_symlinks,
|
2020-02-06 01:01:44 +01:00
|
|
|
..self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn files(self) -> Result<Files, Error> {
|
2020-02-06 03:32:09 +01:00
|
|
|
if !self.follow_symlinks
|
|
|
|
&& self
|
|
|
|
.root
|
|
|
|
.symlink_metadata()
|
|
|
|
.context(error::Filesystem { path: &self.root })?
|
|
|
|
.file_type()
|
|
|
|
.is_symlink()
|
|
|
|
{
|
|
|
|
return Err(Error::SymlinkRoot { root: self.root });
|
|
|
|
}
|
2020-02-06 01:01:44 +01:00
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
let root_metadata = self
|
|
|
|
.root
|
|
|
|
.metadata()
|
|
|
|
.context(error::Filesystem { path: &self.root })?;
|
2020-02-06 01:01:44 +01:00
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
if root_metadata.is_file() {
|
|
|
|
return Ok(Files::file(self.root, Bytes::from(root_metadata.len())));
|
|
|
|
}
|
2020-02-06 01:01:44 +01:00
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
let filter = |entry: &walkdir::DirEntry| {
|
2020-02-06 01:01:44 +01:00
|
|
|
let path = entry.path();
|
|
|
|
|
|
|
|
let file_name = entry.file_name();
|
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
if !self.include_hidden && file_name.to_string_lossy().starts_with('.') {
|
|
|
|
return false;
|
2020-02-06 01:01:44 +01:00
|
|
|
}
|
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
let hidden = Platform::hidden(path).unwrap_or(true);
|
|
|
|
|
|
|
|
if !self.include_hidden && hidden {
|
|
|
|
return false;
|
2020-02-06 01:01:44 +01:00
|
|
|
}
|
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
true
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut paths = Vec::new();
|
|
|
|
let mut total_size = 0;
|
|
|
|
for result in WalkDir::new(&self.root)
|
|
|
|
.follow_links(self.follow_symlinks)
|
|
|
|
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
|
|
|
|
.into_iter()
|
|
|
|
.filter_entry(filter)
|
|
|
|
{
|
|
|
|
let entry = result?;
|
|
|
|
|
|
|
|
let path = entry.path();
|
|
|
|
|
|
|
|
let metadata = entry.metadata()?;
|
|
|
|
|
|
|
|
if !metadata.is_file() {
|
2020-02-06 01:01:44 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
let file_path = FilePath::from_prefix_and_path(&self.root, &path)?;
|
|
|
|
|
|
|
|
if !self.include_junk && JUNK.contains(&file_path.name()) {
|
2020-02-06 01:01:44 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
total_size += metadata.len();
|
2020-02-06 03:32:09 +01:00
|
|
|
|
|
|
|
paths.push(file_path);
|
2020-02-06 01:01:44 +01:00
|
|
|
}
|
|
|
|
|
2020-02-06 03:32:09 +01:00
|
|
|
Ok(Files::dir(self.root, Bytes::from(total_size), paths))
|
2020-02-06 01:01:44 +01:00
|
|
|
}
|
|
|
|
}
|