use qubit_command::{
Command,
CommandRunner,
};
use qubit_io::ReadSeek;
use std::path::Path;
use crate::{
FileBasedMimeDetector,
MimeConfig,
MimeDetectionPolicy,
MimeDetector,
MimeDetectorCore,
MimeRepository,
MimeResult,
};
use super::repository_mime_detector::default_repository;
#[derive(Debug, Clone)]
pub struct FileCommandMimeDetector<'a> {
core: MimeDetectorCore,
repository: &'a MimeRepository,
command_runner: CommandRunner,
}
impl FileCommandMimeDetector<'static> {
pub fn new() -> Self {
Self::with_repository(default_repository())
}
pub fn from_mime_config(config: MimeConfig) -> Self {
Self::with_repository_runner_and_config(
default_repository(),
Self::default_command_runner(),
config,
)
}
}
impl<'a> FileCommandMimeDetector<'a> {
pub const COMMAND: &'static str = "file";
pub const MIME_TYPE_ARG: &'static str = "--mime-type";
pub const BRIEF_ARG: &'static str = "--brief";
pub fn with_repository(repository: &'a MimeRepository) -> Self {
Self::with_repository_and_runner(repository, Self::default_command_runner())
}
pub fn with_repository_and_runner(
repository: &'a MimeRepository,
command_runner: CommandRunner,
) -> Self {
Self::with_repository_runner_and_config(repository, command_runner, MimeConfig::default())
}
pub fn with_repository_runner_and_config(
repository: &'a MimeRepository,
command_runner: CommandRunner,
config: MimeConfig,
) -> Self {
Self {
core: MimeDetectorCore::from_mime_config(config),
repository,
command_runner,
}
}
pub fn core(&self) -> &MimeDetectorCore {
&self.core
}
pub fn core_mut(&mut self) -> &mut MimeDetectorCore {
&mut self.core
}
pub fn repository(&self) -> &'a MimeRepository {
self.repository
}
pub fn command_runner(&self) -> &CommandRunner {
&self.command_runner
}
pub fn set_command_runner(&mut self, command_runner: CommandRunner) {
self.command_runner = command_runner;
}
pub fn with_command_runner(mut self, command_runner: CommandRunner) -> Self {
self.command_runner = command_runner;
self
}
pub fn detect_file_by_content(&self, file: &Path) -> MimeResult<Option<String>> {
Ok(self.guess_from_file_command(file)?.into_iter().next())
}
pub fn detect_file(
&self,
file: &Path,
policy: MimeDetectionPolicy,
) -> MimeResult<Option<String>> {
<Self as MimeDetector>::detect_file(self, file, policy)
}
pub fn detect_reader(
&self,
reader: &mut dyn ReadSeek,
filename: Option<&str>,
policy: MimeDetectionPolicy,
) -> MimeResult<Option<String>> {
<Self as MimeDetector>::detect_reader(self, reader, filename, policy)
}
pub fn is_available() -> bool {
CommandRunner::new()
.disable_logging(true)
.run(Self::command_for_path(Path::new(".")))
.is_ok()
}
fn guess_from_filename(&self, filename: &str) -> Vec<String> {
self.repository
.detect_by_filename(filename)
.into_iter()
.map(|mime_type| mime_type.name().to_owned())
.collect()
}
fn guess_from_file_command(&self, path: &Path) -> MimeResult<Vec<String>> {
let output = self.command_runner.run(Self::command_for_path(path))?;
let text = output.stdout_lossy_text();
let result = text.trim();
if result.is_empty() {
Ok(Vec::new())
} else {
Ok(vec![result.to_owned()])
}
}
fn default_command_runner() -> CommandRunner {
CommandRunner::new()
}
fn command_for_path(path: &Path) -> Command {
Command::new(Self::COMMAND)
.arg(Self::MIME_TYPE_ARG)
.arg(Self::BRIEF_ARG)
.arg_os(path)
}
}
impl Default for FileCommandMimeDetector<'static> {
fn default() -> Self {
Self::new()
}
}
impl<'a> FileBasedMimeDetector for FileCommandMimeDetector<'a> {
fn core(&self) -> &MimeDetectorCore {
&self.core
}
fn max_test_bytes(&self) -> usize {
self.repository.max_test_bytes()
}
fn guess_from_filename(&self, filename: &str) -> Vec<String> {
FileCommandMimeDetector::guess_from_filename(self, filename)
}
fn guess_from_local_file(&self, file: &Path) -> MimeResult<Vec<String>> {
self.guess_from_file_command(file)
}
}