use crate::filter::{FilterResult, FilterSettings};
use crate::progress::Progress;
use anyhow::Context;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
File,
Dir,
Symlink,
Special,
}
impl EntryKind {
#[must_use]
pub fn from_metadata(metadata: &std::fs::Metadata) -> Self {
if metadata.is_dir() {
Self::Dir
} else if metadata.is_symlink() {
Self::Symlink
} else if metadata.is_file() {
Self::File
} else {
Self::Special
}
}
#[must_use]
pub fn from_file_type(file_type: Option<&std::fs::FileType>) -> Self {
match file_type {
Some(ft) if ft.is_dir() => Self::Dir,
Some(ft) if ft.is_symlink() => Self::Symlink,
Some(ft) if ft.is_file() => Self::File,
Some(_) => Self::Special,
None => Self::File,
}
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Dir => "dir",
Self::Symlink => "symlink",
Self::File | Self::Special => "file",
}
}
#[must_use]
pub fn label_long(self) -> &'static str {
match self {
Self::Dir => "directory",
Self::Symlink => "symlink",
Self::File | Self::Special => "file",
}
}
pub fn inc_skipped(self, prog: &Progress) {
match self {
Self::Dir => prog.directories_skipped.inc(),
Self::Symlink => prog.symlinks_skipped.inc(),
Self::File | Self::Special => prog.files_skipped.inc(),
}
}
}
pub(crate) fn throttle_side(side: congestion::Side) -> throttle::Side {
match side {
congestion::Side::Source => throttle::Side::Source,
congestion::Side::Destination => throttle::Side::Destination,
}
}
pub(crate) fn throttle_op(op: congestion::MetadataOp) -> throttle::MetadataOp {
match op {
congestion::MetadataOp::Stat => throttle::MetadataOp::Stat,
congestion::MetadataOp::ReadLink => throttle::MetadataOp::ReadLink,
congestion::MetadataOp::MkDir => throttle::MetadataOp::MkDir,
congestion::MetadataOp::RmDir => throttle::MetadataOp::RmDir,
congestion::MetadataOp::Unlink => throttle::MetadataOp::Unlink,
congestion::MetadataOp::HardLink => throttle::MetadataOp::HardLink,
congestion::MetadataOp::Symlink => throttle::MetadataOp::Symlink,
congestion::MetadataOp::Chmod => throttle::MetadataOp::Chmod,
congestion::MetadataOp::OpenCreate => throttle::MetadataOp::OpenCreate,
}
}
pub(crate) fn meta_resource(
side: congestion::Side,
op: congestion::MetadataOp,
) -> throttle::Resource {
throttle::Resource::meta(throttle_side(side), throttle_op(op))
}
pub async fn next_entry_probed<F>(
entries: &mut tokio::fs::ReadDir,
_side: congestion::Side,
context: F,
) -> anyhow::Result<Option<(tokio::fs::DirEntry, Option<std::fs::FileType>)>>
where
F: FnOnce() -> String,
{
throttle::get_ops_token().await;
let maybe_entry = entries.next_entry().await.with_context(context)?;
let Some(entry) = maybe_entry else {
return Ok(None);
};
let entry_file_type = entry.file_type().await.ok();
Ok(Some((entry, entry_file_type)))
}
pub async fn run_metadata_probed<F, T, E>(
side: congestion::Side,
op_kind: congestion::MetadataOp,
fut: F,
) -> Result<T, E>
where
F: std::future::Future<Output = Result<T, E>>,
{
throttle::get_ops_token().await;
run_metadata_probed_no_rate(side, op_kind, fut).await
}
pub async fn run_metadata_probed_no_rate<F, T, E>(
side: congestion::Side,
op_kind: congestion::MetadataOp,
fut: F,
) -> Result<T, E>
where
F: std::future::Future<Output = Result<T, E>>,
{
let ops_permit = throttle::ops_in_flight_permit(meta_resource(side, op_kind)).await;
let probe = congestion::Probe::start_metadata(side, op_kind);
let result = fut.await;
match &result {
Ok(_) => probe.complete_ok(0),
Err(_) => probe.discard(),
}
drop(ops_permit);
result
}
#[must_use]
pub fn should_skip_entry(
filter: &Option<FilterSettings>,
relative_path: &std::path::Path,
is_dir: bool,
) -> Option<FilterResult> {
if let Some(f) = filter {
let result = f.should_include(relative_path, is_dir);
match result {
FilterResult::Included => None,
_ => Some(result),
}
} else {
None
}
}
#[must_use]
pub fn relative_to_root<'a>(
entry: &'a std::path::Path,
root: &std::path::Path,
) -> &'a std::path::Path {
entry.strip_prefix(root).unwrap_or(entry)
}