format the code :)
This commit is contained in:
parent
be412d5f57
commit
a5dd196937
4 changed files with 177 additions and 57 deletions
|
@ -1,7 +1,8 @@
|
|||
use clap::Parser;
|
||||
|
||||
const NOTIFICATION_TIMEOUT: u8 = 5;
|
||||
const NOTIFICATION_BODY: &str = "<b>Playing:</b> {title} from {album} \n\n <b>Artist:</b> {artist} - {year}";
|
||||
const NOTIFICATION_BODY: &str =
|
||||
"<b>Playing:</b> {title} from {album} \n\n <b>Artist:</b> {artist} - {year}";
|
||||
const NOTIFICATION_SUMMARY: &str = "{artist} - {title}";
|
||||
const NOTIFICATION_APP_NAME: &str = "C* Music Player";
|
||||
const DEFAULT_MAX_DEPTH: u8 = 3;
|
||||
|
|
183
src/cmus/mod.rs
183
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<Self, Self::Err> {
|
||||
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)?
|
||||
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(),
|
||||
)
|
||||
.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())
|
||||
|
@ -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<Track, CmusError> {
|
||||
// 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<Track, Cmu
|
|||
/// This function it should call only one time entire the program life time, So it makes sense to make it inline.
|
||||
/// This function will return a `std::process::Command` that can be used to query cmus, you should store it in a variable :).
|
||||
#[inline(always)]
|
||||
pub fn build_query_command(cmus_remote_bin: &str, socket_addr: &Option<String>, socket_pass: &Option<String>) -> std::process::Command {
|
||||
pub fn build_query_command(
|
||||
cmus_remote_bin: &str,
|
||||
socket_addr: &Option<String>,
|
||||
socket_pass: &Option<String>,
|
||||
) -> std::process::Command {
|
||||
let cmd_arr = cmus_remote_bin.split_whitespace().collect::<Vec<_>>();
|
||||
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<String>,
|
|||
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::<Vec<_>>(), &["--server", "/tmp/cmus-socket", "-Q"]);
|
||||
assert_eq!(
|
||||
command.get_args().collect::<Vec<_>>(),
|
||||
&["--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::<Vec<_>>(), &["--server", "/tmp/cmus-socket", "--passwd", "pass", "-Q"]);
|
||||
assert_eq!(
|
||||
command.get_args().collect::<Vec<_>>(),
|
||||
&["--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::<Vec<_>>(), &["run", "io.github.cmus.cmus", "-Q"]);
|
||||
assert_eq!(
|
||||
command.get_args().collect::<Vec<_>>(),
|
||||
&["run", "io.github.cmus.cmus", "-Q"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
31
src/utils.rs
31
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<Option<String>> {
|
||||
pub fn search_for(
|
||||
search_directory: &str,
|
||||
max_depth: u8,
|
||||
regx: &[&str],
|
||||
) -> std::io::Result<Option<String>> {
|
||||
// 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() {
|
||||
} 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue