diff --git a/src/commands/music_database/find.rs b/src/commands/music_database/find.rs index b2ed5c9..290f903 100644 --- a/src/commands/music_database/find.rs +++ b/src/commands/music_database/find.rs @@ -47,11 +47,10 @@ impl Command for Find { let mut sort_or_window = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window { + let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; sort = Some( - parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(), + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, ); sort_or_window = parts.next(); } diff --git a/src/commands/music_database/findadd.rs b/src/commands/music_database/findadd.rs index 5b42ac3..ae16d01 100644 --- a/src/commands/music_database/findadd.rs +++ b/src/commands/music_database/findadd.rs @@ -48,11 +48,10 @@ impl Command for FindAdd { let mut sort_or_window_or_position = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window_or_position { + let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; sort = Some( - parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(), + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, ); sort_or_window_or_position = parts.next(); } diff --git a/src/commands/music_database/search.rs b/src/commands/music_database/search.rs index 0f9e26e..610aaef 100644 --- a/src/commands/music_database/search.rs +++ b/src/commands/music_database/search.rs @@ -47,11 +47,10 @@ impl Command for Search { let mut sort_or_window = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window { + let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; sort = Some( - parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(), + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, ); sort_or_window = parts.next(); } diff --git a/src/commands/music_database/searchadd.rs b/src/commands/music_database/searchadd.rs index 9efaf0c..dad97ae 100644 --- a/src/commands/music_database/searchadd.rs +++ b/src/commands/music_database/searchadd.rs @@ -50,11 +50,10 @@ impl Command for SearchAdd { let mut sort_or_window_or_position = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window_or_position { + let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; sort = Some( - parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(), + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, ); sort_or_window_or_position = parts.next(); } diff --git a/src/commands/music_database/searchaddpl.rs b/src/commands/music_database/searchaddpl.rs index a14ac53..2258bc6 100644 --- a/src/commands/music_database/searchaddpl.rs +++ b/src/commands/music_database/searchaddpl.rs @@ -61,11 +61,10 @@ impl Command for SearchAddPl { let mut sort_or_window_or_position = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window_or_position { + let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; sort = Some( - parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(), + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, ); sort_or_window_or_position = parts.next(); } diff --git a/src/commands/queue/playlistfind.rs b/src/commands/queue/playlistfind.rs index 48b7335..5343e5c 100644 --- a/src/commands/queue/playlistfind.rs +++ b/src/commands/queue/playlistfind.rs @@ -45,11 +45,10 @@ impl Command for PlaylistFind { let mut sort_or_window = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window { + let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; sort = Some( - parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(), + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, ); sort_or_window = parts.next(); } diff --git a/src/commands/queue/playlistsearch.rs b/src/commands/queue/playlistsearch.rs index cd9b2dd..ccfb51a 100644 --- a/src/commands/queue/playlistsearch.rs +++ b/src/commands/queue/playlistsearch.rs @@ -45,11 +45,10 @@ impl Command for PlaylistSearch { let mut sort_or_window = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window { + let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; sort = Some( - parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(), + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, ); sort_or_window = parts.next(); } diff --git a/src/commands/stickers/sticker_find.rs b/src/commands/stickers/sticker_find.rs index f828471..abe1720 100644 --- a/src/commands/stickers/sticker_find.rs +++ b/src/commands/stickers/sticker_find.rs @@ -71,11 +71,10 @@ impl Command for StickerFind { let mut sort_or_window = parts.next(); let mut sort = None; if let Some("sort") = sort_or_window { + let s = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; sort = Some( - parts - .next() - .ok_or(RequestParserError::UnexpectedEOF)? - .to_string(), + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string()))?, ); sort_or_window = parts.next(); } diff --git a/src/common/types.rs b/src/common/types.rs index ff92a4a..efdfb41 100644 --- a/src/common/types.rs +++ b/src/common/types.rs @@ -6,6 +6,7 @@ mod one_or_range; mod replay_gain_mode_mode; mod save_mode; mod seek_mode; +mod sort; mod subsystem; mod tag; mod time_interval; @@ -20,6 +21,7 @@ pub use one_or_range::OneOrRange; pub use replay_gain_mode_mode::ReplayGainModeMode; pub use save_mode::SaveMode; pub use seek_mode::SeekMode; +pub use sort::Sort; pub use subsystem::SubSystem; pub use tag::Tag; pub use time_interval::TimeInterval; @@ -39,7 +41,6 @@ pub type TagName = String; pub type TagValue = String; pub type Uri = String; pub type Path = String; -pub type Sort = String; pub type Version = String; pub type Feature = String; pub type PartitionName = String; diff --git a/src/common/types/sort.rs b/src/common/types/sort.rs new file mode 100644 index 0000000..0455715 --- /dev/null +++ b/src/common/types/sort.rs @@ -0,0 +1,79 @@ +// sort sorts the result by the specified tag. The sort is descending if the tag is prefixed with a minus (‘-‘). Only the first tag value will be used, if multiple of the same type exist. To sort by “Title”, “Artist”, “Album”, “AlbumArtist” or “Composer”, you should specify “TitleSort”, “ArtistSort”, “AlbumSort”, “AlbumArtistSort” or “ComposerSort” instead. These will automatically fall back to the former if “*Sort” doesn’t exist. “AlbumArtist” falls back to just “Artist”. The type “Last-Modified” can sort by file modification time, and “prio” sorts by queue priority. + +use std::{fmt::Display, str::FromStr}; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Sort { + pub descending: bool, + pub key: SortKey, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum SortKey { + LastModified, + Added, + Prio, + Tag(super::TagName), +} + +impl FromStr for Sort { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s.to_lowercase().as_str() { + "last-modified" => Sort { + descending: false, + key: SortKey::LastModified, + }, + "-last-modified" => Sort { + descending: true, + key: SortKey::LastModified, + }, + "added" => Sort { + descending: false, + key: SortKey::Added, + }, + "-added" => Sort { + descending: true, + key: SortKey::Added, + }, + "prio" => Sort { + descending: false, + key: SortKey::Prio, + }, + "-prio" => Sort { + descending: true, + key: SortKey::Prio, + }, + other => { + let descending = other.starts_with('-'); + let tag_str = if descending { &other[1..] } else { other }; + let tag = tag_str.parse::().map_err(|_| ())?; + Sort { + descending, + key: SortKey::Tag(tag), + } + } + }) + } +} + +impl Display for Sort { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Sort { descending, key } => { + if *descending { + write!(f, "-")?; + } + match key { + SortKey::LastModified => write!(f, "last-modified"), + SortKey::Added => write!(f, "added"), + SortKey::Prio => write!(f, "prio"), + SortKey::Tag(tag) => write!(f, "{}", tag), + } + } + } + } +}