use std::cmp::{self, Ordering};
use std::collections::{BinaryHeap, VecDeque};
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::result::Result;
use std::time::{Duration, Instant};
use crate::errors::TreeBuildError;
use crate::flat_tree::{LineType, Tree, TreeLine};
use crate::git_ignore::GitIgnoreFilter;
use crate::task_sync::TaskLifetime;
use crate::tree_options::{OptionBool, TreeOptions};
struct BLine {
parent_idx: usize,
path: PathBuf,
depth: u16,
name: String,
file_type: fs::FileType,
children: Option<Vec<usize>>, next_child_idx: usize, has_error: bool,
has_match: bool,
score: i32,
ignore_filter: Option<GitIgnoreFilter>,
nb_kept_children: i32, }
enum BLineResult {
Some(BLine), FilteredOutAsHidden,
FilteredOutByPattern,
FilteredOutAsNonFolder,
GitIgnored,
Invalid,
}
impl BLine {
fn from_root(path: PathBuf, respect_ignore: OptionBool) -> Result<BLine, TreeBuildError> {
let name = match path.file_name() {
Some(name) => name.to_string_lossy().to_string(),
None => String::from("???"), };
let ignore_filter = if respect_ignore == OptionBool::No {
None
} else {
let gif = GitIgnoreFilter::applicable_to(&path);
if respect_ignore == OptionBool::Auto && gif.files.is_empty() {
None
} else {
Some(gif)
}
};
if let Ok(md) = fs::metadata(&path) {
let file_type = md.file_type();
Ok(BLine {
parent_idx: 0,
path,
depth: 0,
name,
children: None,
next_child_idx: 0,
file_type,
has_error: false,
has_match: true,
score: 0,
ignore_filter,
nb_kept_children: 0,
})
} else {
Err(TreeBuildError::FileNotFound {
path: format!("{:?}", path),
})
}
}
fn from(
parent_idx: usize,
e: fs::DirEntry,
depth: u16,
options: &TreeOptions,
parent_ignore_filter: &Option<GitIgnoreFilter>,
) -> BLineResult {
let name = e.file_name();
let name = match name.to_str() {
Some(name) => name,
None => {
return BLineResult::Invalid;
}
};
if !options.show_hidden && name.starts_with('.') {
return BLineResult::FilteredOutAsHidden;
}
let mut has_match = true;
let mut score = 10000 - i32::from(depth); if options.pattern.is_some() {
if let Some(m) = options.pattern.find(&name) {
score += m.score;
} else {
has_match = false;
}
}
let file_type = match e.file_type() {
Ok(ft) => ft,
Err(_) => {
return BLineResult::Invalid;
}
};
if file_type.is_file() || file_type.is_symlink() {
if !has_match {
return BLineResult::FilteredOutByPattern;
}
if options.only_folders {
return BLineResult::FilteredOutAsNonFolder;
}
}
let path = e.path();
let mut ignore_filter = None;
if let Some(gif) = parent_ignore_filter {
if !gif.accepts(&path, &name, file_type.is_dir()) {
return BLineResult::GitIgnored;
}
if file_type.is_dir() {
ignore_filter = Some(gif.extended_to(&path));
}
}
BLineResult::Some(BLine {
parent_idx,
path,
depth,
name: name.to_string(),
file_type,
children: None,
next_child_idx: 0,
has_error: false,
has_match,
score,
ignore_filter,
nb_kept_children: 0,
})
}
fn to_tree_line(&self) -> TreeLine {
let mut mode = 0;
let mut uid = 0;
let mut gid = 0;
let mut has_error = self.has_error;
if let Ok(metadata) = fs::symlink_metadata(&self.path) {
mode = metadata.mode();
uid = metadata.uid();
gid = metadata.gid();
}
let line_type = if self.file_type.is_dir() {
LineType::Dir
} else if self.file_type.is_symlink() {
if let Ok(target) = fs::read_link(&self.path) {
let target = target.to_string_lossy().into_owned();
let mut target_path = PathBuf::from(&target);
if target_path.is_relative() {
target_path = self.path.parent().unwrap().join(target_path)
}
if let Ok(target_metadata) = fs::symlink_metadata(&target_path) {
if target_metadata.file_type().is_dir() {
LineType::SymLinkToDir(target)
} else {
LineType::SymLinkToFile(target)
}
} else {
has_error = true;
LineType::SymLinkToFile(target)
}
} else {
has_error = true;
LineType::SymLinkToFile(String::from("????"))
}
} else {
LineType::File
};
let unlisted = if let Some(children) = &self.children {
children.len() - self.next_child_idx
} else {
0
};
TreeLine {
left_branchs: vec![false; self.depth as usize].into_boxed_slice(),
depth: self.depth,
name: self.name.to_string(),
path: self.path.clone(),
line_type,
has_error,
nb_kept_children: self.nb_kept_children as usize,
unlisted,
score: self.score,
mode,
uid,
gid,
size: None,
}
}
}
struct SortableBLineIdx {
idx: usize,
score: i32,
}
impl Eq for SortableBLineIdx {}
impl PartialEq for SortableBLineIdx {
fn eq(&self, other: &SortableBLineIdx) -> bool {
self.score == other.score }
}
impl Ord for SortableBLineIdx {
fn cmp(&self, other: &SortableBLineIdx) -> Ordering {
if self.score == other.score {
Ordering::Equal
} else if self.score < other.score {
Ordering::Greater
} else {
Ordering::Less
}
}
}
impl PartialOrd for SortableBLineIdx {
fn partial_cmp(&self, other: &SortableBLineIdx) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
pub struct TreeBuilder {
blines: Vec<BLine>, options: TreeOptions,
targeted_size: usize, nb_gitignored: u32, }
impl TreeBuilder {
pub fn from(
path: PathBuf,
options: TreeOptions,
targeted_size: usize,
) -> Result<TreeBuilder, TreeBuildError> {
let mut blines = Vec::new();
blines.push(BLine::from_root(path, options.respect_git_ignore)?);
Ok(TreeBuilder {
blines,
options,
targeted_size,
nb_gitignored: 0,
})
}
fn store(&mut self, bline: BLine) -> usize {
let idx = self.blines.len();
self.blines.push(bline);
idx
}
fn load_children(&mut self, bline_idx: usize) -> bool {
let mut has_child_match = false;
match fs::read_dir(&self.blines[bline_idx].path) {
Ok(entries) => {
let mut children: Vec<usize> = Vec::new();
for e in entries {
if let Ok(e) = e {
let bl = BLine::from(
bline_idx,
e,
self.blines[bline_idx].depth + 1,
&self.options,
&self.blines[bline_idx].ignore_filter,
);
match bl {
BLineResult::Some(bl) => {
if bl.has_match {
self.blines[bline_idx].has_match = true;
has_child_match = true;
}
children.push(self.store(bl));
}
BLineResult::GitIgnored => {
self.nb_gitignored += 1;
}
_ => {
}
}
}
}
children.sort_by(|&a, &b| {
self.blines[a]
.name
.to_lowercase()
.cmp(&self.blines[b].name.to_lowercase())
});
self.blines[bline_idx].children = Some(children);
}
Err(_err) => {
self.blines[bline_idx].has_error = true;
self.blines[bline_idx].children = Some(Vec::new());
}
}
has_child_match
}
fn next_child(
&mut self,
bline_idx: usize, ) -> Option<usize> {
let bline = &mut self.blines[bline_idx];
if let Some(children) = &bline.children {
if bline.next_child_idx < children.len() {
let next_child = children[bline.next_child_idx];
bline.next_child_idx += 1;
Some(next_child)
} else {
Option::None
}
} else {
unreachable!();
}
}
fn gather_lines(&mut self, task_lifetime: &TaskLifetime) -> Option<Vec<usize>> {
let start = Instant::now();
let mut out_blines: Vec<usize> = Vec::new(); let not_long = Duration::from_millis(600);
let optimal_size = self
.options
.pattern
.optimal_result_number(self.targeted_size);
out_blines.push(0);
let mut nb_lines_ok = 1; let mut open_dirs: VecDeque<usize> = VecDeque::new();
let mut next_level_dirs: Vec<usize> = Vec::new();
self.load_children(0);
open_dirs.push_back(0);
loop {
if (nb_lines_ok > optimal_size)
|| (nb_lines_ok >= self.targeted_size && start.elapsed() > not_long)
{
break;
}
if let Some(open_dir_idx) = open_dirs.pop_front() {
if let Some(child_idx) = self.next_child(open_dir_idx) {
open_dirs.push_back(open_dir_idx);
let child = &self.blines[child_idx];
if child.has_match {
nb_lines_ok += 1;
}
if child.file_type.is_dir() {
next_level_dirs.push(child_idx);
}
out_blines.push(child_idx);
}
} else {
if next_level_dirs.is_empty() {
break;
}
for next_level_dir_idx in &next_level_dirs {
if task_lifetime.is_expired() {
info!("task expired (core build - inner loop)");
return None;
}
let has_child_match = self.load_children(*next_level_dir_idx);
if has_child_match {
let mut idx = *next_level_dir_idx;
loop {
let mut bline = &mut self.blines[idx];
if !bline.has_match {
bline.has_match = true;
nb_lines_ok += 1;
}
idx = bline.parent_idx;
if idx == 0 {
break;
}
}
}
open_dirs.push_back(*next_level_dir_idx);
}
next_level_dirs.clear();
}
}
if self.options.show_sizes || !self.options.trim_root {
while let Some(child_idx) = self.next_child(0) {
out_blines.push(child_idx);
}
}
Some(out_blines)
}
fn trim_excess(&mut self, out_blines: &[usize]) {
let mut count = 1;
let trim_root = self.options.trim_root && !self.options.show_sizes;
for idx in out_blines[1..].iter() {
if self.blines[*idx].has_match {
count += 1;
let parent_idx = self.blines[*idx].parent_idx;
self.blines[parent_idx].nb_kept_children += 1;
}
}
let mut remove_queue: BinaryHeap<SortableBLineIdx> = BinaryHeap::new();
for idx in out_blines[1..].iter() {
let bline = &self.blines[*idx];
if bline.has_match && bline.nb_kept_children == 0 && (bline.depth > 1 || trim_root)
{
debug!("in list: {:?} score: {}", &bline.path, bline.score);
remove_queue.push(SortableBLineIdx {
idx: *idx,
score: bline.score,
});
}
}
debug!(
"Trimming: we have {} lines for a goal of {}",
count, self.targeted_size
);
while count > self.targeted_size {
if let Some(sli) = remove_queue.pop() {
debug!("removing {:?}", &self.blines[sli.idx].path);
self.blines[sli.idx].has_match = false;
let parent_idx = self.blines[sli.idx].parent_idx;
let mut parent = &mut self.blines[parent_idx];
parent.nb_kept_children -= 1;
parent.next_child_idx -= 1; if parent.nb_kept_children == 0 {
remove_queue.push(SortableBLineIdx {
idx: parent_idx,
score: parent.score,
});
}
count -= 1;
} else {
debug!("trimming prematurely interrupted");
break;
}
}
}
fn take(&mut self, out_blines: &[usize]) -> Tree {
let mut lines: Vec<TreeLine> = Vec::new();
for idx in out_blines.iter() {
if self.blines[*idx].has_match {
if self.blines[*idx].file_type.is_dir() && self.blines[*idx].children.is_none() {
self.load_children(*idx);
}
lines.push(self.blines[*idx].to_tree_line());
}
}
let mut tree = Tree {
lines: lines.into_boxed_slice(),
selection: 0,
options: self.options.clone(),
scroll: 0,
nb_gitignored: self.nb_gitignored,
};
tree.after_lines_changed();
if self.options.show_sizes {
tree.fetch_file_sizes(); }
tree
}
pub fn build(mut self, task_lifetime: &TaskLifetime) -> Option<Tree> {
debug!("start building with pattern {}", self.options.pattern);
match self.gather_lines(task_lifetime) {
Some(out_blines) => {
self.trim_excess(&out_blines);
Some(self.take(&out_blines))
}
None => None, }
}
}