use crate::{
LayerIndex, NormalizedPath, SourceKind, SourceMeta, VfsKeyInput,
paths::{key_to_path_buf_bytes, key_to_path_buf_lossy, normalized_safe_key},
};
use ahash::{AHashMap, AHashSet};
use rayon::prelude::*;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
#[derive(Debug, Default)]
pub struct SourceConflicts {
pub overrides: AHashSet<PathBuf>,
pub overridden_by: AHashSet<PathBuf>,
}
impl SourceConflicts {
#[must_use]
pub fn has_overrides(&self) -> bool {
!self.overrides.is_empty()
}
#[must_use]
pub fn is_overridden(&self) -> bool {
!self.overridden_by.is_empty()
}
}
pub struct ConflictIndex {
pub sources: Vec<PathBuf>,
pub(super) source_meta: Vec<SourceMeta>,
pub conflicts: Vec<SourceConflicts>,
pub(super) source_file_counts: Vec<usize>,
path_to_sources: AHashMap<NormalizedPath, Vec<usize>>,
}
impl ConflictIndex {
#[must_use]
pub fn from_layer_index(layer: &LayerIndex) -> Self {
let sources = layer.sources.clone();
let mut source_file_counts = vec![0; sources.len()];
let mut path_to_sources: AHashMap<NormalizedPath, Vec<usize>> = AHashMap::new();
for key in layer.keys() {
let providers = layer.sources_containing(&key);
let mut unique_sources = Vec::new();
let mut seen_sources = AHashSet::new();
for &source_idx in providers {
let source = &sources[source_idx];
let source_identity = (
source.kind,
crate::paths::normalize_host_path(&source.path).into_owned(),
);
if seen_sources.insert(source_identity) {
unique_sources.push(source_idx);
}
}
for &source_idx in &unique_sources {
source_file_counts[source_idx] += 1;
}
if unique_sources.len() > 1 {
path_to_sources.insert(key, unique_sources);
}
}
Self::from_provider_map(sources, source_file_counts, path_to_sources)
}
fn from_provider_map(
source_meta: Vec<SourceMeta>,
source_file_counts: Vec<usize>,
path_to_sources: AHashMap<NormalizedPath, Vec<usize>>,
) -> Self {
let mut conflicts: Vec<SourceConflicts> = (0..source_meta.len())
.map(|_| SourceConflicts::default())
.collect();
for (key, source_indices) in &path_to_sources {
let path = key_to_path_buf_lossy(key);
for (pos, &src_idx) in source_indices.iter().enumerate() {
if pos > 0 {
conflicts[src_idx].overrides.insert(path.clone());
}
if pos < source_indices.len() - 1 {
conflicts[src_idx].overridden_by.insert(path.clone());
}
}
}
let sources = source_meta
.iter()
.map(|source| source.path.clone())
.collect();
Self {
sources,
source_meta,
conflicts,
source_file_counts,
path_to_sources,
}
}
pub(super) fn walk_dir(dir: &Path) -> Vec<PathBuf> {
WalkDir::new(dir)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok().filter(|e| e.file_type().is_file()))
.par_bridge()
.filter_map(|entry| {
let relative = entry
.path()
.strip_prefix(dir)
.expect("entry must be prefixed by scan dir")
.to_path_buf();
normalized_safe_key(&relative).and_then(|key| key_to_path_buf_bytes(&key))
})
.collect()
}
pub fn from_file_lists(sources: impl IntoIterator<Item = (PathBuf, Vec<PathBuf>)>) -> Self {
let layer = LayerIndex::from_file_lists(sources.into_iter().map(|(path, files)| {
(
SourceMeta {
path,
kind: SourceKind::LooseDir,
},
files,
)
}));
Self::from_layer_index(&layer)
}
pub fn from_directories(dirs: impl IntoIterator<Item = impl AsRef<Path> + Sync>) -> Self {
let sources: Vec<(PathBuf, Vec<PathBuf>)> = dirs
.into_iter()
.map(|d| {
let d = d.as_ref().to_path_buf();
let files = Self::walk_dir(&d);
(d, files)
})
.collect();
Self::from_file_lists(sources)
}
pub fn sources_containing(&self, path: &Path) -> &[usize] {
let normalized = path.to_vfs_key();
self.path_to_sources
.get(&normalized)
.map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn displaced_by(&self, source_index: usize, path: &Path) -> Option<usize> {
let indices = self.sources_containing(path);
let pos = indices.iter().position(|&i| i == source_index)?;
if pos == 0 {
return None;
}
Some(indices[pos - 1])
}
#[must_use]
pub fn overridden_by_dir(&self, source_index: usize, path: &Path) -> Option<usize> {
let indices = self.sources_containing(path);
let pos = indices.iter().position(|&i| i == source_index)?;
if pos == indices.len() - 1 {
return None;
}
Some(indices[pos + 1])
}
}