use anyhow::Result; use camino::{Utf8Path, Utf8PathBuf}; use clap::{Parser, Subcommand}; use m3u::Entry; use std::collections::HashSet; /// Check if files in a playlist actually exist. #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// Check mode. #[command(subcommand)] mode: Mode, /// Playlist file name. input: String, } #[derive(Subcommand, Clone, Debug)] enum Mode { #[command()] Files, #[command()] Directories { #[arg(long, short)] base_directory: String, #[arg(long, short, default_value_t = false)] relative: bool, }, } fn get_directories(base_directory: &str, is_relative: bool) -> Result> { let mut result = HashSet::::new(); for entry in Utf8Path::new(base_directory).read_dir_utf8()? { let entry = entry?; if entry.metadata()?.is_dir() { let p = if is_relative { entry.file_name().to_string() } else { entry.path().to_string() }; result.insert(p); } } Ok(result) } fn main() -> Result<()> { let args = Args::parse(); let mut reader = m3u::Reader::open_ext(&args.input)?; match args.mode { Mode::Files => { for entry in reader.entry_exts() { if let Entry::Path(p) = entry?.entry { let p = Utf8PathBuf::try_from(p)?; if std::fs::metadata(&p).is_err() { println!("{p}"); } } } } Mode::Directories { base_directory, relative, } => { let mut playlist_directories = HashSet::::new(); for entry in reader.entry_exts() { if let Entry::Path(p) = entry?.entry { let p = Utf8PathBuf::try_from(p)?; if let Some(dir) = p.parent() { playlist_directories.insert(dir.to_string()); } } } let actual_directories = get_directories(&base_directory, relative)?; let mut missing_directories = actual_directories .difference(&playlist_directories) .into_iter() .collect::>(); missing_directories.sort_unstable(); for d in missing_directories { println!("{}", d); } } } Ok(()) }