#[cfg(coverage)]
use std::sync::PoisonError;
use std::sync::{
Arc,
LazyLock,
RwLock,
RwLockReadGuard,
RwLockWriteGuard,
};
use crate::{
BoxMimeDetector,
MimeConfig,
MimeError,
MimeResult,
};
use super::{
FileCommandMimeDetectorProvider,
MimeDetectorAvailability,
MimeDetectorProvider,
RepositoryMimeDetectorProvider,
};
#[derive(Debug, Clone, Default)]
pub struct MimeDetectorRegistry {
providers: Vec<Arc<dyn MimeDetectorProvider>>,
}
static DEFAULT_MIME_DETECTOR_REGISTRY: LazyLock<RwLock<MimeDetectorRegistry>> =
LazyLock::new(|| RwLock::new(MimeDetectorRegistry::builtin()));
#[cfg(not(coverage))]
const BACKEND: &str = "mime-detector-registry";
#[cfg(not(coverage))]
const LOCK_ERR: &str = "lock poisoned";
impl MimeDetectorRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn builtin() -> Self {
Self {
providers: vec![
Arc::new(RepositoryMimeDetectorProvider) as Arc<dyn MimeDetectorProvider>,
Arc::new(FileCommandMimeDetectorProvider) as Arc<dyn MimeDetectorProvider>,
],
}
}
pub fn default_registry() -> MimeResult<Self> {
let registry = read_default_registry()?;
Ok(registry.clone())
}
pub fn register_default<P>(provider: P) -> MimeResult<()>
where
P: MimeDetectorProvider + 'static,
{
let mut registry = write_default_registry()?;
registry.register(provider)
}
pub fn register<P>(&mut self, provider: P) -> MimeResult<()>
where
P: MimeDetectorProvider + 'static,
{
self.register_arc(Arc::new(provider))
}
pub fn register_arc(&mut self, provider: Arc<dyn MimeDetectorProvider>) -> MimeResult<()> {
for name in provider_names(provider.as_ref()) {
if self.find_provider(&name).is_some() {
return Err(MimeError::DuplicateDetectorName { name });
}
}
self.providers.push(provider);
Ok(())
}
pub fn provider_names(&self) -> Vec<&'static str> {
self.providers
.iter()
.map(|provider| provider.id())
.collect()
}
pub fn find_provider(&self, name: &str) -> Option<&dyn MimeDetectorProvider> {
self.providers
.iter()
.map(Arc::as_ref)
.find(|provider| provider_matches(*provider, name))
}
pub fn create(&self, name: &str, config: &MimeConfig) -> MimeResult<BoxMimeDetector> {
let provider = self
.find_provider(name)
.ok_or_else(|| MimeError::UnknownDetector {
name: name.to_owned(),
})?;
match provider.availability(config) {
MimeDetectorAvailability::Available => {
provider.create(config).map(BoxMimeDetector::new)
}
MimeDetectorAvailability::Unavailable { reason } => {
Err(MimeError::DetectorUnavailable {
name: name.to_owned(),
reason,
})
}
}
}
pub fn create_default(&self, config: &MimeConfig) -> MimeResult<BoxMimeDetector> {
let candidates = self.default_candidates(config);
if candidates.is_empty() {
return Err(MimeError::NoAvailableDetector {
reason: "detector registry is empty".to_owned(),
});
}
let mut errors = Vec::new();
for candidate in candidates {
match self.create(&candidate, config) {
Ok(detector) => return Ok(detector),
Err(error) => errors.push(error.to_string()),
}
}
Err(MimeError::NoAvailableDetector {
reason: errors.join("; "),
})
}
fn default_candidates(&self, config: &MimeConfig) -> Vec<String> {
let configured = config.mime_detector_default().trim();
if configured.is_empty() || configured.eq_ignore_ascii_case("auto") {
return self.auto_candidates(config);
}
let mut candidates = vec![configured.to_owned()];
candidates.extend(config.mime_detector_fallbacks().iter().cloned());
candidates
}
fn auto_candidates(&self, config: &MimeConfig) -> Vec<String> {
let mut providers: Vec<&dyn MimeDetectorProvider> = self
.providers
.iter()
.map(Arc::as_ref)
.filter(|provider| provider.availability(config).is_available())
.collect();
providers.sort_by(|left, right| {
right
.priority()
.cmp(&left.priority())
.then_with(|| left.id().cmp(right.id()))
});
providers
.into_iter()
.map(|provider| provider.id().to_owned())
.collect()
}
}
fn provider_names(provider: &dyn MimeDetectorProvider) -> Vec<String> {
let mut names = Vec::with_capacity(provider.aliases().len() + 1);
names.push(provider.id().to_owned());
names.extend(provider.aliases().iter().map(|alias| (*alias).to_owned()));
names
}
fn provider_matches(provider: &dyn MimeDetectorProvider, name: &str) -> bool {
provider.id().eq_ignore_ascii_case(name)
|| provider
.aliases()
.iter()
.any(|alias| alias.eq_ignore_ascii_case(name))
}
#[cfg(not(coverage))]
fn read_default_registry() -> MimeResult<RwLockReadGuard<'static, MimeDetectorRegistry>> {
match DEFAULT_MIME_DETECTOR_REGISTRY.read() {
Ok(registry) => Ok(registry),
Err(_) => Err(MimeError::DetectorBackend {
backend: BACKEND.into(),
reason: LOCK_ERR.into(),
}),
}
}
#[cfg(coverage)]
fn read_default_registry() -> MimeResult<RwLockReadGuard<'static, MimeDetectorRegistry>> {
Ok(DEFAULT_MIME_DETECTOR_REGISTRY
.read()
.unwrap_or_else(PoisonError::into_inner))
}
#[cfg(not(coverage))]
fn write_default_registry() -> MimeResult<RwLockWriteGuard<'static, MimeDetectorRegistry>> {
match DEFAULT_MIME_DETECTOR_REGISTRY.write() {
Ok(registry) => Ok(registry),
Err(_) => Err(MimeError::DetectorBackend {
backend: BACKEND.into(),
reason: LOCK_ERR.into(),
}),
}
}
#[cfg(coverage)]
fn write_default_registry() -> MimeResult<RwLockWriteGuard<'static, MimeDetectorRegistry>> {
Ok(DEFAULT_MIME_DETECTOR_REGISTRY
.write()
.unwrap_or_else(PoisonError::into_inner))
}