1
0
mirror of https://github.com/bobwen-dev/hunter synced 2025-04-12 00:55:41 +02:00
hunter/src/files.rs
2020-02-05 18:55:09 +01:00

1165 lines
34 KiB
Rust

use std::cmp::Ord;
use std::collections::{HashMap, HashSet};
use std::ops::Index;
use std::fs::Metadata;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, RwLock};
use std::sync::mpsc::Sender;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
use lscolors::LsColors;
use tree_magic;
use users::{get_current_username,
get_current_groupname,
get_user_by_uid,
get_group_by_gid};
use chrono::TimeZone;
use failure::Error;
use rayon::{ThreadPool, ThreadPoolBuilder};
use alphanumeric_sort::compare_str;
use mime_guess;
use rayon::prelude::*;
use pathbuftools::PathBufTools;
use async_value::{Async, Stale, StopIter};
use crate::fail::{HResult, HError, ErrorLog};
use crate::dirty::{AsyncDirtyBit, DirtyBit, Dirtyable};
use crate::widget::Events;
use crate::icon::Icons;
use crate::fscache::FsEvent;
lazy_static! {
static ref COLORS: LsColors = LsColors::from_env().unwrap_or_default();
static ref TAGS: RwLock<(bool, Vec<PathBuf>)> = RwLock::new((false, vec![]));
static ref ICONS: Icons = Icons::new();
}
fn make_pool(sender: Option<Sender<Events>>) -> ThreadPool {
let sender = Arc::new(Mutex::new(sender));
ThreadPoolBuilder::new()
.num_threads(8)
.exit_handler(move |thread_num| {
if thread_num == 0 {
if let Ok(lock) = sender.lock() {
if let Some(sender) = lock.as_ref() {
sender.send(Events::WidgetReady).ok();
}
}
}
})
.build()
.expect("Failed to create thread pool")
}
pub fn load_tags() -> HResult<()> {
std::thread::spawn(|| -> HResult<()> {
let tag_path = crate::paths::tagfile_path()?;
if !tag_path.exists() {
import_tags().log();
}
let tags = std::fs::read_to_string(tag_path)?;
let mut tags = tags.lines()
.map(|f|
PathBuf::from(f))
.collect::<Vec<PathBuf>>();
let mut tag_lock = TAGS.write()?;
tag_lock.0 = true;
tag_lock.1.append(&mut tags);
Ok(())
});
Ok(())
}
pub fn import_tags() -> HResult<()> {
let mut ranger_tags = crate::paths::ranger_path()?;
ranger_tags.push("tagged");
if ranger_tags.exists() {
let tag_path = crate::paths::tagfile_path()?;
std::fs::copy(ranger_tags, tag_path)?;
}
Ok(())
}
pub fn check_tag(path: &PathBuf) -> HResult<bool> {
tags_loaded()?;
let tagged = TAGS.read()?.1.contains(path);
Ok(tagged)
}
pub fn tags_loaded() -> HResult<()> {
let loaded = TAGS.read()?.0;
if loaded { Ok(()) }
else { HError::tags_not_loaded() }
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct RefreshPackage {
pub new_files: Option<Vec<File>>,
pub new_buffer: Option<Vec<String>>,
pub new_len: usize,
}
impl RefreshPackage {
fn new(mut files: Files,
old_buffer: Vec<String>,
events: Vec<FsEvent>,
render_fn: impl Fn(&File) -> String) -> RefreshPackage {
use FsEvent::*;
// If there is only a placeholder at this point, remove it now
if files.len() == 1 {
files.remove_placeholder();
}
//To preallocate collections
let event_count = events.len();
// Need at least one copy for the hashmaps
let static_files = files.clone();
// Optimization to speed up access to array
let file_pos_map: HashMap<&File, usize> = static_files
.files
.iter()
.enumerate()
.map(|(i, file)| (file, i))
.collect();
// Need to know which line of the ListView buffer belongs to which file
let list_pos_map: HashMap<&File, usize> = static_files
.iter_files()
.enumerate()
.take_while(|&(i, _)| i < old_buffer.len())
.map(|(i, file)| (file, i))
.collect();
// Save new files to add them all at once later
let mut new_files = Vec::with_capacity(event_count);
// Files that need rerendering to make all changes visible (size, etc.)
let mut changed_files = HashSet::with_capacity(event_count);
// Save deletions to delete them efficiently later
let mut deleted_files = HashSet::with_capacity(event_count);
for event in events.into_iter() {
match event {
Create(mut file) => {
let dirty_meta = files.dirty_meta.clone();
file.dirty_meta = Some(dirty_meta);
file.meta_sync().log();
new_files.push(file);
}
Change(file) => {
if let Some(&fpos) = file_pos_map.get(&file) {
files.files[fpos].meta_sync().log();
changed_files.insert(file);
}
}
Rename(old, new) => {
if let Some(&fpos) = file_pos_map.get(&old) {
files.files[fpos].rename(&new.path).log();
files.files[fpos].meta_sync().log();
}
}
Remove(file) => {
if let Some(_) = file_pos_map.get(&file) {
deleted_files.insert(file);
}
}
}
}
if deleted_files.len() > 0 {
files.files.retain(|file| !deleted_files.contains(file));
}
// Finally add all new files
files.files.extend(new_files);
// Files added, removed, renamed to hidden, etc...
files.recalculate_len();
files.sort();
// Prerender new buffer in current thread
let mut old_buffer = old_buffer;
let new_buffer = files.iter_files()
.map(|file| {
match list_pos_map.get(&file) {
Some(&old_pos) =>
match changed_files.contains(&file) {
true => render_fn(&file),
false => std::mem::take(&mut old_buffer[old_pos])
}
None => render_fn(&file)
}
}).collect();
// Need to unpack this to prevent issue with recursive Files type
// Also, if no files remain add placeholder and set len
let (files, new_len, new_buffer) = if files.len() > 0 {
(files.files, files.len, new_buffer)
} else {
let placeholder = File::new_placeholder(&files.directory.path).unwrap();
let buffer = vec![render_fn(&placeholder)];
files.files.push(placeholder);
(files.files, 1, buffer)
};
RefreshPackage {
new_files: Some(files),
new_buffer: Some(new_buffer),
new_len: new_len
}
}
}
#[derive(Derivative)]
#[derivative(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Files {
pub directory: File,
pub files: Vec<File>,
pub len: usize,
#[derivative(Debug="ignore")]
#[derivative(PartialEq="ignore")]
#[derivative(Hash="ignore")]
pub pending_events: Arc<RwLock<Vec<FsEvent>>>,
#[derivative(Debug="ignore")]
#[derivative(PartialEq="ignore")]
#[derivative(Hash="ignore")]
pub refresh: Option<Async<RefreshPackage>>,
pub meta_upto: Option<usize>,
pub sort: SortBy,
pub dirs_first: bool,
pub reverse: bool,
pub show_hidden: bool,
pub filter: Option<String>,
pub filter_selected: bool,
pub dirty: DirtyBit,
pub dirty_meta: AsyncDirtyBit,
}
impl Index<usize> for Files {
type Output = File;
fn index(&self, pos: usize) -> &File {
&self.files[pos]
}
}
impl Dirtyable for Files {
fn is_dirty(&self) -> bool {
self.dirty.is_dirty()
}
fn set_dirty(&mut self) {
self.dirty.set_dirty();
}
fn set_clean(&mut self) {
self.dirty.set_clean();
}
}
use std::default::Default;
impl Default for Files {
fn default() -> Files {
Files {
directory: File::new_placeholder(Path::new("")).unwrap(),
files: vec![],
len: 0,
pending_events: Arc::new(RwLock::new(vec![])),
refresh: None,
meta_upto: None,
sort: SortBy::Name,
dirs_first: true,
reverse: false,
show_hidden: true,
filter: None,
filter_selected: false,
dirty: DirtyBit::new(),
dirty_meta: AsyncDirtyBit::new(),
}
}
}
impl Files {
pub fn new_from_path(path: &Path) -> HResult<Files> {
let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect();
let dirty_meta = AsyncDirtyBit::new();
let tags = &TAGS.read().ok()?.1;
let files: Vec<_> = direntries?
.iter()
.map(|file| {
let name = file.file_name();
let name = name.to_string_lossy();
let path = file.path();
let mut file = File::new(&name,
path,
Some(dirty_meta.clone()));
file.set_tag_status(&tags);
Some(file)
})
.collect();
let len = files.len();
let mut files = Files::default();
files.directory = File::new_from_path(&path, None)?;
files.len = len;
files.dirty_meta = dirty_meta;
Ok(files)
}
pub fn new_from_path_cancellable(path: &Path,
stale: Stale)
-> HResult<Files> {
let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect();
let dirty = DirtyBit::new();
let dirty_meta = AsyncDirtyBit::new();
let files: Vec<_> = direntries?
.into_iter()
.stop_stale(stale.clone())
.par_bridge()
.map(|file| {
let file = File::new_from_direntry(file,
Some(dirty_meta.clone()));
file
})
.collect();
if stale.is_stale()? {
return Err(crate::fail::HError::StalePreviewError {
file: path.to_string_lossy().to_string()
})?;
}
let len = files.len();
let files = Files {
directory: File::new_from_path(&path, None)?,
files: files,
len: len,
pending_events: Arc::new(RwLock::new(vec![])),
refresh: None,
meta_upto: None,
sort: SortBy::Name,
dirs_first: true,
reverse: false,
show_hidden: true,
filter: None,
filter_selected: false,
dirty: dirty,
dirty_meta: dirty_meta,
};
Ok(files)
}
pub fn recalculate_len(&mut self) {
self.len = self.par_iter_files().count();
}
pub fn get_file_mut(&mut self, index: usize) -> Option<&mut File> {
self.par_iter_files_mut()
.find_first(|(i, _)| *i == index)
.map(|(_, f)| f)
}
pub fn par_iter_files(&self) -> impl ParallelIterator<Item=&File> {
let filter = self.filter.clone();
let filter_selected = self.filter_selected;
let show_hidden = self.show_hidden;
self.files
.par_iter()
.filter(move |f|
f.kind == Kind::Placeholder ||
!(filter.is_some() &&
!f.name.contains(filter.as_ref().unwrap())) &&
(!filter_selected || f.selected))
.filter(move |f| !(!show_hidden && f.hidden))
}
pub fn par_iter_files_mut(&mut self) -> impl ParallelIterator<Item=(usize,
&mut File)> {
let filter = self.filter.clone();
let filter_selected = self.filter_selected;
let show_hidden = self.show_hidden;
self.files
.par_iter_mut()
.enumerate()
.filter(move |(_,f)|
f.kind == Kind::Placeholder ||
!(filter.is_some() &&
!f.name.contains(filter.as_ref().unwrap())) &&
(!filter_selected || f.selected))
.filter(move |(_,f)| !(!show_hidden && f.hidden))
}
pub fn iter_files(&self) -> impl Iterator<Item=&File> {
let filter = self.filter.clone();
let filter_selected = self.filter_selected;
let show_hidden = self.show_hidden;
self.files
.iter()
.filter(move |f|
f.kind == Kind::Placeholder ||
!(filter.is_some() &&
!f.name.contains(filter.as_ref().unwrap())) &&
(!filter_selected || f.selected))
.filter(move |f| !(!show_hidden && f.hidden))
}
pub fn iter_files_mut(&mut self) -> impl Iterator<Item=&mut File> {
let filter = self.filter.clone();
let filter_selected = self.filter_selected;
let show_hidden = self.show_hidden;
self.files
.iter_mut()
.filter(move |f|
f.kind == Kind::Placeholder ||
!(filter.is_some() &&
!f.name.contains(filter.as_ref().unwrap())) &&
(!filter_selected || f.selected))
.filter(move |f| !(!show_hidden && f.hidden))
}
#[allow(trivial_bounds)]
pub fn into_iter_files(self) -> impl Iterator<Item=File> {
let filter = self.filter;
let filter_selected = self.filter_selected;
let show_hidden = self.show_hidden;
self.files
.into_iter()
.filter(move |f|
f.kind == Kind::Placeholder ||
!(filter.is_some() &&
!f.name.contains(filter.as_ref().unwrap())) &&
(!filter_selected || f.selected))
.filter(move |f| !(!show_hidden && f.name.starts_with(".")))
}
pub fn sort(&mut self) {
use std::cmp::Ordering::*;
let dirs_first = self.dirs_first;
match self.sort {
SortBy::Name => self
.files
.par_sort_unstable_by(|a, b| {
if dirs_first {
match (a.is_dir(), b.is_dir()) {
(true, false) => Less,
(false, true) => Greater,
_ => compare_str(&a.name, &b.name),
}
} else {
compare_str(&a.name, &b.name)
}
}),
SortBy::Size => {
if self.meta_upto < Some(self.len()) {
self.meta_all_sync().log();
}
self.files.par_sort_unstable_by(|a, b| {
if dirs_first {
match (a.is_dir(), b.is_dir()) {
(true, false) => return Less,
(false, true) => return Greater,
_ => {}
}
}
match (a.meta(), b.meta()) {
(Some(a_meta), Some(b_meta)) => {
match a_meta.size() == b_meta.size() {
true => compare_str(&b.name, &a.name),
false => b_meta.size()
.cmp(&a_meta.size())
}
}
_ => Equal
}
})
}
SortBy::MTime => {
if self.meta_upto < Some(self.len()) {
self.meta_all_sync().log();
}
self.files.par_sort_unstable_by(|a, b| {
if dirs_first {
match (a.is_dir(), b.is_dir()) {
(true, false) => return Less,
(false, true) => return Greater,
_ => {}
}
}
match (a.meta(), b.meta()) {
(Some(a_meta), Some(b_meta)) => {
match a_meta.mtime() == b_meta.mtime() {
true => compare_str(&b.name, &a.name),
false => b_meta.mtime()
.cmp(&a_meta.mtime())
}
}
_ => Equal
}
})
}
}
}
pub fn cycle_sort(&mut self) {
self.sort = match self.sort {
SortBy::Name => SortBy::Size,
SortBy::Size => SortBy::MTime,
SortBy::MTime => SortBy::Name,
};
}
pub fn reverse_sort(&mut self) {
self.reverse = !self.reverse
}
pub fn toggle_hidden(&mut self) {
self.show_hidden = !self.show_hidden;
self.set_dirty();
if self.show_hidden == true && self.len() > 1 {
self.remove_placeholder();
} else {
// avoid doing this twice, since remove_placeholder() does it too
self.recalculate_len();
}
}
fn remove_placeholder(&mut self) {
let dirpath = self.directory.path.clone();
self.find_file_with_path(&dirpath).cloned()
.map(|placeholder| {
self.files.remove_item(&placeholder);
self.recalculate_len();
});
}
pub fn ready_to_refresh(&self) -> HResult<bool> {
let pending = self.pending_events.read()?.len();
let running = self.refresh.is_some();
Ok(pending > 0 && !running)
}
pub fn get_refresh(&mut self) -> HResult<Option<RefreshPackage>> {
if let Some(mut refresh) = self.refresh.take() {
if refresh.is_ready() {
refresh.pull_async()?;
let mut refresh = refresh.value?;
self.files = refresh.new_files.take()?;
if refresh.new_len != self.len() {
self.len = refresh.new_len;
}
return Ok(Some(refresh));
} else {
self.refresh.replace(refresh);
}
}
return Ok(None)
}
pub fn process_fs_events(&mut self,
buffer: Vec<String>,
sender: Sender<Events>,
render_fn: impl Fn(&File) -> String + Send + 'static)
-> HResult<()> {
let pending = self.pending_events.read()?.len();
if pending > 0 {
let events = self.pending_events
.write()?
.drain(0..pending)
.collect::<Vec<_>>();
let files = self.clone();
let mut refresh = Async::new(move |_| {
let refresh = RefreshPackage::new(files,
buffer,
events,
render_fn);
Ok(refresh)
});
refresh.on_ready(move |_,_| {
Ok(sender.send(Events::WidgetReady)?)
})?;
refresh.run()?;
self.refresh = Some(refresh);
}
Ok(())
}
pub fn path_in_here(&self, path: &Path) -> HResult<bool> {
let dir = &self.directory.path;
let path = if path.is_dir() { path } else { path.parent().unwrap() };
if dir == path {
Ok(true)
} else {
HError::wrong_directory(path.into(), dir.to_path_buf())?
}
}
pub fn find_file_with_name(&self, name: &str) -> Option<&File> {
self.iter_files()
.find(|f| f.name.to_lowercase().contains(name))
}
pub fn find_file_with_path(&mut self, path: &Path) -> Option<&mut File> {
self.iter_files_mut().find(|file| file.path == path)
}
pub fn meta_all_sync(&mut self) -> HResult<()> {
let same = Mutex::new(true);
self.iter_files_mut()
.par_bridge()
.for_each(|f| {
if !f.meta_processed {
f.meta_sync().log();
same.lock()
.map(|mut t| *t = false)
.map_err(HError::from)
.log();
}
});
if !*same.lock()? {
self.set_dirty();
}
Ok(())
}
pub fn set_filter(&mut self, filter: Option<String>) {
self.filter = filter;
// Do this first, so we know len() == 0 needs a placeholder
self.remove_placeholder();
if self.len() == 0 {
let placeholder = File::new_placeholder(&self.directory.path).unwrap();
self.files.push(placeholder);
self.len = 1;
}
self.set_dirty();
}
pub fn get_filter(&self) -> Option<String> {
self.filter.clone()
}
pub fn toggle_filter_selected(&mut self) {
self.filter_selected = !self.filter_selected;
}
pub fn len(&self) -> usize {
self.len
}
pub fn get_selected(&self) -> impl Iterator<Item=&File> {
self.iter_files()
.filter(|f| f.is_selected())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Kind {
Directory,
File,
Placeholder
}
impl std::fmt::Display for SortBy {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let text = match self {
SortBy::Name => "name",
SortBy::Size => "size",
SortBy::MTime => "mtime",
};
write!(formatter, "{}", text)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum SortBy {
Name,
Size,
MTime,
}
impl PartialEq for File {
fn eq(&self, other: &File) -> bool {
if self.path == other.path {
true
} else {
false
}
}
}
impl Hash for File {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.path.hash(state);
}
}
impl Eq for File {}
impl std::fmt::Debug for File {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(formatter, "{:?}", self.path)
}
}
impl std::default::Default for File {
fn default() -> File {
File::new_placeholder(Path::new("")).unwrap()
}
}
#[derive(Clone)]
pub struct File {
pub name: String,
pub path: PathBuf,
pub hidden: bool,
pub kind: Kind,
pub dirsize: Option<usize>,
pub target: Option<PathBuf>,
pub color: Option<lscolors::Color>,
pub meta: Option<Metadata>,
pub dirty_meta: Option<AsyncDirtyBit>,
pub meta_processed: bool,
pub selected: bool,
pub tag: Option<bool>
}
impl File {
pub fn new(
name: &str,
path: PathBuf,
dirty_meta: Option<AsyncDirtyBit>) -> File {
let hidden = name.starts_with(".");
File {
name: name.to_string(),
hidden: hidden,
kind: if path.is_dir() { Kind::Directory } else { Kind::File },
path: path,
dirsize: None,
target: None,
meta: None,
meta_processed: false,
dirty_meta: dirty_meta,
color: None,
selected: false,
tag: None,
}
}
pub fn new_with_stale(name: &str,
path: PathBuf,
dirty_meta: Option<AsyncDirtyBit>) -> File {
let hidden = name.starts_with(".");
File {
name: name.to_string(),
hidden: hidden,
kind: if path.is_dir() { Kind::Directory } else { Kind::File },
path: path,
dirsize: None,
target: None,
meta: None,
meta_processed: false,
dirty_meta: dirty_meta,
color: None,
selected: false,
tag: None,
}
}
pub fn new_from_direntry(direntry: std::fs::DirEntry,
dirty_meta: Option<AsyncDirtyBit>) -> File {
let path = direntry.path();
let name = direntry.file_name()
.to_string_lossy()
.to_string();
let hidden = name.chars().nth(0) == Some('.');
let kind = match direntry.file_type() {
Ok(ftype) => match ftype.is_file() {
true => Kind::File,
false => Kind::Directory
}
_ => Kind::Placeholder
};
File {
name: name,
hidden: hidden,
kind: kind,
path: path,
dirsize: None,
target: None,
meta: None,
meta_processed: false,
dirty_meta: dirty_meta,
color: None,
selected: false,
tag: None,
}
}
pub fn new_from_path(path: &Path,
dirty_meta: Option<AsyncDirtyBit>) -> HResult<File> {
let pathbuf = path.to_path_buf();
let name = path
.file_name()
.map(|name| name.to_string_lossy().to_string())
.unwrap_or("/".to_string());
Ok(File::new(&name, pathbuf, dirty_meta))
}
pub fn new_placeholder(path: &Path) -> Result<File, Error> {
let mut file = File::new_from_path(path, None)?;
file.name = "<empty>".to_string();
file.kind = Kind::Placeholder;
Ok(file)
}
pub fn rename(&mut self, new_path: &Path) -> HResult<()> {
self.name = new_path.file_name()?.to_string_lossy().to_string();
self.path = new_path.into();
Ok(())
}
pub fn meta_sync(&mut self) -> HResult<()> {
let meta = std::fs::symlink_metadata(&self.path)?;
self.meta = Some(meta);
self.process_meta().log();
if self.is_dir() {
let dirsize = std::fs::read_dir(&self.path)?.count();
self.dirsize = Some(dirsize);
}
Ok(())
}
pub fn meta(&self) -> Option<&Metadata> {
self.meta.as_ref()
}
pub fn process_meta(&mut self) -> HResult<()> {
if let Some(ref meta) = self.meta {
let color = self.get_color(&meta);
let target = if meta.file_type().is_symlink() {
self.path.read_link().ok()
} else { None };
self.color = color;
self.target = target;
self.meta_processed = true;
}
Ok(())
}
pub fn reload_meta(&mut self) -> HResult<()> {
self.meta_processed = false;
self.meta_sync()
}
fn get_color(&self, meta: &std::fs::Metadata) -> Option<lscolors::Color> {
match COLORS.style_for_path_with_metadata(&self.path, Some(&meta)) {
Some(style) => style.clone().foreground,
None => None,
}
}
pub fn calculate_size(&self) -> HResult<(u32, &str)> {
if let Some(ref dirsize) = self.dirsize {
return Ok((*dirsize as u32, ""))
}
let mut unit = 0;
let mut size = self.meta()?.size();
while size > 1024 {
size /= 1024;
unit += 1;
}
let unit = match unit {
0 => "",
1 => " KB",
2 => " MB",
3 => " GB",
4 => " TB",
5 => " wtf are you doing",
_ => "",
};
Ok((size as u32, unit))
}
pub fn get_mime(&self) -> Option<mime_guess::Mime> {
if let Some(ext) = self.path.extension() {
let mime = mime_guess::from_ext(&ext.to_string_lossy()).first();
mime
} else {
// Fix crash in tree_magic when called on non-regular file
// Also fix crash when a file doesn't exist any more
self.meta()
.and_then(|meta| {
if meta.is_file() && self.path.exists() {
let mime = tree_magic::from_filepath(&self.path);
mime::Mime::from_str(&mime).ok()
} else { None }
})
}
}
pub fn is_text(&self) -> bool {
tree_magic::match_filepath("text/plain", &self.path)
}
pub fn parent(&self) -> Option<PathBuf> {
Some(self.path.parent()?.to_path_buf())
}
pub fn parent_as_file(&self) -> HResult<File> {
let pathbuf = self.parent()?;
File::new_from_path(&pathbuf, None)
}
pub fn grand_parent(&self) -> Option<PathBuf> {
Some(self.path.parent()?.parent()?.to_path_buf())
}
pub fn grand_parent_as_file(&self) -> HResult<File> {
let pathbuf = self.grand_parent()?;
File::new_from_path(&pathbuf, None)
}
pub fn is_dir(&self) -> bool {
self.kind == Kind::Directory
}
pub fn read_dir(&self) -> HResult<Files> {
Files::new_from_path(&self.path)
}
pub fn strip_prefix(&self, base: &File) -> PathBuf {
if self == base {
return PathBuf::from("./");
}
let base_path = base.path.clone();
match self.path.strip_prefix(base_path) {
Ok(path) => PathBuf::from(path),
Err(_) => self.path.clone()
}
}
pub fn path(&self) -> PathBuf {
self.path.clone()
}
pub fn toggle_selection(&mut self) {
self.selected = !self.selected
}
pub fn is_selected(&self) -> bool {
self.selected
}
pub fn is_tagged(&self) -> HResult<bool> {
if let Some(tag) = self.tag {
return Ok(tag);
}
let tag = check_tag(&self.path)?;
Ok(tag)
}
pub fn set_tag_status(&mut self, tags: &[PathBuf]) {
match tags.contains(&self.path) {
true => self.tag = Some(true),
false => self.tag = Some(false)
}
}
pub fn toggle_tag(&mut self) -> HResult<()> {
let new_state = match self.tag {
Some(tag) => !tag,
None => {
let tag = check_tag(&self.path);
!tag?
}
};
self.tag = Some(new_state);
match new_state {
true => TAGS.write()?.1.push(self.path.clone()),
false => { TAGS.write()?.1.remove_item(&self.path); },
}
self.save_tags()?;
Ok(())
}
pub fn save_tags(&self) -> HResult<()> {
std::thread::spawn(|| -> HResult<()> {
let tagfile_path = crate::paths::tagfile_path()?;
let tags = TAGS.read()?.clone();
let tags_str = tags.1.iter().map(|p| {
let path = p.to_string_lossy().to_string();
format!("{}\n", path)
}).collect::<String>();
std::fs::write(tagfile_path, tags_str)?;
Ok(())
});
Ok(())
}
pub fn is_readable(&self) -> HResult<bool> {
let meta = self.meta()?;
let current_user = get_current_username()?.to_string_lossy().to_string();
let current_group = get_current_groupname()?.to_string_lossy().to_string();
let file_user = get_user_by_uid(meta.uid())?
.name()
.to_string_lossy()
.to_string();
let file_group = get_group_by_gid(meta.gid())?
.name()
.to_string_lossy()
.to_string();
let perms = meta.mode();
let user_readable = perms & 0o400;
let group_readable = perms & 0o040;
let other_readable = perms & 0o004;
if current_user == file_user && user_readable > 0 {
Ok(true)
} else if current_group == file_group && group_readable > 0 {
Ok(true)
} else if other_readable > 0 {
Ok(true)
} else {
Ok(false)
}
}
pub fn pretty_print_permissions(&self) -> HResult<String> {
let perms: usize = format!("{:o}", self.meta()?.mode()).parse().unwrap();
let perms: usize = perms % 800;
let perms = format!("{}", perms);
let r = format!("{}r", crate::term::color_green());
let w = format!("{}w", crate::term::color_yellow());
let x = format!("{}x", crate::term::color_red());
let n = format!("{}-", crate::term::highlight_color());
let perms = perms.chars().map(|c| match c.to_string().parse().unwrap() {
1 => format!("{}{}{}", n,n,x),
2 => format!("{}{}{}", n,w,n),
3 => format!("{}{}{}", n,w,x),
4 => format!("{}{}{}", r,n,n),
5 => format!("{}{}{}", r,n,x),
6 => format!("{}{}{}", r,w,n),
7 => format!("{}{}{}", r,w,x),
_ => format!("---")
}).collect();
Ok(perms)
}
pub fn pretty_user(&self) -> Option<String> {
if self.meta().is_none() { return None }
let uid = self.meta().unwrap().uid();
let file_user = users::get_user_by_uid(uid)?;
let cur_user = users::get_current_username()?;
let color =
if file_user.name() == cur_user {
crate::term::color_green()
} else {
crate::term::color_red() };
Some(format!("{}{}", color, file_user.name().to_string_lossy()))
}
pub fn pretty_group(&self) -> Option<String> {
if self.meta().is_none() { return None }
let gid = self.meta().unwrap().gid();
let file_group = users::get_group_by_gid(gid)?;
let cur_group = users::get_current_groupname()?;
let color =
if file_group.name() == cur_group {
crate::term::color_green()
} else {
crate::term::color_red() };
Some(format!("{}{}", color, file_group.name().to_string_lossy()))
}
pub fn pretty_mtime(&self) -> Option<String> {
if self.meta().is_none() { return None }
let time: chrono::DateTime<chrono::Local>
= chrono::Local.timestamp(self.meta().unwrap().mtime(), 0);
Some(time.format("%F %R").to_string())
}
pub fn icon(&self) -> &'static str {
ICONS.get(&self.path)
}
pub fn short_path(&self) -> PathBuf {
self.path.short_path()
}
pub fn short_string(&self) -> String {
self.path.short_string()
}
}