diff --git a/Cargo.lock b/Cargo.lock index 3e5d2e4..54bad6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,6 +161,7 @@ dependencies = [ "camino", "clap", "m3u", + "once_cell", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5efb6b9..4ed3f7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ anyhow = "1.0.75" camino = "1.1.6" clap = { version = "4.4.0", features = ["derive"] } m3u = "1.0.0" +once_cell = "1.18.0" diff --git a/src/main.rs b/src/main.rs index 19ef8a5..d5d5237 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use anyhow::Result; use camino::{Utf8Path, Utf8PathBuf}; use clap::{Parser, Subcommand}; use m3u::Entry; +use once_cell::sync::Lazy; use std::collections::HashSet; /// Check if files in a playlist actually exist. @@ -28,17 +29,57 @@ enum Mode { }, } -fn get_directories(base_directory: &str, is_relative: bool) -> Result> { +static AUDIO_EXTENSIONS: Lazy> = + Lazy::new(|| HashSet::from(["m4a", "mp3", "ogg", "flac", "opus"])); + +fn has_audio_files(path: &Utf8Path) -> Result { + let any_audio_file = path + .read_dir_utf8()? + .map(|entry| { + let entry = entry?; + if entry.metadata()?.is_file() { + if let Some(extension) = entry.path().extension() { + Ok(AUDIO_EXTENSIONS.contains(extension)) + } else { + Ok(false) + } + } else { + Ok(false) + } + }) + .any(|res: Result| res.unwrap_or(false)); + Ok(any_audio_file) +} + +fn get_directories( + base_directory: &Utf8Path, + relative_to: Option, +) -> Result> { let mut result = HashSet::::new(); - for entry in Utf8Path::new(base_directory).read_dir_utf8()? { + for entry in 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() + // Skip dot directories + if entry.file_name().starts_with('.') { + continue; + } + let p = match &relative_to { + Some(base) => { + if base.is_empty() { + entry.file_name().to_owned() + } else { + format!("{base}/{dir}", dir = entry.file_name()) + } + } + None => entry.path().to_string(), }; - result.insert(p); + // The directory must have audio files to count + if has_audio_files(entry.path()).unwrap_or(false) { + result.insert(p.clone()); + } + // Recursively check + let relative_to = relative_to.as_ref().map(|_| p); + result.extend(get_directories(entry.path(), relative_to).unwrap_or_default()); } } Ok(result) @@ -71,7 +112,8 @@ fn main() -> Result<()> { } } } - let actual_directories = get_directories(&base_directory, relative)?; + let relative_to = if relative { Some(String::new()) } else { None }; + let actual_directories = get_directories(Utf8Path::new(&base_directory), relative_to)?; let mut missing_directories = actual_directories .difference(&playlist_directories) .collect::>();