use std::{
cmp::Ordering,
fs::Metadata,
path::{Path, PathBuf},
};
use_enabled_fs_module!();
use crate::error::{DirectoryEmptinessScanError, DirectoryScanError};
pub(crate) mod collected;
mod iter;
pub use iter::*;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum DirectoryScanDepthLimit {
Unlimited,
Limited {
maximum_depth: usize,
},
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct DirectoryScanOptions {
pub yield_base_directory: bool,
pub maximum_scan_depth: DirectoryScanDepthLimit,
pub follow_symbolic_links: bool,
pub follow_base_directory_symbolic_link: bool,
}
impl DirectoryScanOptions {
#[inline]
pub(crate) const fn should_track_ancestors(&self) -> bool {
self.follow_symbolic_links
}
}
impl Default for DirectoryScanOptions {
fn default() -> Self {
Self {
yield_base_directory: true,
maximum_scan_depth: DirectoryScanDepthLimit::Unlimited,
follow_symbolic_links: false,
follow_base_directory_symbolic_link: false,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ScanEntryDepth {
BaseDirectory,
AtDepth {
depth: usize,
},
}
impl ScanEntryDepth {
fn plus_one_level(self) -> Self {
match self {
ScanEntryDepth::BaseDirectory => ScanEntryDepth::AtDepth { depth: 0 },
ScanEntryDepth::AtDepth { depth } => ScanEntryDepth::AtDepth { depth: depth + 1 },
}
}
}
impl PartialOrd for ScanEntryDepth {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(ScanEntryDepth::BaseDirectory, ScanEntryDepth::BaseDirectory) => Some(Ordering::Equal),
(ScanEntryDepth::BaseDirectory, ScanEntryDepth::AtDepth { .. }) => Some(Ordering::Less),
(ScanEntryDepth::AtDepth { .. }, ScanEntryDepth::BaseDirectory) => {
Some(Ordering::Greater)
}
(
ScanEntryDepth::AtDepth { depth: left_depth },
ScanEntryDepth::AtDepth { depth: right_depth },
) => left_depth.partial_cmp(right_depth),
}
}
}
pub struct ScanEntry {
path: PathBuf,
metadata: Metadata,
depth: ScanEntryDepth,
}
impl ScanEntry {
#[inline]
fn new(path: PathBuf, metadata: Metadata, depth: ScanEntryDepth) -> Self {
Self {
path,
metadata,
depth,
}
}
pub fn depth(&self) -> &ScanEntryDepth {
&self.depth
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn into_path(self) -> PathBuf {
self.path
}
pub fn into_metadata(self) -> Metadata {
self.metadata
}
pub fn into_path_and_metadata(self) -> (PathBuf, Metadata) {
(self.path, self.metadata)
}
}
pub struct DirectoryScanner {
base_path: PathBuf,
options: DirectoryScanOptions,
}
impl DirectoryScanner {
pub fn new<P>(base_directory_path: P, options: DirectoryScanOptions) -> Self
where
P: Into<PathBuf>,
{
Self {
base_path: base_directory_path.into(),
options,
}
}
}
impl IntoIterator for DirectoryScanner {
type IntoIter = BreadthFirstDirectoryIter;
type Item = Result<ScanEntry, DirectoryScanError>;
fn into_iter(self) -> Self::IntoIter {
BreadthFirstDirectoryIter::new(self.base_path, self.options)
}
}
pub(crate) fn is_directory_empty_unchecked(directory_path: &Path) -> std::io::Result<bool> {
let mut directory_read = fs::read_dir(directory_path)?;
let Some(first_entry_result) = directory_read.next() else {
return Ok(true);
};
first_entry_result?;
Ok(false)
}
pub fn is_directory_empty<P>(directory_path: P) -> Result<bool, DirectoryEmptinessScanError>
where
P: AsRef<Path>,
{
let directory_path: &Path = directory_path.as_ref();
let directory_metadata =
fs::metadata(directory_path).map_err(|_| DirectoryEmptinessScanError::NotFound {
path: directory_path.to_path_buf(),
})?;
if !directory_metadata.is_dir() {
return Err(DirectoryEmptinessScanError::NotADirectory {
path: directory_path.to_path_buf(),
});
}
let mut directory_read = fs::read_dir(directory_path).map_err(|error| {
DirectoryEmptinessScanError::UnableToReadDirectory {
directory_path: directory_path.to_path_buf(),
error,
}
})?;
let Some(first_entry_result) = directory_read.next() else {
return Ok(true);
};
if let Err(first_entry_error) = first_entry_result {
return Err(DirectoryEmptinessScanError::UnableToReadDirectoryEntry {
directory_path: directory_path.to_path_buf(),
error: first_entry_error,
});
}
Ok(false)
}