use crate::args::Settings;
use crate::chunks::BytesChunkBuffer;
use crate::paths::{HeaderPrinter, PathExtTail};
use crate::text;
use std::collections::HashMap;
use std::collections::hash_map::Keys;
use std::fs::{File, Metadata};
use std::io::{BufRead, BufReader, BufWriter, Write, stdout};
use std::path::{Path, PathBuf};
use uucore::error::UResult;
pub struct FileHandling {
map: HashMap<PathBuf, PathData>,
last: Option<PathBuf>,
header_printer: HeaderPrinter,
}
impl FileHandling {
pub fn from(settings: &Settings) -> Self {
Self {
map: HashMap::with_capacity(settings.inputs.len()),
last: None,
header_printer: HeaderPrinter::new(settings.verbose, false),
}
}
pub fn insert(&mut self, k: &Path, v: PathData, update_last: bool) {
let k = Self::canonicalize_path(k);
if update_last {
self.last = Some(k.clone());
}
let _ = self.map.insert(k, v);
}
pub fn remove(&mut self, k: &Path) -> PathData {
self.map.remove(&Self::canonicalize_path(k)).unwrap()
}
pub fn get(&self, k: &Path) -> &PathData {
self.map.get(&Self::canonicalize_path(k)).unwrap()
}
pub fn get_mut(&mut self, k: &Path) -> &mut PathData {
self.map.get_mut(&Self::canonicalize_path(k)).unwrap()
}
fn canonicalize_path(path: &Path) -> PathBuf {
if path.is_relative() && !path.is_stdin() {
if let Ok(p) = path.canonicalize() {
return p;
}
}
path.to_owned()
}
pub fn get_mut_metadata(&mut self, path: &Path) -> Option<&Metadata> {
self.get_mut(path).metadata.as_ref()
}
pub fn keys(&self) -> Keys<'_, PathBuf, PathData> {
self.map.keys()
}
pub fn contains_key(&self, k: &Path) -> bool {
self.map.contains_key(k)
}
pub fn get_last(&self) -> Option<&PathBuf> {
self.last.as_ref()
}
pub fn only_stdin_remaining(&self) -> bool {
self.map.len() == 1 && (self.map.contains_key(Path::new(text::DASH)))
}
pub fn files_remaining(&self) -> bool {
for path in self.map.keys() {
if path.is_tailable() || path.is_stdin() {
return true;
}
}
false
}
pub fn no_files_remaining(&self, settings: &Settings) -> bool {
self.map.is_empty() || !self.files_remaining() && !settings.retry
}
pub fn reset_reader(&mut self, path: &Path) {
self.get_mut(path).reader = None;
}
pub fn update_reader(&mut self, path: &Path) -> UResult<()> {
self.get_mut(path)
.reader
.replace(Box::new(BufReader::new(File::open(path)?)));
Ok(())
}
pub fn update_metadata(&mut self, path: &Path, metadata: Option<Metadata>) {
self.get_mut(path).metadata = if metadata.is_some() {
metadata
} else {
path.metadata().ok()
};
}
pub fn tail_file(&mut self, path: &Path, verbose: bool) -> UResult<bool> {
let mut chunks = BytesChunkBuffer::new(u64::MAX);
if let Some(reader) = self.get_mut(path).reader.as_mut() {
chunks.fill(reader)?;
}
if chunks.has_data() {
if self.needs_header(path, verbose) {
let display_name = self.get(path).display_name.clone();
self.header_printer.print(display_name.as_str());
}
let mut writer = BufWriter::new(stdout().lock());
chunks.print(&mut writer)?;
writer.flush()?;
self.last.replace(path.to_owned());
self.update_metadata(path, None);
Ok(true)
} else {
Ok(false)
}
}
pub fn needs_header(&self, path: &Path, verbose: bool) -> bool {
if verbose {
if let Some(ref last) = self.last {
!last.eq(&path)
} else {
true
}
} else {
false
}
}
}
pub struct PathData {
pub reader: Option<Box<dyn BufRead>>,
pub metadata: Option<Metadata>,
pub display_name: String,
}
impl PathData {
pub fn new(
reader: Option<Box<dyn BufRead>>,
metadata: Option<Metadata>,
display_name: &str,
) -> Self {
Self {
reader,
metadata,
display_name: display_name.to_owned(),
}
}
pub fn from_other_with_path(data: Self, path: &Path) -> Self {
let old_reader = data.reader;
let reader = if old_reader.is_some() {
old_reader
} else if let Ok(file) = File::open(path) {
Some(Box::new(BufReader::new(file)) as Box<dyn BufRead>)
} else {
None
};
Self::new(reader, path.metadata().ok(), data.display_name.as_str())
}
}