use std::{fmt, fs, io};
use std::ffi::{OsStr, OsString};
use std::fs::{File, Metadata};
use std::path::{Path, PathBuf};
use log::error;
use crate::error::Failed;
#[derive(Clone, Debug)]
pub struct DirEntry {
path: PathBuf,
metadata: Metadata,
file_name: OsString,
}
impl DirEntry {
pub fn path(&self) -> &Path {
&self.path
}
pub fn into_path(self) -> PathBuf {
self.path
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn file_name(&self) -> &OsStr {
&self.file_name
}
pub fn into_file_name(self) -> OsString {
self.file_name
}
pub fn into_name_and_path(self) -> (OsString, PathBuf) {
(self.file_name, self.path)
}
pub fn is_file(&self) -> bool {
self.metadata.is_file()
}
pub fn is_dir(&self) -> bool {
self.metadata.is_dir()
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> u64 {
self.metadata.len()
}
}
#[derive(Debug)]
pub struct ReadDir<'a> {
path: &'a Path,
iter: fs::ReadDir,
}
impl Iterator for ReadDir<'_> {
type Item = Result<DirEntry, Failed>;
fn next(&mut self) -> Option<Self::Item> {
let entry = match self.iter.next()? {
Ok(entry) => entry,
Err(err) => {
error!(
"Fatal: failed to read directory {}: {}",
self.path.display(), IoErrorDisplay(err)
);
return Some(Err(Failed))
}
};
let metadata = match entry.metadata() {
Ok(metadata) => metadata,
Err(err) => {
error!(
"Fatal: failed to read directory {}: {}",
self.path.display(), IoErrorDisplay(err)
);
return Some(Err(Failed))
}
};
Some(Ok(DirEntry {
path: entry.path(),
metadata,
file_name: entry.file_name()
}))
}
}
pub fn read_dir(path: &Path) -> Result<ReadDir<'_>, Failed> {
match fs::read_dir(path) {
Ok(iter) => Ok(ReadDir { path, iter }),
Err(err) => {
error!(
"Fatal: failed to open directory {}: {}",
path.display(), IoErrorDisplay(err)
);
Err(Failed)
}
}
}
pub fn read_existing_dir(path: &Path) -> Result<Option<ReadDir<'_>>, Failed> {
match fs::read_dir(path) {
Ok(iter) => Ok(Some(ReadDir { path, iter })),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => {
error!(
"Fatal: failed to open directory {}: {}",
path.display(), IoErrorDisplay(err)
);
Err(Failed)
}
}
}
pub fn create_dir_all(path: &Path) -> Result<(), Failed> {
fs::create_dir_all(path).map_err(|err| {
error!(
"Fatal: failed to create directory {}: {}",
path.display(), IoErrorDisplay(err)
);
Failed
})
}
pub fn create_parent_all(path: &Path) -> Result<(), Failed> {
if let Some(path) = path.parent() {
fs::create_dir_all(path).map_err(|err| {
error!(
"Fatal: failed to create directory {}: {}",
path.display(), IoErrorDisplay(err)
);
Failed
})?
}
Ok(())
}
pub fn remove_dir_all(path: &Path) -> Result<(), Failed> {
if let Err(err) = fs::remove_dir_all(path) {
if err.kind() != io::ErrorKind::NotFound {
error!(
"Fatal: failed to remove directory tree {}: {}",
path.display(), IoErrorDisplay(err)
);
return Err(Failed)
}
}
Ok(())
}
pub fn remove_file(path: &Path) -> Result<(), Failed> {
if let Err(err) = fs::remove_file(path) {
if err.kind() != io::ErrorKind::NotFound {
error!(
"Fatal: failed to remove file {}: {}",
path.display(), IoErrorDisplay(err)
);
return Err(Failed)
}
}
Ok(())
}
pub fn remove_all(path: &Path) -> Result<(), Failed> {
if path.is_dir() {
remove_dir_all(path)
}
else {
remove_file(path)
}
}
pub fn rename(source: &Path, target: &Path) -> Result<(), Failed> {
fs::rename(source, target).map_err(|err| {
error!(
"Fatal: failed to move {} to {}: {}",
source.display(), target.display(), IoErrorDisplay(err)
);
Failed
})
}
pub fn open_file(path: &Path) -> Result<File, Failed> {
File::open(path).map_err(|err| {
error!(
"Fatal: failed to open file {}: {}",
path.display(), IoErrorDisplay(err)
);
Failed
})
}
pub fn open_existing_file(path: &Path) -> Result<Option<File>, Failed> {
match File::open(path) {
Ok(file) => Ok(Some(file)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => {
error!(
"Fatal: failed to open file {}: {}",
path.display(), IoErrorDisplay(err)
);
Err(Failed)
}
}
}
pub fn create_file(path: &Path) -> Result<File, Failed> {
File::create(path).map_err(|err| {
error!(
"Fatal: failed to open file {}: {}",
path.display(), IoErrorDisplay(err)
);
Failed
})
}
pub fn read_file(path: &Path) -> Result<Vec<u8>, Failed> {
fs::read(path).map_err(|err| {
error!(
"Fatal: failed to read file {}: {}",
path.display(), IoErrorDisplay(err)
);
Failed
})
}
pub fn read_existing_file(path: &Path) -> Result<Option<Vec<u8>>, Failed> {
match fs::read(path) {
Ok(some) => Ok(Some(some)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => {
error!(
"Fatal: failed to read file {}: {}",
path.display(), IoErrorDisplay(err)
);
Err(Failed)
}
}
}
pub fn write_file(path: &Path, contents: &[u8]) -> Result<(), Failed> {
fs::write(path, contents).map_err(|err| {
error!(
"Fatal: failed to write file {}: {}",
path.display(), IoErrorDisplay(err)
);
Failed
})
}
pub fn copy_existing_dir_all(
source: &Path, target: &Path
) -> Result<(), Failed> {
let source_dir = match read_existing_dir(source)? {
Some(entry) => entry,
None => return Ok(()),
};
create_dir_all(target)?;
for entry in source_dir {
let entry = entry?;
if entry.is_file() {
if let Err(err) = fs::copy(
entry.path(), target.join(entry.file_name())
) {
error!(
"Fatal: failed to copy {}: {}",
entry.path().display(), IoErrorDisplay(err)
);
return Err(Failed)
}
}
else if entry.is_dir() {
copy_existing_dir_all(
entry.path(),
&target.join(entry.file_name())
)?;
}
}
Ok(())
}
struct IoErrorDisplay(io::Error);
#[cfg(unix)]
impl fmt::Display for IoErrorDisplay {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use nix::errno::Errno;
if matches!(
self.0.raw_os_error().map(Errno::from_raw),
Some(Errno::ENOSPC)
) {
f.write_str("No space or inodes left on device")
}
else {
self.0.fmt(f)
}
}
}
#[cfg(not(unix))]
impl fmt::Display for IoErrorDisplay {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}