use std::borrow::Borrow;
use std::collections::{BTreeSet, VecDeque};
use std::fs::read_dir;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::{Context, Result};
use crate::app::TabSettings;
use crate::io::git;
use crate::modes::{is_not_hidden, path_is_video, FileInfo, FileKind, FilterKind, SortKind, Users};
use crate::{impl_content, impl_index_to_index, impl_selectable, log_info};
pub struct Directory {
pub path: Arc<Path>,
pub content: Vec<FileInfo>,
pub index: usize,
used_space: u64,
}
impl Directory {
pub fn new(path: &Path, users: &Users, filter: &FilterKind, show_hidden: bool) -> Result<Self> {
let path = Arc::from(path);
let mut content = Self::files(&path, show_hidden, filter, users)?;
let sort_kind = SortKind::default();
sort_kind.sort(&mut content);
let index: usize = 0;
let used_space = get_used_space(&content);
Ok(Self {
path,
content,
index,
used_space,
})
}
pub fn change_directory(
&mut self,
path: &Path,
settings: &TabSettings,
users: &Users,
) -> Result<()> {
self.content = Self::files(path, settings.show_hidden, &settings.filter, users)?;
settings.sort_kind.sort(&mut self.content);
self.index = 0;
self.used_space = get_used_space(&self.content);
self.path = Arc::from(path);
Ok(())
}
fn files(
path: &Path,
show_hidden: bool,
filter_kind: &FilterKind,
users: &Users,
) -> Result<Vec<FileInfo>> {
let mut files: Vec<FileInfo> = Self::create_dot_dotdot(path, users)?;
if let Some(true_files) = files_collection(path, users, show_hidden, filter_kind, false) {
files.extend(true_files);
}
Ok(files)
}
fn create_dot_dotdot(path: &Path, users: &Users) -> Result<Vec<FileInfo>> {
let current = FileInfo::from_path_with_name(path, ".", users)?;
let Some(parent) = path.parent() else {
return Ok(vec![current]);
};
let parent = FileInfo::from_path_with_name(parent, "..", users)?;
Ok(vec![current, parent])
}
pub fn sort(&mut self, sort_kind: &SortKind) {
sort_kind.sort(&mut self.content)
}
pub fn owner_column_width(&self) -> usize {
let owner_size_btreeset: BTreeSet<usize> =
self.iter().map(|file| file.owner.len()).collect();
*owner_size_btreeset.iter().next_back().unwrap_or(&1)
}
pub fn group_column_width(&self) -> usize {
let group_size_btreeset: BTreeSet<usize> =
self.iter().map(|file| file.group.len()).collect();
*group_size_btreeset.iter().next_back().unwrap_or(&1)
}
pub fn select_index(&mut self, index: usize) {
if index < self.content.len() {
self.index = index;
}
}
pub fn reset_files(&mut self, settings: &TabSettings, users: &Users) -> Result<()> {
self.content = Self::files(&self.path, settings.show_hidden, &settings.filter, users)?;
self.sort(&settings.sort_kind);
self.index = 0;
Ok(())
}
pub fn is_selected_dir(&self) -> Result<bool> {
let fileinfo = self.selected().context("")?;
match fileinfo.file_kind {
FileKind::Directory => Ok(true),
FileKind::SymbolicLink(true) => {
let dest = std::fs::read_link(&fileinfo.path).unwrap_or_default();
Ok(dest.is_dir())
}
_ => Ok(false),
}
}
pub fn used_space(&self) -> String {
human_size(self.used_space)
}
pub fn git_string(&self) -> Result<String> {
git(&self.path)
}
#[inline]
pub fn iter(&self) -> std::slice::Iter<'_, FileInfo> {
self.content.iter()
}
#[inline]
pub fn enumerate(&self) -> Enumerate<std::slice::Iter<'_, FileInfo>> {
self.iter().enumerate()
}
fn find_jump_index(&self, jump_target: &Path) -> Option<usize> {
self.content
.iter()
.position(|file| <Arc<Path> as Borrow<Path>>::borrow(&file.path) == jump_target)
}
pub fn select_file(&mut self, jump_target: &Path) -> usize {
let index = self.find_jump_index(jump_target).unwrap_or_default();
self.select_index(index);
index
}
pub fn paths(&self) -> Vec<&Path> {
self.content
.iter()
.map(|fileinfo| fileinfo.path.borrow())
.collect()
}
pub fn is_dotdot_selected(&self) -> bool {
let Some(selected) = &self.selected() else {
return false;
};
let Some(parent) = self.path.parent() else {
return false;
};
selected.path.as_ref() == parent
}
pub fn videos(&self) -> VecDeque<PathBuf> {
self.content()
.iter()
.map(|f| f.path.to_path_buf())
.filter(|p| path_is_video(p))
.collect()
}
}
impl_index_to_index!(FileInfo, Directory);
impl_content!(Directory, FileInfo);
fn get_used_space(files: &[FileInfo]) -> u64 {
files
.iter()
.filter(|f| !f.is_dir())
.map(|f| f.true_size)
.sum()
}
pub fn files_collection(
path: &Path,
users: &Users,
show_hidden: bool,
filter_kind: &FilterKind,
keep_dir: bool,
) -> Option<Vec<FileInfo>> {
match read_dir(path) {
Ok(read_dir) => Some(
read_dir
.filter_map(|direntry| direntry.ok())
.filter(|direntry| show_hidden || is_not_hidden(direntry).unwrap_or(true))
.filter_map(|direntry| FileInfo::from_direntry(&direntry, users).ok())
.filter(|fileinfo| filter_kind.filter_by(fileinfo, keep_dir))
.collect(),
),
Err(error) => {
log_info!("Couldn't read path {path} - {error}", path = path.display(),);
None
}
}
}
const SIZES: [&str; 9] = ["B", "k", "M", "G", "T", "P", "E", "Z", "Y"];
#[inline]
pub fn human_size(bytes: u64) -> String {
let mut factor = 0;
let mut size = bytes as f64;
while size >= 1000.0 && factor < SIZES.len() - 1 {
size /= 1000.0;
factor += 1;
}
if size < 9.5 && factor > 0 {
format!("{size:.1}{unit}", unit = SIZES[factor])
} else {
format!(
"{size:>3}{unit}",
size = size.round() as u64,
unit = SIZES[factor]
)
}
}