use anyhow::Result;
use owo_colors::OwoColorize;
use super::Registry;
use super::types::{Capabilities, CredentialState};
use crate::config::{Config, Credentials};
use crate::proc;
const TOOLS: &[(&str, &str, &str)] = &[
("yt-dlp", "--version", "soundcloud search and rip"),
("ffmpeg", "-version", "audio decode for fingerprinting"),
(
"ffprobe",
"-version",
"precise duration for the speed pre-filter",
),
];
pub fn run(cfg: &Config, creds: &Credentials, config_path: &std::path::Path) -> Result<()> {
let reg = Registry::from_config(cfg, creds);
println!("{}", "config".bold().cyan());
let exists = if config_path.exists() {
"".to_string()
} else {
format!(" {}", "(not created — using defaults)".dimmed())
};
println!(" {}{}", config_path.display(), exists);
println!(" downloads: {}", cfg.download_dir()?.display());
match super::format_preference(cfg) {
Ok(prefs) => println!(
" formats: {}",
prefs
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join(" → ")
),
Err(e) => println!(" formats: {}", e.to_string().red()),
}
println!();
println!("{}", "backends".bold().cyan());
if reg.is_empty() {
println!(" {}", "none enabled".dimmed());
}
for b in reg.iter() {
let caps = b.capabilities();
println!(
" {} {}",
b.id().to_string().bold(),
caps_summary(&caps).dimmed()
);
match b.credentials() {
CredentialState::NotRequired => {
println!(" auth: {}", "not required".green())
}
CredentialState::Present { hint } => {
println!(" auth: {} ({hint})", "configured".green())
}
CredentialState::Missing { how_to_fix } => {
println!(" auth: {} — {how_to_fix}", "missing".yellow())
}
CredentialState::Malformed { detail } => {
println!(" auth: {} — {detail}", "malformed".red())
}
}
}
println!();
println!("{}", "external tools".bold().cyan());
let mut missing = Vec::new();
for (tool, flag, why) in TOOLS {
let path = if *tool == "yt-dlp" {
cfg.soundcloud.yt_dlp_path.as_str()
} else {
tool
};
if proc::tool_available(path, flag) {
println!(
" {} {path} {}",
"ok ".green(),
format!("— {why}").dimmed()
);
} else {
println!(" {} {path} {}", "MISS".red(), format!("— {why}").dimmed());
missing.push(*tool);
}
}
if !missing.is_empty() {
eprintln!();
eprintln!(
"{} missing: {}. Install with: brew install {}",
"warning:".yellow(),
missing.join(", "),
missing.join(" ")
);
}
Ok(())
}
fn caps_summary(c: &Capabilities) -> String {
let mut bits = Vec::new();
if c.search {
bits.push("search");
}
if c.price_quotes {
bits.push("prices");
}
if c.ownership_check {
bits.push("ownership");
}
if c.fetch {
bits.push("fetch");
}
if c.lossless_capable {
bits.push("lossless");
}
if c.requires_purchase {
bits.push("purchase-gated");
}
if bits.is_empty() {
"(no capabilities)".to_string()
} else {
format!("[{}]", bits.join(", "))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn caps_summary_lists_only_what_is_supported() {
let s = caps_summary(&Capabilities {
search: true,
fetch: true,
lossless_capable: true,
..Default::default()
});
assert_eq!(s, "[search, fetch, lossless]");
}
#[test]
fn caps_summary_says_so_when_there_is_nothing() {
assert_eq!(caps_summary(&Capabilities::default()), "(no capabilities)");
}
#[test]
fn report_runs_without_a_config_file_or_a_database() {
let missing = std::path::Path::new("/nonexistent/rekord-ripper/config.toml");
run(&Config::default(), &Credentials::default(), missing).unwrap();
}
}