diff --git a/src/arguments.rs b/src/arguments.rs index ed075b7..4174f51 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -1,7 +1,8 @@ use clap::Parser; const NOTIFICATION_TIMEOUT: u8 = 5; -const NOTIFICATION_BODY: &str = "Playing: {title} from {album} \n\n Artist: {artist} - {year}"; +const NOTIFICATION_BODY: &str = + "Playing: {title} from {album} \n\n Artist: {artist} - {year}"; const NOTIFICATION_SUMMARY: &str = "{artist} - {title}"; const NOTIFICATION_APP_NAME: &str = "C* Music Player"; const DEFAULT_MAX_DEPTH: u8 = 3; diff --git a/src/cmus/mod.rs b/src/cmus/mod.rs index 5f28396..cc1fbef 100644 --- a/src/cmus/mod.rs +++ b/src/cmus/mod.rs @@ -34,7 +34,7 @@ pub enum CmusError { EmptyPath, DurationError(String), PositionError(String), - UnknownError(String) + UnknownError(String), } impl Display for CmusError { @@ -75,21 +75,43 @@ impl FromStr for Track { fn from_str(s: &str) -> Result { let mut lines = s.lines(); - Ok(Track::builder().status( - TrackStatus::from_str(lines.next().ok_or(CmusError::NoStatus)?.split_once(' ') - .ok_or(CmusError::NoStatus)?.1)? - ) - .path(lines.next().ok_or(CmusError::EmptyPath)?.split_once(' ') - .ok_or(CmusError::EmptyPath)?.1.to_string()) + Ok(Track::builder() + .status(TrackStatus::from_str( + lines + .next() + .ok_or(CmusError::NoStatus)? + .split_once(' ') + .ok_or(CmusError::NoStatus)? + .1, + )?) + .path( + lines + .next() + .ok_or(CmusError::EmptyPath)? + .split_once(' ') + .ok_or(CmusError::EmptyPath)? + .1 + .to_string(), + ) .duration( - lines.next().ok_or(CmusError::DurationError("Missing duration".to_string()))?.split_once(' ') - .ok_or(CmusError::DurationError("Empty duration".to_string()))?.1.parse() - .map_err(|e: ParseIntError| CmusError::DurationError(e.to_string()))? + lines + .next() + .ok_or(CmusError::DurationError("Missing duration".to_string()))? + .split_once(' ') + .ok_or(CmusError::DurationError("Empty duration".to_string()))? + .1 + .parse() + .map_err(|e: ParseIntError| CmusError::DurationError(e.to_string()))?, ) .position( - lines.next().ok_or(CmusError::PositionError("Missing position".to_string()))?.split_once(' ') - .ok_or(CmusError::PositionError("Empty position".to_string()))?.1.parse() - .map_err(|e: ParseIntError| CmusError::PositionError(e.to_string()))? + lines + .next() + .ok_or(CmusError::PositionError("Missing position".to_string()))? + .split_once(' ') + .ok_or(CmusError::PositionError("Empty position".to_string()))? + .1 + .parse() + .map_err(|e: ParseIntError| CmusError::PositionError(e.to_string()))?, ) .metadata(TrackMetadata::parse(lines)) .build()) @@ -101,7 +123,7 @@ impl TrackMetadata { /// This function will assume you processed the first 4 lines, and remove them from the iterator. /// /// and also assume the all tags is contained in the iterator. - fn parse<'a>(mut lines: impl Iterator) -> Self { + fn parse<'a>(mut lines: impl Iterator) -> Self { let mut tags = HashMap::new(); while let Some(line) = lines.next() { @@ -129,8 +151,15 @@ impl Track { /// /// This is the title, if it exists, otherwise it's the file name without the extension. pub fn get_name(&self) -> &str { - self.metadata.get("title").unwrap_or_else(|| self.path.split('/').last() - .unwrap_or("").split_once(".").unwrap_or(("", "")).0) + self.metadata.get("title").unwrap_or_else(|| { + self.path + .split('/') + .last() + .unwrap_or("") + .split_once(".") + .unwrap_or(("", "")) + .0 + }) } } @@ -140,14 +169,18 @@ impl Track { #[inline] pub fn get_track(query_command: &mut std::process::Command) -> Result { // Just run the command, and collect the output. - let output = query_command.output().map_err(|e| CmusError::CmusRunningError(e.to_string()))?; + let output = query_command + .output() + .map_err(|e| CmusError::CmusRunningError(e.to_string()))?; if !output.status.success() { - return Err(CmusError::CmusRunningError(String::from_utf8(output.stderr) - .map_err(|e| CmusError::UnknownError(e.to_string()))?)); + return Err(CmusError::CmusRunningError( + String::from_utf8(output.stderr).map_err(|e| CmusError::UnknownError(e.to_string()))?, + )); } - let output = String::from_utf8(output.stdout).map_err(|e| CmusError::UnknownError(e.to_string()))?; + let output = + String::from_utf8(output.stdout).map_err(|e| CmusError::UnknownError(e.to_string()))?; Track::from_str(&output).map_err(|e| CmusError::UnknownError(e.to_string())) } @@ -156,7 +189,11 @@ pub fn get_track(query_command: &mut std::process::Command) -> Result, socket_pass: &Option) -> std::process::Command { +pub fn build_query_command( + cmus_remote_bin: &str, + socket_addr: &Option, + socket_pass: &Option, +) -> std::process::Command { let cmd_arr = cmus_remote_bin.split_whitespace().collect::>(); let mut command = std::process::Command::new(cmd_arr[0]); @@ -178,13 +215,13 @@ pub fn build_query_command(cmus_remote_bin: &str, socket_addr: &Option, command } - #[cfg(test)] mod tests { use super::*; use std::assert_matches::assert_matches; - const OUTPUT_WITH_ALL_TAGS: &str = include_str!("../../tests/samples/cmus-remote-output-with-all-tags.txt"); + const OUTPUT_WITH_ALL_TAGS: &str = + include_str!("../../tests/samples/cmus-remote-output-with-all-tags.txt"); const SOME_TAGS: &str = r#"tag artist Alex Goot tag album Alex Goot & Friends, Vol. 3 @@ -214,18 +251,48 @@ mod tests { assert_eq!(track.path, "/mnt/Data/Music/FLAC/Alex Goot/Alex Goot - Alex Goot & Friends, Vol. 3/08 - Photograph.mp3"); assert_eq!(track.duration, 284); assert_eq!(track.position, 226); - assert_eq!(track.metadata.tags.get("artist"), Some(&"Alex Goot".to_string())); - assert_eq!(track.metadata.tags.get("album"), Some(&"Alex Goot & Friends, Vol. 3".to_string())); - assert_eq!(track.metadata.tags.get("title"), Some(&"Photograph".to_string())); + assert_eq!( + track.metadata.tags.get("artist"), + Some(&"Alex Goot".to_string()) + ); + assert_eq!( + track.metadata.tags.get("album"), + Some(&"Alex Goot & Friends, Vol. 3".to_string()) + ); + assert_eq!( + track.metadata.tags.get("title"), + Some(&"Photograph".to_string()) + ); assert_eq!(track.metadata.tags.get("date"), Some(&"2014".to_string())); assert_eq!(track.metadata.tags.get("genre"), Some(&"Pop".to_string())); - assert_eq!(track.metadata.tags.get("discnumber"), Some(&"1".to_string())); - assert_eq!(track.metadata.tags.get("tracknumber"), Some(&"8".to_string())); - assert_eq!(track.metadata.tags.get("albumartist"), Some(&"Alex Goot".to_string())); - assert_eq!(track.metadata.tags.get("replaygain_track_gain"), Some(&"-9.4 dB".to_string())); - assert_eq!(track.metadata.tags.get("composer"), Some(&"Chad Kroeger".to_string())); - assert_eq!(track.metadata.tags.get("label"), Some(&"mudhutdigital.com".to_string())); - assert_eq!(track.metadata.tags.get("publisher"), Some(&"mudhutdigital.com".to_string())); + assert_eq!( + track.metadata.tags.get("discnumber"), + Some(&"1".to_string()) + ); + assert_eq!( + track.metadata.tags.get("tracknumber"), + Some(&"8".to_string()) + ); + assert_eq!( + track.metadata.tags.get("albumartist"), + Some(&"Alex Goot".to_string()) + ); + assert_eq!( + track.metadata.tags.get("replaygain_track_gain"), + Some(&"-9.4 dB".to_string()) + ); + assert_eq!( + track.metadata.tags.get("composer"), + Some(&"Chad Kroeger".to_string()) + ); + assert_eq!( + track.metadata.tags.get("label"), + Some(&"mudhutdigital.com".to_string()) + ); + assert_eq!( + track.metadata.tags.get("publisher"), + Some(&"mudhutdigital.com".to_string()) + ); assert_eq!(track.metadata.tags.get("bpm"), Some(&"146".to_string())); } @@ -234,17 +301,35 @@ mod tests { let metadata = TrackMetadata::parse(SOME_TAGS.lines()); assert_eq!(metadata.tags.get("artist"), Some(&"Alex Goot".to_string())); - assert_eq!(metadata.tags.get("album"), Some(&"Alex Goot & Friends, Vol. 3".to_string())); + assert_eq!( + metadata.tags.get("album"), + Some(&"Alex Goot & Friends, Vol. 3".to_string()) + ); assert_eq!(metadata.tags.get("title"), Some(&"Photograph".to_string())); assert_eq!(metadata.tags.get("date"), Some(&"2014".to_string())); assert_eq!(metadata.tags.get("genre"), Some(&"Pop".to_string())); assert_eq!(metadata.tags.get("discnumber"), Some(&"1".to_string())); assert_eq!(metadata.tags.get("tracknumber"), Some(&"8".to_string())); - assert_eq!(metadata.tags.get("albumartist"), Some(&"Alex Goot".to_string())); - assert_eq!(metadata.tags.get("replaygain_track_gain"), Some(&"-9.4 dB".to_string())); - assert_eq!(metadata.tags.get("composer"), Some(&"Chad Kroeger".to_string())); - assert_eq!(metadata.tags.get("label"), Some(&"mudhutdigital.com".to_string())); - assert_eq!(metadata.tags.get("publisher"), Some(&"mudhutdigital.com".to_string())); + assert_eq!( + metadata.tags.get("albumartist"), + Some(&"Alex Goot".to_string()) + ); + assert_eq!( + metadata.tags.get("replaygain_track_gain"), + Some(&"-9.4 dB".to_string()) + ); + assert_eq!( + metadata.tags.get("composer"), + Some(&"Chad Kroeger".to_string()) + ); + assert_eq!( + metadata.tags.get("label"), + Some(&"mudhutdigital.com".to_string()) + ); + assert_eq!( + metadata.tags.get("publisher"), + Some(&"mudhutdigital.com".to_string()) + ); assert_eq!(metadata.tags.get("bpm"), Some(&"146".to_string())); } @@ -258,18 +343,29 @@ mod tests { #[test] fn test_build_the_query_command_with_custom_socket_and_no_pass() { - let command = build_query_command("cmus-remote", &Some("/tmp/cmus-socket".to_string()), &None); + let command = + build_query_command("cmus-remote", &Some("/tmp/cmus-socket".to_string()), &None); assert_eq!(command.get_program(), "cmus-remote"); - assert_eq!(command.get_args().collect::>(), &["--server", "/tmp/cmus-socket", "-Q"]); + assert_eq!( + command.get_args().collect::>(), + &["--server", "/tmp/cmus-socket", "-Q"] + ); } #[test] fn test_build_the_query_command_with_custom_socket_and_pass() { - let command = build_query_command("cmus-remote", &Some("/tmp/cmus-socket".to_string()), &Some("pass".to_string())); + let command = build_query_command( + "cmus-remote", + &Some("/tmp/cmus-socket".to_string()), + &Some("pass".to_string()), + ); assert_eq!(command.get_program(), "cmus-remote"); - assert_eq!(command.get_args().collect::>(), &["--server", "/tmp/cmus-socket", "--passwd", "pass", "-Q"]); + assert_eq!( + command.get_args().collect::>(), + &["--server", "/tmp/cmus-socket", "--passwd", "pass", "-Q"] + ); } #[test] @@ -277,6 +373,9 @@ mod tests { let command = build_query_command("flatpak run io.github.cmus.cmus", &None, &None); assert_eq!(command.get_program(), "flatpak"); - assert_eq!(command.get_args().collect::>(), &["run", "io.github.cmus.cmus", "-Q"]); + assert_eq!( + command.get_args().collect::>(), + &["run", "io.github.cmus.cmus", "-Q"] + ); } } diff --git a/src/main.rs b/src/main.rs index 4fa98eb..9a664c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,11 @@ fn main() { // Build the command, or use the default. (to speed up the main loop, because we don't need to build it every time) let mut query_command = cmus::build_query_command( - &args.cmus_remote_bin_path.unwrap_or("cmus-remote".to_string()).as_str(), - &args.cmus_socket_address, &args.cmus_socket_password); + &args + .cmus_remote_bin_path + .unwrap_or("cmus-remote".to_string()) + .as_str(), + &args.cmus_socket_address, + &args.cmus_socket_password, + ); } diff --git a/src/utils.rs b/src/utils.rs index c33a137..10472dc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,10 +1,14 @@ -use std::path::Path; use crate::cmus; +use std::path::Path; /// Search in the track directory for the cover image or the lyrics(depending on the `regx`). /// If the cover image or the lyrics is not found, search in the parent directory, and so on, until the max depth is reached. /// If the cover image or the lyrics is not found, return `None`. -pub fn search_for(search_directory: &str, max_depth: u8, regx: &[&str]) -> std::io::Result> { +pub fn search_for( + search_directory: &str, + max_depth: u8, + regx: &[&str], +) -> std::io::Result> { // Search in the track directory. for entry in std::fs::read_dir(search_directory)? { if let Ok(entry) = entry { @@ -24,7 +28,8 @@ pub fn search_for(search_directory: &str, max_depth: u8, regx: &[&str]) -> std:: // If the max depth is reached, return `None`. if max_depth == 0 { Ok(None) - } else { // If the max depth is not reached, search in the parent directory (recursively). + } else { + // If the max depth is not reached, search in the parent directory (recursively). let Some(parent) = Path::new(search_directory).parent() else { return Ok(None); }; let Some(parent) = parent.to_str() else { return Ok(None); }; search_for(parent, max_depth - 1, regx) @@ -41,11 +46,15 @@ pub fn process_template_placeholders(template: &String, track: &cmus::Track) -> for c in template.chars() { if c == '{' { key = String::new(); - } else if c == '}' { // Replace the key with their matching value if exists, if not replace with the empty string. - processed = processed.replace(&format!("{{{}}}", key), match key.as_str() { - "title" => track.get_name(), - _ => track.metadata.get(&key).unwrap_or(""), - }); + } else if c == '}' { + // Replace the key with their matching value if exists, if not replace with the empty string. + processed = processed.replace( + &format!("{{{}}}", key), + match key.as_str() { + "title" => track.get_name(), + _ => track.metadata.get(&key).unwrap_or(""), + }, + ); } else { key.push(c); } @@ -67,7 +76,10 @@ mod tests { impl TestContext for TestContextWithFullTrack { fn setup() -> Self { Self { - track: cmus::Track::from_str(include_str!("../tests/samples/cmus-remote-output-with-all-tags.txt")).unwrap() + track: cmus::Track::from_str(include_str!( + "../tests/samples/cmus-remote-output-with-all-tags.txt" + )) + .unwrap(), } } } @@ -78,6 +90,9 @@ mod tests { let cover_path_template = String::from("{title}/{artist}/{album}/{tracknumber}"); let cover_path = process_template_placeholders(&cover_path_template, &ctx.track); - assert_eq!(cover_path, "Photograph/Alex Goot/Alex Goot & Friends, Vol. 3/8"); + assert_eq!( + cover_path, + "Photograph/Alex Goot/Alex Goot & Friends, Vol. 3/8" + ); } }