use crate::file::FileInfo;
use crate::recover::RecoverType;
use crate::{is_exist, is_same_root, fix_path, replace, FileAttr, RecoverResult};
use std::collections::VecDeque;
use std::ffi::OsStr;
use std::fmt::{Debug, Display, Formatter};
use std::fs::{create_dir_all, read_dir, remove_dir_all, rename};
use std::io::{Error, ErrorKind, Result};
use std::path::{Path, PathBuf};
#[derive(Clone)]
pub struct DirectoryInfo {
pub(crate) path: PathBuf,
}
impl Debug for DirectoryInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DirectoryInfo")
.field("path", &self.as_path().display().to_string())
.finish()
}
}
impl Display for DirectoryInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.path.display().to_string())
}
}
impl DirectoryInfo {
pub fn open<P: AsRef<Path>>(path: P) -> Result<DirectoryInfo> {
let path = fix_path(path)?;
if path.is_dir() {
Ok(DirectoryInfo { path })
} else {
Err(Error::new(
ErrorKind::NotFound,
format!(
"The path '{}' is not a directory or does not exist",
path.display()
),
))
}
}
pub unsafe fn open_uncheck(path: impl AsRef<Path>) -> DirectoryInfo {
DirectoryInfo {
path: path.as_ref().to_path_buf()
}
}
pub fn create<P: AsRef<Path>>(path: P) -> Result<DirectoryInfo> {
let path = fix_path(path)?;
create_dir_all(path.as_path())?;
Ok(DirectoryInfo { path })
}
fn _open(path: PathBuf) -> DirectoryInfo {
DirectoryInfo { path }
}
pub fn children(&self) -> Result<Vec<PathBuf>> {
get_children(self.as_path(), |path| path.is_dir() || path.is_file())
}
pub fn children_filter_by<F>(&self, f: F) -> Result<Vec<PathBuf>>
where
F: Fn(&Path) -> bool,
{
get_children(self.as_path(), |path| {
(path.is_dir() || path.is_file()) && f(path)
})
}
pub fn files(&self) -> Result<Vec<FileInfo>> {
get_children(self.as_path(), |path| path.is_file()).and_then(|paths| {
Ok(paths
.into_iter()
.map(|path| FileInfo::_open(path))
.collect())
})
}
pub fn files_filter_by<F>(&self, f: F) -> Result<Vec<FileInfo>>
where
F: Fn(&FileInfo) -> bool,
{
get_children(self.as_path(), |path| path.is_file()).and_then(|paths| {
Ok(paths
.into_iter()
.map(|path| FileInfo::_open(path))
.filter(f)
.collect())
})
}
pub fn directories(&self) -> Result<Vec<DirectoryInfo>> {
get_children(self.as_path(), |path| path.is_dir()).and_then(|paths| {
Ok(paths
.into_iter()
.map(|path| DirectoryInfo::_open(path))
.collect())
})
}
pub fn directories_filter_by<F>(&self, f: F) -> Result<Vec<DirectoryInfo>>
where
F: Fn(&DirectoryInfo) -> bool,
{
get_children(self.as_path(), |path| path.is_dir()).and_then(|paths| {
Ok(paths
.into_iter()
.map(|path| DirectoryInfo::_open(path))
.filter(f)
.collect())
})
}
}
#[inline]
fn get_children<F>(path: &Path, f: F) -> Result<Vec<PathBuf>>
where
F: Fn(&Path) -> bool,
{
let children = read_dir(path)?
.into_iter()
.filter_map(|dir| match dir {
Ok(dir) => {
let path = dir.path();
if f(&path) {
Some(path)
} else {
None
}
}
_ => None,
})
.collect();
Ok(children)
}
impl FileAttr for DirectoryInfo {
fn as_path(&self) -> &Path {
self.path.as_path()
}
fn size(&self) -> u64 {
let mut queue = VecDeque::new();
queue.push_back(self.clone());
let mut size = 0;
while let Some(dir) = queue.pop_front() {
for directory in dir.directories().unwrap_or_default() {
queue.push_back(directory);
}
for file in dir.files().unwrap_or_default() {
size += file.size();
}
}
size
}
fn rename<T: AsRef<OsStr>>(&mut self, name: T) -> Result<()> {
let mut path = self.path.clone();
path.set_file_name(name);
rename(self.as_path(), path.as_path())?;
self.path = path;
Ok(())
}
fn eq(&self, other: &Self) -> bool {
fn _eq(dir: DirectoryInfo, other: DirectoryInfo) -> Result<bool> {
let mut queue = VecDeque::new();
queue.push_back((dir, other));
while let Some((dir, other)) = queue.pop_front() {
let mut dir_vec = dir.directories()?;
let mut other_vec = other.directories()?;
if dir_vec.len() != other_vec.len() {
return Ok(false);
}
while let Some(dir) = dir_vec.pop() {
let Some(other) = other_vec.pop() else {
return Ok(false)
};
if dir.file_name() != other.file_name() {
return Ok(false);
}
queue.push_back((dir, other));
}
let mut f_vec = dir.files()?;
let mut other_vec = other.files()?;
if f_vec.len() != other_vec.len() {
return Ok(false);
}
while let Some(f) = f_vec.pop() {
let Some(other) = other_vec.pop() else {
return Ok(false)
};
if f.file_name() != other.file_name() || !f.eq(&other) {
return Ok(false);
}
}
}
Ok(true)
}
_eq(self.clone(), other.clone()).unwrap_or_default()
}
fn copy_new<P: AsRef<Path>>(&self, path: P) -> RecoverResult {
let path = fix_path(path)?;
if is_exist(path.as_path()) {
return Err((RecoverType::CopyDirectory(self), path).into());
}
_copy_dir(self.clone(), path)?;
Ok(())
}
fn move_new<P: AsRef<Path>>(&mut self, path: P) -> RecoverResult {
let path = fix_path(path)?;
if is_exist(path.as_path()) {
return Err((RecoverType::MoveDirectory(self), path).into());
}
if is_same_root(self.as_path(), path.as_path()) {
rename(self.as_path(), path.as_path())?;
} else {
_move_dir(self.clone(), path.clone())?;
_delete_dir(self)?;
}
self.path = path;
Ok(())
}
}
pub(crate) fn _delete_dir(dir: &mut DirectoryInfo) -> Result<()> {
if dir.read_only()? {
dir.set_readonly(false)?
}
remove_dir_all(dir.as_path())
}
pub(crate) fn _move_dir(dir: DirectoryInfo, to: PathBuf) -> Result<()> {
let mut queue = VecDeque::new();
let path = dir.path.clone();
queue.push_back(dir);
while let Some(dir) = queue.pop_front() {
for directory in dir.directories()? {
queue.push_front(directory)
}
let dir_path = replace(dir.as_path(), path.as_path(), to.as_path());
if !dir_path.is_dir() {
create_dir_all(dir_path.as_path())?;
}
for mut file in dir.files()? {
file.move_to(dir_path.as_path())
.or_else(|e| e.try_recover())?;
}
}
Ok(())
}
pub(crate) fn _copy_dir(dir: DirectoryInfo, to: PathBuf) -> Result<()> {
let mut queue = VecDeque::new();
let path = dir.path.clone();
queue.push_back(dir);
while let Some(dir) = queue.pop_front() {
for directory in dir.directories()? {
queue.push_front(directory)
}
let dir_path = replace(dir.as_path(), path.as_path(), to.as_path());
if !dir_path.is_dir() {
create_dir_all(dir_path.as_path())?;
}
for file in dir.files()? {
file.copy_to(dir_path.as_path())
.or_else(|e| e.try_recover())?;
}
}
Ok(())
}