use std::collections::HashSet;
use std::path::Path;
use tracing::trace;
use crate::file::{FileEntry, Result};
pub struct DedupByRealpath<I> {
inner: I,
seen_targets: HashSet<String>,
seen_aliased_lnks: HashSet<(String, String)>,
}
impl<I> DedupByRealpath<I> {
pub fn new(inner: I) -> Self {
Self {
inner,
seen_targets: HashSet::new(),
seen_aliased_lnks: HashSet::new(),
}
}
}
impl<I> Iterator for DedupByRealpath<I>
where
I: Iterator<Item = Result<Box<dyn FileEntry>>>,
{
type Item = Result<Box<dyn FileEntry>>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let item = self.inner.next()?;
let Ok(entry) = &item else {
return Some(item);
};
if dedup_decision(
&mut self.seen_targets,
&mut self.seen_aliased_lnks,
entry.path(),
entry.link_path(),
) {
return Some(item);
}
}
}
}
pub fn dedup_decision(
seen_targets: &mut HashSet<String>,
seen_aliased_lnks: &mut HashSet<(String, String)>,
target: &Path,
link_path: Option<&Path>,
) -> bool {
let target_key = target.to_string_lossy().to_lowercase();
let lnk_stem = link_path
.filter(|p| {
p.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("lnk"))
})
.and_then(|p| p.file_stem())
.map(|s| s.to_string_lossy().to_lowercase());
let target_stem = target
.file_stem()
.map(|s| s.to_string_lossy().to_lowercase());
let is_aliased_lnk = matches!(
(&lnk_stem, &target_stem),
(Some(l), Some(t)) if l != t
);
let kept = if is_aliased_lnk {
seen_aliased_lnks.insert((target_key.clone(), lnk_stem.clone().unwrap()))
} else {
seen_targets.insert(target_key.clone())
};
trace!(
target = %target_key,
link = ?lnk_stem,
aliased = is_aliased_lnk,
kept,
"dedup decision",
);
kept
}