use std::io::{self, Write};
use std::mem::MaybeUninit;
use std::path::PathBuf;
use std::vec::IntoIter as VecIntoIter;
use ansi_term::Style;
use scoped_threadpool::Pool;
use crate::fs::{Dir, File};
use crate::fs::dir_action::RecurseOptions;
use crate::fs::feature::git::GitCache;
use crate::fs::feature::xattr::Attribute;
use crate::fs::fields::SecurityContextType;
use crate::fs::filter::FileFilter;
use crate::output::cell::TextCell;
use crate::output::file_name::Options as FileStyle;
use crate::output::table::{Table, Options as TableOptions, Row as TableRow};
use crate::output::tree::{TreeTrunk, TreeParams, TreeDepth};
use crate::theme::Theme;
#[derive(PartialEq, Eq, Debug)]
pub struct Options {
pub table: Option<TableOptions>,
pub header: bool,
pub xattr: bool,
pub secattr: bool,
}
pub struct Render<'a> {
pub dir: Option<&'a Dir>,
pub files: Vec<File<'a>>,
pub theme: &'a Theme,
pub file_style: &'a FileStyle,
pub opts: &'a Options,
pub recurse: Option<RecurseOptions>,
pub filter: &'a FileFilter,
pub git_ignoring: bool,
pub git: Option<&'a GitCache>,
}
struct Egg<'a> {
table_row: Option<TableRow>,
xattrs: &'a [Attribute],
errors: Vec<(io::Error, Option<PathBuf>)>,
dir: Option<Dir>,
file: &'a File<'a>,
}
impl<'a> AsRef<File<'a>> for Egg<'a> {
fn as_ref(&self) -> &File<'a> {
self.file
}
}
impl<'a> Render<'a> {
pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
let n_cpus = match num_cpus::get() as u32 {
0 => 1,
n => n,
};
let mut pool = Pool::new(n_cpus);
let mut rows = Vec::new();
if let Some(ref table) = self.opts.table {
match (self.git, self.dir) {
(Some(g), Some(d)) => if ! g.has_anything_for(&d.path) { self.git = None },
(Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { self.git = None },
(None, _) => {},
}
let mut table = Table::new(table, self.git, self.theme);
if self.opts.header {
let header = table.header_row();
table.add_widths(&header);
rows.push(self.render_header(header));
}
let mut table = Some(table);
self.add_files_to_table(&mut pool, &mut table, &mut rows, &self.files, TreeDepth::root());
for row in self.iterate_with_table(table.unwrap(), rows) {
writeln!(w, "{}", row.strings())?
}
}
else {
self.add_files_to_table(&mut pool, &mut None, &mut rows, &self.files, TreeDepth::root());
for row in self.iterate(rows) {
writeln!(w, "{}", row.strings())?
}
}
Ok(())
}
pub fn show_xattr_hint(&self, file: &File<'_>) -> bool {
let xattr_count = file.extended_attributes.len();
let selinux_ctx_shown = self.opts.secattr && match file.security_context().context {
SecurityContextType::SELinux(_) => true,
SecurityContextType::None => false,
};
xattr_count > 1 || (xattr_count == 1 && !selinux_ctx_shown)
}
fn add_files_to_table<'dir>(&self, pool: &mut Pool, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &[File<'dir>], depth: TreeDepth) {
use std::sync::{Arc, Mutex};
use crate::fs::feature::xattr;
let mut file_eggs = (0..src.len()).map(|_| MaybeUninit::uninit()).collect::<Vec<_>>();
pool.scoped(|scoped| {
let file_eggs = Arc::new(Mutex::new(&mut file_eggs));
let table = table.as_ref();
for (idx, file) in src.iter().enumerate() {
let file_eggs = Arc::clone(&file_eggs);
scoped.execute(move || {
let mut errors = Vec::new();
let xattrs: &[Attribute] = if xattr::ENABLED && self.opts.xattr {
&file.extended_attributes
} else {
&[]
};
let table_row = table.as_ref()
.map(|t| t.row_for_file(file, self.show_xattr_hint(file)));
let mut dir = None;
if let Some(r) = self.recurse {
if file.is_directory() && r.tree && ! r.is_too_deep(depth.0) {
match file.to_dir() {
Ok(d) => {
dir = Some(d);
}
Err(e) => {
errors.push((e, None));
}
}
}
};
let egg = Egg { table_row, xattrs, errors, dir, file };
unsafe { std::ptr::write(file_eggs.lock().unwrap()[idx].as_mut_ptr(), egg) }
});
}
});
let mut file_eggs = unsafe { std::mem::transmute::<_, Vec<Egg<'_>>>(file_eggs) };
self.filter.sort_files(&mut file_eggs);
for (tree_params, egg) in depth.iterate_over(file_eggs.into_iter()) {
let mut files = Vec::new();
let mut errors = egg.errors;
if let (Some(ref mut t), Some(row)) = (table.as_mut(), egg.table_row.as_ref()) {
t.add_widths(row);
}
let file_name = self.file_style.for_file(egg.file, self.theme)
.with_link_paths()
.paint()
.promote();
let row = Row {
tree: tree_params,
cells: egg.table_row,
name: file_name,
};
rows.push(row);
if let Some(ref dir) = egg.dir {
for file_to_add in dir.files(self.filter.dot_filter, self.git, self.git_ignoring) {
match file_to_add {
Ok(f) => {
files.push(f);
}
Err((path, e)) => {
errors.push((e, Some(path)));
}
}
}
self.filter.filter_child_files(&mut files);
if ! files.is_empty() {
for xattr in egg.xattrs {
rows.push(self.render_xattr(xattr, TreeParams::new(depth.deeper(), false)));
}
for (error, path) in errors {
rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), false), path));
}
self.add_files_to_table(pool, table, rows, &files, depth.deeper());
continue;
}
}
let count = egg.xattrs.len();
for (index, xattr) in egg.xattrs.into_iter().enumerate() {
let params = TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1);
let r = self.render_xattr(xattr, params);
rows.push(r);
}
let count = errors.len();
for (index, (error, path)) in errors.into_iter().enumerate() {
let params = TreeParams::new(depth.deeper(), index == count - 1);
let r = self.render_error(&error, params, path);
rows.push(r);
}
}
}
pub fn render_header(&self, header: TableRow) -> Row {
Row {
tree: TreeParams::new(TreeDepth::root(), false),
cells: Some(header),
name: TextCell::paint_str(self.theme.ui.header, "Name"),
}
}
fn render_error(&self, error: &io::Error, tree: TreeParams, path: Option<PathBuf>) -> Row {
use crate::output::file_name::Colours;
let error_message = if let Some(path) = path {
format!("<{}: {}>", path.display(), error)
} else {
format!("<{}>", error)
};
let name = TextCell::paint(self.theme.broken_symlink(), error_message);
Row { cells: None, name, tree }
}
fn render_xattr(&self, xattr: &Attribute, tree: TreeParams) -> Row {
let name = TextCell::paint(self.theme.ui.perms.attribute, format!("{}=\"{}\"", xattr.name, xattr.value));
Row { cells: None, name, tree }
}
pub fn render_file(&self, cells: TableRow, name: TextCell, tree: TreeParams) -> Row {
Row { cells: Some(cells), name, tree }
}
pub fn iterate_with_table(&'a self, table: Table<'a>, rows: Vec<Row>) -> TableIter<'a> {
TableIter {
tree_trunk: TreeTrunk::default(),
total_width: table.widths().total(),
table,
inner: rows.into_iter(),
tree_style: self.theme.ui.punctuation,
}
}
pub fn iterate(&'a self, rows: Vec<Row>) -> Iter {
Iter {
tree_trunk: TreeTrunk::default(),
inner: rows.into_iter(),
tree_style: self.theme.ui.punctuation,
}
}
}
pub struct Row {
pub cells: Option<TableRow>,
pub name: TextCell,
pub tree: TreeParams,
}
pub struct TableIter<'a> {
inner: VecIntoIter<Row>,
table: Table<'a>,
total_width: usize,
tree_style: Style,
tree_trunk: TreeTrunk,
}
impl<'a> Iterator for TableIter<'a> {
type Item = TextCell;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|row| {
let mut cell =
if let Some(cells) = row.cells {
self.table.render(cells)
}
else {
let mut cell = TextCell::default();
cell.add_spaces(self.total_width);
cell
};
for tree_part in self.tree_trunk.new_row(row.tree) {
cell.push(self.tree_style.paint(tree_part.ascii_art()), 4);
}
if ! row.tree.is_at_root() {
cell.add_spaces(1);
}
cell.append(row.name);
cell
})
}
}
pub struct Iter {
tree_trunk: TreeTrunk,
tree_style: Style,
inner: VecIntoIter<Row>,
}
impl Iterator for Iter {
type Item = TextCell;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|row| {
let mut cell = TextCell::default();
for tree_part in self.tree_trunk.new_row(row.tree) {
cell.push(self.tree_style.paint(tree_part.ascii_art()), 4);
}
if ! row.tree.is_at_root() {
cell.add_spaces(1);
}
cell.append(row.name);
cell
})
}
}