#![deny(clippy::pedantic)]
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use clap::Parser;
use indicatif::{ParallelProgressIterator, ProgressStyle};
use itertools::{Either, Itertools};
use mqa_identify::identify_mqa;
use rayon::prelude::*;
use walkdir::WalkDir;
const PROGRESS_TEMPLATE: &str = "{spinner} [{elapsed_precise}] [{wide_bar}] {pos}/{len} ({eta})";
#[derive(Parser)]
#[command(version, about)]
struct Cli {
#[arg(required = true)]
paths: Vec<PathBuf>,
}
fn main() -> ExitCode {
let cli = Cli::parse();
let mut errors = Vec::new();
let flac_files = collect_flac_files(&cli.paths, &mut errors);
let style = ProgressStyle::with_template(PROGRESS_TEMPLATE)
.unwrap_or_else(|_| ProgressStyle::default_bar());
let (mqa_files, scan_errors): (Vec<PathBuf>, Vec<String>) = flac_files
.into_par_iter()
.progress_with_style(style)
.filter_map(|path| match identify_mqa(&path) {
Ok(true) => Some(Either::Left(path)),
Ok(false) => None,
Err(error) => Some(Either::Right(format!("{}: {error}", path.display()))),
})
.collect::<Vec<_>>()
.into_iter()
.partition_map(|result| result);
errors.extend(scan_errors);
match mqa_files.len() {
0 => println!("No MQA files found."),
1 => println!("Found 1 MQA file:\n{}", mqa_files[0].display()),
n => println!(
"Found {} MQA files:\n{}",
n,
mqa_files.iter().map(|path| path.display()).join("\n")
),
}
if errors.is_empty() {
return ExitCode::SUCCESS;
}
eprintln!("\n{} path(s) could not be checked:", errors.len());
for error in &errors {
eprintln!(" {error}");
}
ExitCode::FAILURE
}
fn collect_flac_files(paths: &[PathBuf], errors: &mut Vec<String>) -> Vec<PathBuf> {
let mut flac_files = Vec::new();
for path in paths {
for entry in WalkDir::new(path) {
match entry {
Ok(entry) if entry.file_type().is_file() && is_flac(entry.path()) => {
flac_files.push(entry.into_path());
}
Ok(_) => {}
Err(error) => errors.push(error.to_string()),
}
}
}
flac_files
}
fn is_flac(path: &Path) -> bool {
path.extension()
.is_some_and(|extension| extension.eq_ignore_ascii_case("flac"))
}