mirror of
https://github.com/bobwen-dev/hunter
synced 2025-04-12 00:55:41 +02:00

Squashed commit of the following: commit 353e97f4dcff04929cabd707275df193bdde2f31 Author: rabite <rabite@posteo.de> Date: Tue Apr 9 18:44:10 2019 +0200 remove dbgs commit 97e9cd3eadd259742f0f2a3a619c0eaa23112e54 Author: rabite <rabite@posteo.de> Date: Mon Apr 8 17:53:15 2019 +0200 print names while sorting by name commit 698fffc4b70255268befdbf5b90948e1ae218538 Author: rabite <rabite@posteo.de> Date: Mon Apr 8 17:50:59 2019 +0200 possible fix for crash when sorting
1194 lines
35 KiB
Rust
1194 lines
35 KiB
Rust
use std::cmp::{Ord, Ordering};
|
|
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::os::unix::ffi::{OsStringExt, OsStrExt};
|
|
use std::ffi::{OsStr, OsString};
|
|
|
|
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 notify::DebouncedEvent;
|
|
use rayon::{ThreadPool, ThreadPoolBuilder};
|
|
use alphanumeric_sort::compare_str;
|
|
|
|
use crate::fail::{HResult, HError, ErrorLog};
|
|
use crate::dirty::{AsyncDirtyBit, DirtyBit, Dirtyable};
|
|
use crate::preview::{Async, Stale};
|
|
use crate::widget::Events;
|
|
|
|
|
|
lazy_static! {
|
|
static ref COLORS: LsColors = LsColors::from_env().unwrap();
|
|
static ref TAGS: RwLock<(bool, Vec<PathBuf>)> = RwLock::new((false, vec![]));
|
|
}
|
|
|
|
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 Files {
|
|
pub directory: File,
|
|
pub files: Vec<File>,
|
|
pub meta_upto: Option<usize>,
|
|
pub meta_updated: bool,
|
|
pub sort: SortBy,
|
|
pub dirs_first: bool,
|
|
pub reverse: bool,
|
|
pub show_hidden: bool,
|
|
pub filter: Option<String>,
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
impl Files {
|
|
pub fn new_from_path(path: &Path) -> Result<Files, Error> {
|
|
let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect();
|
|
let dirty = DirtyBit::new();
|
|
let dirty_meta = AsyncDirtyBit::new();
|
|
|
|
let files: Vec<_> = direntries?
|
|
.iter()
|
|
.map(|file| {
|
|
let name = file.file_name();
|
|
let name = name.to_string_lossy();
|
|
let path = file.path();
|
|
File::new(&name,
|
|
path,
|
|
Some(dirty_meta.clone()))
|
|
})
|
|
.collect();
|
|
|
|
let mut files = Files {
|
|
directory: File::new_from_path(&path, None)?,
|
|
files: files,
|
|
meta_upto: None,
|
|
meta_updated: false,
|
|
sort: SortBy::Name,
|
|
dirs_first: true,
|
|
reverse: false,
|
|
show_hidden: true,
|
|
filter: None,
|
|
dirty: dirty,
|
|
dirty_meta: dirty_meta,
|
|
};
|
|
|
|
files.sort();
|
|
|
|
|
|
|
|
if files.files.len() == 0 {
|
|
files.files = vec![File::new_placeholder(&path)?];
|
|
}
|
|
|
|
Ok(files)
|
|
}
|
|
|
|
pub fn new_from_path_cancellable(path: &Path,
|
|
stale: Stale)
|
|
-> Result<Files, Error> {
|
|
let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect();
|
|
let dirty = DirtyBit::new();
|
|
let dirty_meta = AsyncDirtyBit::new();
|
|
|
|
let files: Vec<_> = direntries?
|
|
.iter()
|
|
.map(|file| {
|
|
if crate::preview::is_stale(&stale).unwrap() {
|
|
None
|
|
} else {
|
|
let name = file.file_name();
|
|
let name = name.to_string_lossy();
|
|
let path = file.path();
|
|
Some(File::new_with_stale(&name,
|
|
path,
|
|
Some(dirty_meta.clone()),
|
|
stale.clone()))
|
|
}
|
|
})
|
|
.fuse()
|
|
.flatten()
|
|
.collect();
|
|
|
|
if crate::preview::is_stale(&stale).unwrap() {
|
|
return Err(crate::fail::HError::StalePreviewError {
|
|
file: path.to_string_lossy().to_string()
|
|
})?;
|
|
}
|
|
|
|
let mut files = Files {
|
|
directory: File::new_from_path(&path, None)?,
|
|
files: files,
|
|
meta_upto: None,
|
|
meta_updated: false,
|
|
sort: SortBy::Name,
|
|
dirs_first: true,
|
|
reverse: false,
|
|
show_hidden: true,
|
|
filter: None,
|
|
dirty: dirty,
|
|
dirty_meta: dirty_meta,
|
|
};
|
|
|
|
files.sort();
|
|
|
|
if files.files.len() == 0 {
|
|
files.files = vec![File::new_placeholder(&path)?];
|
|
}
|
|
|
|
Ok(files)
|
|
}
|
|
|
|
pub fn get_file_mut(&mut self, index: usize) -> Option<&mut File> {
|
|
let filter = self.filter.clone();
|
|
let show_hidden = self.show_hidden;
|
|
|
|
let file = self.files
|
|
.iter_mut()
|
|
.filter(|f| !(filter.is_some() &&
|
|
!f.name.contains(filter.as_ref().unwrap())))
|
|
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
|
|
.nth(index);
|
|
file
|
|
}
|
|
|
|
pub fn get_files(&self) -> Vec<&File> {
|
|
self.files
|
|
.iter()
|
|
.filter(|f| !(self.filter.is_some() &&
|
|
!f.name.contains(self.filter.as_ref().unwrap())))
|
|
.filter(|f| !(!self.show_hidden && f.name.starts_with(".")))
|
|
.collect()
|
|
}
|
|
|
|
pub fn get_files_mut(&mut self) -> Vec<&mut File> {
|
|
let filter = self.filter.clone();
|
|
let show_hidden = self.show_hidden;
|
|
self.files
|
|
.iter_mut()
|
|
.filter(|f| !(filter.is_some() &&
|
|
!f.name.contains(filter.as_ref().unwrap())))
|
|
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
|
|
.collect()
|
|
}
|
|
|
|
pub fn sort(&mut self) {
|
|
match self.sort {
|
|
SortBy::Name => self
|
|
.files
|
|
.sort_by(|a, b| {
|
|
compare_str(&a.name, &b.name)
|
|
}),
|
|
SortBy::Size => {
|
|
self.meta_all_sync().log();
|
|
self.files.sort_by(|a, b| {
|
|
match (a.meta(), b.meta()) {
|
|
(Ok(a_meta), Ok(b_meta)) => {
|
|
if a_meta.size() == b_meta.size() {
|
|
compare_str(&b.name, &a.name)
|
|
} else {
|
|
a_meta.size().cmp(&b_meta.size()).reverse()
|
|
}
|
|
|
|
}
|
|
_ => return std::cmp::Ordering::Equal
|
|
}
|
|
|
|
|
|
});
|
|
}
|
|
SortBy::MTime => {
|
|
self.meta_all_sync().log();
|
|
self.files.sort_by(|a, b| {
|
|
match (a.meta(), b.meta()) {
|
|
(Ok(a_meta), Ok(b_meta)) => {
|
|
if a_meta.mtime() == b_meta.mtime() {
|
|
compare_str(&b.name, &a.name)
|
|
} else {
|
|
a_meta.mtime().cmp(&b_meta.mtime()).reverse()
|
|
}
|
|
|
|
}
|
|
_ => return std::cmp::Ordering::Equal
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
if self.dirs_first {
|
|
self.files.sort_by(|a, b| {
|
|
if a.is_dir() && !b.is_dir() {
|
|
Ordering::Less
|
|
} else {
|
|
Ordering::Equal
|
|
}
|
|
});
|
|
self.files.sort_by(|a, b| {
|
|
if a.name.starts_with(".") && !b.name.starts_with(".") {
|
|
Ordering::Less
|
|
} else {
|
|
Ordering::Equal
|
|
}
|
|
});
|
|
}
|
|
|
|
if self.reverse {
|
|
self.files.reverse();
|
|
}
|
|
self.set_dirty();
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
pub fn replace_file(&mut self,
|
|
old: Option<&File>,
|
|
new: Option<File>) -> HResult<()> {
|
|
let (tag, selected) = if let Some(old) = old {
|
|
if let Some(old) = self.find_file_with_path(&old.path) {
|
|
(old.tag, old.selected)
|
|
} else {
|
|
(None, false)
|
|
}
|
|
} else {
|
|
(None, false)
|
|
};
|
|
old.map(|old| self.files.remove_item(old));
|
|
new.map(|mut new| {
|
|
new.tag = tag;
|
|
new.selected = selected;
|
|
self.files.push(new);
|
|
});
|
|
self.sort();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn handle_event(&mut self,
|
|
event: &DebouncedEvent) -> HResult<()> {
|
|
match event {
|
|
DebouncedEvent::Create(path) => {
|
|
self.path_in_here(&path)?;
|
|
let file = File::new_from_path(&path,
|
|
Some(self.dirty_meta.clone()))?;
|
|
self.files.push(file);
|
|
self.sort();
|
|
},
|
|
DebouncedEvent::Write(path) | DebouncedEvent::Chmod(path) => {
|
|
self.path_in_here(&path)?;
|
|
let file = self.find_file_with_path(&path)?;
|
|
file.reload_meta()?;
|
|
},
|
|
DebouncedEvent::Remove(path) => {
|
|
self.path_in_here(&path)?;
|
|
let file = self.find_file_with_path(&path)?.clone();
|
|
self.files.remove_item(&file);
|
|
},
|
|
DebouncedEvent::Rename(old_path, new_path) => {
|
|
self.path_in_here(&new_path)?;
|
|
let mut file = self.find_file_with_path(&old_path)?;
|
|
file.name = new_path.file_name()?.to_string_lossy().to_string();
|
|
file.path = new_path.into();
|
|
file.reload_meta()?;
|
|
},
|
|
DebouncedEvent::Error(err, path) => {
|
|
dbg!(err);
|
|
dbg!(path);
|
|
},
|
|
_ => {},
|
|
}
|
|
self.set_dirty();
|
|
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_path(&mut self, path: &Path) -> Option<&mut File> {
|
|
self.files.iter_mut().find(|file| file.path == path)
|
|
}
|
|
|
|
pub fn meta_all_sync(&mut self) -> HResult<()> {
|
|
for file in self.files.iter_mut() {
|
|
if !file.meta_processed {
|
|
file.meta_sync().log();
|
|
}
|
|
}
|
|
self.set_dirty();
|
|
self.meta_updated = true;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn meta_all(&mut self) {
|
|
let len = self.len();
|
|
self.meta_upto(len, None);
|
|
}
|
|
|
|
pub fn meta_upto(&mut self, to: usize, sender: Option<Sender<Events>>) {
|
|
let meta_files = if self.meta_upto > Some(to) {
|
|
self.meta_upto.unwrap()
|
|
} else {
|
|
if to > self.len() {
|
|
self.len()
|
|
} else {
|
|
to
|
|
}
|
|
};
|
|
|
|
if self.meta_upto >= Some(meta_files) && !self.dirty_meta.is_dirty() { return }
|
|
|
|
self.set_dirty();
|
|
self.dirty_meta.set_clean();
|
|
|
|
let meta_pool = make_pool(sender.clone());
|
|
let show_hidden = self.show_hidden;
|
|
|
|
for file in self.files
|
|
.iter_mut()
|
|
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
|
|
.take(meta_files) {
|
|
if !file.meta_processed {
|
|
file.take_meta(&meta_pool, &mut self.meta_updated).ok();
|
|
}
|
|
if file.is_dir() {
|
|
file.take_dirsize(&meta_pool, &mut self.meta_updated).ok();
|
|
}
|
|
}
|
|
|
|
self.meta_upto = Some(meta_files);
|
|
}
|
|
|
|
pub fn meta_set_fresh(&self) -> HResult<()> {
|
|
self.files.get(0)?.meta.set_fresh()?;
|
|
Ok(())
|
|
}
|
|
|
|
|
|
pub fn set_filter(&mut self, filter: Option<String>) {
|
|
self.filter = filter;
|
|
self.set_dirty();
|
|
}
|
|
|
|
pub fn get_filter(&self) -> Option<String> {
|
|
self.filter.clone()
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.get_files().len()
|
|
}
|
|
|
|
pub fn get_selected(&self) -> Vec<&File> {
|
|
self.files.iter().filter(|f| f.is_selected()).collect()
|
|
}
|
|
}
|
|
|
|
#[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 {}
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct File {
|
|
pub name: String,
|
|
pub path: PathBuf,
|
|
pub kind: Kind,
|
|
pub dirsize: Option<Async<usize>>,
|
|
pub target: Option<PathBuf>,
|
|
pub color: Option<lscolors::Color>,
|
|
pub meta: Async<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 tag = check_tag(&path).ok();
|
|
let meta = File::make_async_meta(&path, dirty_meta.clone(), None);
|
|
let dirsize = if path.is_dir() {
|
|
Some(File::make_async_dirsize(&path, dirty_meta.clone(), None))
|
|
} else { None };
|
|
|
|
File {
|
|
name: name.to_string(),
|
|
kind: if path.is_dir() { Kind::Directory } else { Kind::File },
|
|
path: path,
|
|
dirsize: dirsize,
|
|
target: None,
|
|
meta: meta,
|
|
meta_processed: false,
|
|
dirty_meta: dirty_meta,
|
|
color: None,
|
|
selected: false,
|
|
tag: tag,
|
|
}
|
|
}
|
|
|
|
pub fn new_with_stale(name: &str,
|
|
path: PathBuf,
|
|
dirty_meta: Option<AsyncDirtyBit>,
|
|
stale: Stale) -> File {
|
|
let tag = check_tag(&path).ok();
|
|
let meta = File::make_async_meta(&path,
|
|
dirty_meta.clone(),
|
|
Some(stale.clone()));
|
|
let dirsize = if path.is_dir() {
|
|
Some(File::make_async_dirsize(&path,
|
|
dirty_meta.clone(),
|
|
Some(stale)))
|
|
} else { None };
|
|
|
|
File {
|
|
name: name.to_string(),
|
|
kind: if path.is_dir() { Kind::Directory } else { Kind::File },
|
|
path: path,
|
|
dirsize: dirsize,
|
|
target: None,
|
|
meta: meta,
|
|
meta_processed: false,
|
|
dirty_meta: dirty_meta,
|
|
color: None,
|
|
selected: false,
|
|
tag: tag,
|
|
}
|
|
}
|
|
|
|
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 meta_sync(&mut self) -> HResult<()> {
|
|
let stale = self.meta.get_stale();
|
|
let meta = std::fs::metadata(&self.path)?;
|
|
self.meta = Async::new_with_value(meta);
|
|
self.meta.put_stale(stale);
|
|
self.process_meta()
|
|
}
|
|
|
|
pub fn make_async_meta(path: &PathBuf,
|
|
dirty_meta: Option<AsyncDirtyBit>,
|
|
stale_preview: Option<Stale>) -> Async<Metadata> {
|
|
let path = path.clone();
|
|
|
|
let meta_closure = Box::new(move |stale: Stale| {
|
|
if stale.is_stale()? { HError::stale()? }
|
|
Ok(std::fs::symlink_metadata(&path)?)
|
|
});
|
|
|
|
let mut meta = match stale_preview {
|
|
Some(stale) => Async::new_with_stale(meta_closure, stale),
|
|
None => Async::new(meta_closure)
|
|
};
|
|
if let Some(dirty_meta) = dirty_meta {
|
|
meta.on_ready(Box::new(move || {
|
|
let mut dirty_meta = dirty_meta.clone();
|
|
dirty_meta.set_dirty();
|
|
|
|
Ok(())
|
|
}));
|
|
}
|
|
meta
|
|
}
|
|
|
|
pub fn make_async_dirsize(path: &PathBuf,
|
|
dirty_meta: Option<AsyncDirtyBit>,
|
|
stale_preview: Option<Stale>) -> Async<usize> {
|
|
let path = path.clone();
|
|
|
|
let dirsize_closure = Box::new(move |stale: Stale| {
|
|
if stale.is_stale()? { HError::stale()? }
|
|
Ok(std::fs::read_dir(&path)?.count())
|
|
});
|
|
|
|
let mut dirsize = match stale_preview {
|
|
Some(stale) => Async::new_with_stale(dirsize_closure, stale),
|
|
None => Async::new(dirsize_closure)
|
|
};
|
|
|
|
if let Some(dirty_meta) = dirty_meta {
|
|
dirsize.on_ready(Box::new(move || {
|
|
let mut dirty_meta = dirty_meta.clone();
|
|
dirty_meta.set_dirty();
|
|
|
|
Ok(())
|
|
}));
|
|
}
|
|
dirsize
|
|
}
|
|
|
|
pub fn meta(&self) -> HResult<&Metadata> {
|
|
self.meta.get()
|
|
}
|
|
|
|
fn take_dirsize(&mut self,
|
|
pool: &ThreadPool,
|
|
meta_updated: &mut bool) -> HResult<()> {
|
|
let dirsize = self.dirsize.as_mut()?;
|
|
if let Ok(_) = dirsize.value { return Ok(()) }
|
|
|
|
match dirsize.take_async() {
|
|
Ok(_) => { *meta_updated = true; },
|
|
Err(HError::AsyncNotReadyError) => { dirsize.run_pooled(&*pool).ok(); },
|
|
Err(HError::AsyncAlreadyTakenError) => {},
|
|
Err(HError::NoneError) => {},
|
|
err @ Err(_) => { err?; }
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn take_meta(&mut self,
|
|
pool: &ThreadPool,
|
|
meta_updated: &mut bool) -> HResult<()> {
|
|
if self.meta_processed { return Ok(()) }
|
|
|
|
match self.meta.take_async() {
|
|
Ok(_) => { *meta_updated = true; },
|
|
Err(HError::AsyncNotReadyError) => { self.meta.run_pooled(&*pool).ok(); },
|
|
Err(HError::AsyncAlreadyTakenError) => {},
|
|
Err(HError::NoneError) => {},
|
|
err @ Err(_) => { err?; }
|
|
}
|
|
|
|
self.process_meta()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn process_meta(&mut self) -> HResult<()> {
|
|
if let Ok(meta) = self.meta.get() {
|
|
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 = File::make_async_meta(&self.path,
|
|
self.dirty_meta.clone(),
|
|
None);
|
|
self.meta.run().log();
|
|
|
|
if self.dirsize.is_some() {
|
|
self.dirsize
|
|
= Some(File::make_async_dirsize(&self.path, self.dirty_meta.clone(), None));
|
|
self.dirsize.as_mut()?.run().log();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
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<(u64, String)> {
|
|
if let Some(ref dirsize) = self.dirsize {
|
|
return Ok((dirsize.value.clone()? as u64, "".to_string()))
|
|
}
|
|
|
|
|
|
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",
|
|
_ => "",
|
|
}
|
|
.to_string();
|
|
Ok((size, unit))
|
|
}
|
|
|
|
// pub fn get_mime(&self) -> String {
|
|
// tree_magic::from_filepath(&self.path)
|
|
// }
|
|
|
|
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) -> Result<Files, Error> {
|
|
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 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_err() { 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_err() { 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_err() { 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 short_path(&self) -> PathBuf {
|
|
self.path.short_path()
|
|
}
|
|
|
|
pub fn short_string(&self) -> String {
|
|
self.path.short_string()
|
|
}
|
|
}
|
|
|
|
|
|
pub trait PathBufExt {
|
|
fn short_path(&self) -> PathBuf;
|
|
fn short_string(&self) -> String;
|
|
fn name_starts_with(&self, pat: &str) -> bool;
|
|
fn quoted_file_name(&self) -> Option<OsString>;
|
|
fn quoted_path(&self) -> OsString;
|
|
}
|
|
|
|
impl PathBufExt for PathBuf {
|
|
fn short_path(&self) -> PathBuf {
|
|
if let Ok(home) = crate::paths::home_path() {
|
|
if let Ok(short) = self.strip_prefix(home) {
|
|
let mut path = PathBuf::from("~");
|
|
path.push(short);
|
|
return path
|
|
}
|
|
}
|
|
return self.clone();
|
|
}
|
|
|
|
fn short_string(&self) -> String {
|
|
self.short_path().to_string_lossy().to_string()
|
|
}
|
|
|
|
fn name_starts_with(&self, pat: &str) -> bool {
|
|
if let Some(name) = self.file_name() {
|
|
let nbytes = name.as_bytes();
|
|
let pbytes = pat.as_bytes();
|
|
|
|
if nbytes.starts_with(pbytes) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
fn quoted_file_name(&self) -> Option<OsString> {
|
|
if let Some(name) = self.file_name() {
|
|
let mut name = name.as_bytes().to_vec();
|
|
let mut quote = "\"".as_bytes().to_vec();
|
|
let mut quoted = vec![];
|
|
quoted.append(&mut quote.clone());
|
|
quoted.append(&mut name);
|
|
quoted.append(&mut quote);
|
|
|
|
let quoted_name = OsStr::from_bytes("ed).to_os_string();
|
|
return Some(quoted_name);
|
|
}
|
|
None
|
|
}
|
|
|
|
fn quoted_path(&self) -> OsString {
|
|
let mut path = self.clone().into_os_string().into_vec();
|
|
let mut quote = "\"".as_bytes().to_vec();
|
|
|
|
let mut quoted = vec![];
|
|
quoted.append(&mut quote.clone());
|
|
quoted.append(&mut path);
|
|
quoted.append(&mut quote);
|
|
|
|
OsString::from_vec(quoted)
|
|
}
|
|
}
|
|
|
|
pub trait OsStrTools {
|
|
fn split(&self, pat: &OsStr) -> Vec<OsString>;
|
|
fn replace(&self, from: &OsStr, to: &OsStr) -> OsString;
|
|
fn trim_last_space(&self) -> OsString;
|
|
fn contains_osstr(&self, pat: &OsStr) -> bool;
|
|
fn position(&self, pat: &OsStr) -> Option<usize>;
|
|
fn splice_quoted(&self, from: &OsStr, to: Vec<OsString>) -> Vec<OsString>;
|
|
fn splice_with(&self, from: &OsStr, to: Vec<OsString>) -> Vec<OsString>;
|
|
fn quote(&self) -> OsString;
|
|
}
|
|
|
|
impl OsStrTools for OsStr {
|
|
fn split(&self, pat: &OsStr) -> Vec<OsString> {
|
|
let orig_string = self.as_bytes().to_vec();
|
|
let pat = pat.as_bytes().to_vec();
|
|
let pat_len = pat.len();
|
|
|
|
let split_string = orig_string
|
|
.windows(pat_len)
|
|
.enumerate()
|
|
.fold(Vec::new(), |mut split_pos, (i, chars)| {
|
|
if chars == pat.as_slice() {
|
|
if split_pos.len() == 0 {
|
|
split_pos.push((0, i));
|
|
} else {
|
|
let len = split_pos.len();
|
|
let last_split = split_pos[len-1].1;
|
|
split_pos.push((last_split, i));
|
|
}
|
|
}
|
|
split_pos
|
|
}).iter()
|
|
.map(|(start, end)| {
|
|
OsString::from_vec(orig_string[*start..*end]
|
|
.to_vec()).replace(&OsString::from_vec(pat.clone()),
|
|
&OsString::from(""))
|
|
}).collect();
|
|
split_string
|
|
}
|
|
|
|
|
|
fn quote(&self) -> OsString {
|
|
let mut string = self.as_bytes().to_vec();
|
|
let mut quote = "\"".as_bytes().to_vec();
|
|
|
|
let mut quoted = vec![];
|
|
quoted.append(&mut quote.clone());
|
|
quoted.append(&mut string);
|
|
quoted.append(&mut quote);
|
|
|
|
OsString::from_vec(quoted)
|
|
}
|
|
|
|
fn splice_quoted(&self, from: &OsStr, to: Vec<OsString>) -> Vec<OsString> {
|
|
let quoted_to = to.iter()
|
|
.map(|to| to.quote())
|
|
.collect();
|
|
self.splice_with(from, quoted_to)
|
|
}
|
|
|
|
fn splice_with(&self, from: &OsStr, to: Vec<OsString>) -> Vec<OsString> {
|
|
let pos = self.position(from);
|
|
|
|
if pos.is_none() {
|
|
return vec![OsString::from(self)];
|
|
}
|
|
|
|
let pos = pos.unwrap();
|
|
let string = self.as_bytes().to_vec();
|
|
let from = from.as_bytes().to_vec();
|
|
let fromlen = from.len();
|
|
|
|
let lpart = OsString::from_vec(string[0..pos].to_vec());
|
|
let rpart = OsString::from_vec(string[pos+fromlen..].to_vec());
|
|
|
|
let mut result = vec![
|
|
vec![lpart.trim_last_space()],
|
|
to,
|
|
vec![rpart]
|
|
].into_iter()
|
|
.flatten()
|
|
.filter(|part| part.len() != 0)
|
|
.collect::<Vec<OsString>>();
|
|
|
|
if result.last() == Some(&OsString::from("")) {
|
|
result.pop();
|
|
result
|
|
} else { result }
|
|
}
|
|
|
|
fn replace(&self, from: &OsStr, to: &OsStr) -> OsString {
|
|
let orig_string = self.as_bytes().to_vec();
|
|
let from = from.as_bytes();
|
|
let to = to.as_bytes().to_vec();
|
|
let from_len = from.len();
|
|
|
|
let new_string = orig_string
|
|
.windows(from_len)
|
|
.enumerate()
|
|
.fold(Vec::new(), |mut pos, (i, chars)| {
|
|
if chars == from {
|
|
pos.push(i);
|
|
}
|
|
pos
|
|
}).iter().rev().fold(orig_string.to_vec(), |mut string, pos| {
|
|
let pos = *pos;
|
|
string.splice(pos..pos+from_len, to.clone());
|
|
string
|
|
});
|
|
|
|
OsString::from_vec(new_string)
|
|
}
|
|
|
|
fn trim_last_space(&self) -> OsString {
|
|
let string = self.as_bytes();
|
|
let len = string.len();
|
|
|
|
if len > 0 {
|
|
OsString::from_vec(string[..len-1].to_vec())
|
|
} else {
|
|
self.to_os_string()
|
|
}
|
|
}
|
|
|
|
fn contains_osstr(&self, pat: &OsStr) -> bool {
|
|
let string = self.as_bytes();
|
|
let pat = pat.as_bytes();
|
|
let pat_len = pat.len();
|
|
|
|
string.windows(pat_len)
|
|
.find(|chars|
|
|
chars == &pat
|
|
).is_some()
|
|
}
|
|
|
|
fn position(&self, pat: &OsStr) -> Option<usize> {
|
|
let string = self.as_bytes();
|
|
let pat = pat.as_bytes();
|
|
let pat_len = pat.len();
|
|
|
|
string.windows(pat_len)
|
|
.position(|chars|
|
|
chars == pat
|
|
)
|
|
}
|
|
}
|