diff --git a/src/commands.rs b/src/commands.rs index c7b9050..e0c94ab 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -10,6 +10,7 @@ mod client_to_client; mod connection_settings; mod controlling_playback; mod mounts_and_neighbors; +mod music_database; mod partition_commands; mod playback_options; mod querying_mpd_status; @@ -22,6 +23,7 @@ pub use client_to_client::*; pub use connection_settings::*; pub use controlling_playback::*; pub use mounts_and_neighbors::*; +pub use music_database::*; pub use partition_commands::*; pub use playback_options::*; pub use querying_mpd_status::*; diff --git a/src/commands/music_database.rs b/src/commands/music_database.rs new file mode 100644 index 0000000..d44b586 --- /dev/null +++ b/src/commands/music_database.rs @@ -0,0 +1,37 @@ +pub mod albumart; +pub mod count; +pub mod find; +pub mod findadd; +pub mod getfingerprint; +pub mod list; +pub mod listall; +pub mod listallinfo; +pub mod listfiles; +pub mod lsinfo; +pub mod readcomments; +pub mod readpicture; +pub mod rescan; +pub mod search; +pub mod searchadd; +pub mod searchaddpl; +pub mod searchcount; +pub mod update; + +pub use albumart::AlbumArt; +pub use count::Count; +pub use find::Find; +pub use findadd::FindAdd; +pub use getfingerprint::GetFingerprint; +pub use list::List; +pub use listall::ListAll; +pub use listallinfo::ListAllInfo; +pub use listfiles::ListFiles; +pub use lsinfo::LsInfo; +pub use readcomments::ReadComments; +pub use readpicture::ReadPicture; +pub use rescan::Rescan; +pub use search::Search; +pub use searchadd::SearchAdd; +pub use searchaddpl::SearchAddPl; +pub use searchcount::SearchCount; +pub use update::Update; diff --git a/src/commands/music_database/albumart.rs b/src/commands/music_database/albumart.rs new file mode 100644 index 0000000..eaac7f1 --- /dev/null +++ b/src/commands/music_database/albumart.rs @@ -0,0 +1,51 @@ +use std::collections::HashMap; + +use crate::{ + commands::{ + get_and_parse_property, get_property, Command, Request, RequestParserError, + RequestParserResult, ResponseAttributes, ResponseParserError, + }, + common::Offset, +}; + +pub struct AlbumArt; + +pub struct AlbumArtResponse { + pub size: usize, + pub binary: Vec, +} + +impl Command for AlbumArt { + type Response = AlbumArtResponse; + const COMMAND: &'static str = "albumart"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = match parts.next() { + Some(s) => s, + None => return Err(RequestParserError::UnexpectedEOF), + }; + + let offset = match parts.next() { + Some(s) => s + .parse::() + .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?, + None => return Err(RequestParserError::UnexpectedEOF), + }; + + debug_assert!(parts.next().is_none()); + + Ok((Request::AlbumArt(uri.to_string(), offset), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + let size = get_and_parse_property!(parts, "size", Text); + + let binary = get_property!(parts, "binary", Binary).into(); + + Ok(AlbumArtResponse { size, binary }) + } +} diff --git a/src/commands/music_database/count.rs b/src/commands/music_database/count.rs new file mode 100644 index 0000000..c1322f7 --- /dev/null +++ b/src/commands/music_database/count.rs @@ -0,0 +1,52 @@ +use std::collections::HashMap; + +use crate::{ + commands::{ + get_and_parse_property, Command, Request, RequestParserError, RequestParserResult, + ResponseAttributes, ResponseParserError, + }, + filter::parse_filter, +}; + +pub struct Count; + +pub struct CountResponse { + pub songs: usize, + pub playtime: u64, +} + +impl Command for Count { + type Response = CountResponse; + const COMMAND: &'static str = "count"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let filter = parse_filter(&mut parts)?; + + let group = if let Some("group") = parts.next() { + Some( + parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .parse() + .map_err(|_| RequestParserError::SyntaxError(1, "group".to_owned()))?, + ) + } else { + None + }; + + debug_assert!(parts.next().is_none()); + + Ok((Request::Count(filter, group), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + let songs = get_and_parse_property!(parts, "songs", Text); + let playtime = get_and_parse_property!(parts, "playtime", Text); + + Ok(CountResponse { songs, playtime }) + } +} diff --git a/src/commands/music_database/find.rs b/src/commands/music_database/find.rs new file mode 100644 index 0000000..fd73ac9 --- /dev/null +++ b/src/commands/music_database/find.rs @@ -0,0 +1,51 @@ +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + filter::parse_filter, +}; + +pub struct Find; + +pub struct FindResponse {} + +impl Command for Find { + type Response = FindResponse; + const COMMAND: &'static str = "find"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let filter = parse_filter(&mut parts)?; + + let mut sort_or_window = parts.next(); + let mut sort = None; + if let Some("sort") = sort_or_window { + sort = Some( + parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(), + ); + sort_or_window = parts.next(); + } + + let mut window = None; + if let Some("window") = sort_or_window { + let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + window = Some( + w.parse() + .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?, + ); + } + + debug_assert!(parts.next().is_none()); + + Ok((Request::Find(filter, sort, window), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/findadd.rs b/src/commands/music_database/findadd.rs new file mode 100644 index 0000000..11586ef --- /dev/null +++ b/src/commands/music_database/findadd.rs @@ -0,0 +1,61 @@ +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + filter::parse_filter, +}; + +pub struct FindAdd; + +pub struct FindAddResponse {} + +impl Command for FindAdd { + type Response = FindAddResponse; + const COMMAND: &'static str = "findadd"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let filter = parse_filter(&mut parts)?; + + let mut sort_or_window_or_position = parts.next(); + let mut sort = None; + if let Some("sort") = sort_or_window_or_position { + sort = Some( + parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(), + ); + sort_or_window_or_position = parts.next(); + } + + let mut window = None; + if let Some("window") = sort_or_window_or_position { + let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + window = Some( + w.parse() + .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?, + ); + sort_or_window_or_position = parts.next(); + } + + let mut position = None; + if let Some("position") = sort_or_window_or_position { + let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + position = Some( + p.parse() + .map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?, + ); + } + + debug_assert!(parts.next().is_none()); + + Ok((Request::FindAdd(filter, sort, window, position), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/getfingerprint.rs b/src/commands/music_database/getfingerprint.rs new file mode 100644 index 0000000..848dfeb --- /dev/null +++ b/src/commands/music_database/getfingerprint.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; + +use crate::commands::{ + get_and_parse_property, Command, Request, RequestParserError, RequestParserResult, + ResponseAttributes, ResponseParserError, +}; + +pub struct GetFingerprint; + +pub struct GetFingerprintResponse { + pub chromaprint: String, +} + +impl Command for GetFingerprint { + type Response = GetFingerprintResponse; + const COMMAND: &'static str = "getfingerprint"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = uri + .parse() + .map_err(|_| RequestParserError::SyntaxError(1, "uri".to_owned()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::GetFingerprint(uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + let chromaprint = get_and_parse_property!(parts, "chromaprint", Text); + + Ok(GetFingerprintResponse { chromaprint }) + } +} diff --git a/src/commands/music_database/list.rs b/src/commands/music_database/list.rs new file mode 100644 index 0000000..6cf84e2 --- /dev/null +++ b/src/commands/music_database/list.rs @@ -0,0 +1,63 @@ +use crate::{ + commands::{ + Command, GenericResponseValue, Request, RequestParserError, RequestParserResult, + ResponseAttributes, ResponseParserError, + }, + filter::parse_filter, +}; + +pub struct List; + +pub type ListResponse = Vec; + +impl Command for List { + type Response = ListResponse; + const COMMAND: &'static str = "list"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let tagtype = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let tagtype = tagtype + .parse() + .map_err(|_| RequestParserError::SyntaxError(1, "tagtype".to_owned()))?; + + // TODO: This should be optional + let filter = parse_filter(&mut parts)?; + + let group = if let Some("group") = parts.next() { + let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + Some( + group + .parse() + .map_err(|_| RequestParserError::SyntaxError(1, "group".to_owned()))?, + ) + } else { + None + }; + + debug_assert!(parts.next().is_none()); + + Ok((Request::List(tagtype, filter, group), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!({ + let key = parts.0.first().map(|(k, _)| k); + parts.0.iter().all(|(k, _)| k == key.unwrap()) + }); + + let list = parts + .0 + .iter() + .map(|(_, v)| match v { + GenericResponseValue::Text(value) => Ok(value.to_string()), + GenericResponseValue::Binary(_) => Err( + ResponseParserError::UnexpectedPropertyType("handler", "Binary"), + ), + }) + .collect::, ResponseParserError>>()?; + + Ok(list) + } +} diff --git a/src/commands/music_database/listall.rs b/src/commands/music_database/listall.rs new file mode 100644 index 0000000..ad60971 --- /dev/null +++ b/src/commands/music_database/listall.rs @@ -0,0 +1,32 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct ListAll; + +// TODO: This is supposed to be a tree-like structure, with directories containing files and playlists +pub type ListAllResponse = Vec; + +impl Command for ListAll { + type Response = ListAllResponse; + const COMMAND: &'static str = "listall"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(1, "uri".to_owned())) + }) + .transpose()?; + + Ok((Request::ListAll(uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/listallinfo.rs b/src/commands/music_database/listallinfo.rs new file mode 100644 index 0000000..6bac8ca --- /dev/null +++ b/src/commands/music_database/listallinfo.rs @@ -0,0 +1,33 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct ListAllInfo; + +// TODO: This is supposed to be a tree-like structure, with directories containing files and playlists +// in addition to the metadata of each entry +pub type ListAllInfoResponse = Vec; + +impl Command for ListAllInfo { + type Response = ListAllInfoResponse; + const COMMAND: &'static str = "listallinfo"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(1, "uri".to_owned())) + }) + .transpose()?; + + Ok((Request::ListAllInfo(uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/listfiles.rs b/src/commands/music_database/listfiles.rs new file mode 100644 index 0000000..fdf8d9d --- /dev/null +++ b/src/commands/music_database/listfiles.rs @@ -0,0 +1,32 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct ListFiles; + +// TODO: fix this type +pub type ListFilesResponse = Vec; + +impl Command for ListFiles { + type Response = ListFilesResponse; + const COMMAND: &'static str = "listfiles"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(1, "uri".to_owned())) + }) + .transpose()?; + + Ok((Request::ListFiles(uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/lsinfo.rs b/src/commands/music_database/lsinfo.rs new file mode 100644 index 0000000..58c6658 --- /dev/null +++ b/src/commands/music_database/lsinfo.rs @@ -0,0 +1,32 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct LsInfo; + +// TODO: fix this type +pub type LsInfoResponse = Vec; + +impl Command for LsInfo { + type Response = LsInfoResponse; + const COMMAND: &'static str = "lsinfo"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(1, "uri".to_owned())) + }) + .transpose()?; + + Ok((Request::LsInfo(uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/readcomments.rs b/src/commands/music_database/readcomments.rs new file mode 100644 index 0000000..c7b53d6 --- /dev/null +++ b/src/commands/music_database/readcomments.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; + +use crate::commands::{ + Command, GenericResponseValue, Request, RequestParserError, RequestParserResult, + ResponseAttributes, ResponseParserError, +}; + +pub struct ReadComments; + +pub type ReadCommentsResponse = HashMap; + +impl Command for ReadComments { + type Response = ReadCommentsResponse; + const COMMAND: &'static str = "readcomments"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let uri = uri + .parse() + .map_err(|_| RequestParserError::SyntaxError(1, "uri".to_owned()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::ReadComments(uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + let comments = parts + .iter() + .map(|(k, v)| match v { + GenericResponseValue::Text(s) => Ok((k.to_string(), s.to_string())), + GenericResponseValue::Binary(_) => Err(ResponseParserError::SyntaxError(1, k)), + }) + .collect::, ResponseParserError>>()?; + + Ok(comments) + } +} diff --git a/src/commands/music_database/readpicture.rs b/src/commands/music_database/readpicture.rs new file mode 100644 index 0000000..91c5c98 --- /dev/null +++ b/src/commands/music_database/readpicture.rs @@ -0,0 +1,62 @@ +use std::collections::HashMap; + +use crate::{ + commands::{ + get_and_parse_property, get_optional_property, get_property, Command, Request, + RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + common::Offset, +}; + +pub struct ReadPicture; + +pub struct ReadPictureResponse { + pub size: usize, + pub binary: Vec, + pub mimetype: Option, +} + +impl Command for ReadPicture { + type Response = Option; + const COMMAND: &'static str = "readpicture"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = match parts.next() { + Some(s) => s, + None => return Err(RequestParserError::UnexpectedEOF), + }; + + let offset = match parts.next() { + Some(s) => s + .parse::() + .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?, + None => return Err(RequestParserError::UnexpectedEOF), + }; + + debug_assert!(parts.next().is_none()); + + Ok((Request::ReadPicture(uri.to_string(), offset), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + if parts.is_empty() { + return Ok(None); + } + + let size = get_and_parse_property!(parts, "size", Text); + + let binary = get_property!(parts, "binary", Binary).into(); + + let mimetype = get_optional_property!(parts, "mimetype", Text).map(|s| s.to_string()); + + Ok(Some(ReadPictureResponse { + size, + binary, + mimetype, + })) + } +} diff --git a/src/commands/music_database/rescan.rs b/src/commands/music_database/rescan.rs new file mode 100644 index 0000000..29d3e83 --- /dev/null +++ b/src/commands/music_database/rescan.rs @@ -0,0 +1,35 @@ +use std::collections::HashMap; + +use crate::commands::{ + get_and_parse_property, Command, Request, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct Rescan; + +pub struct RescanResponse { + pub updating_db: usize, +} + +impl Command for Rescan { + type Response = RescanResponse; + const COMMAND: &'static str = "rescan"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = parts.next().map(|s| s.to_string()); + + debug_assert!(parts.next().is_none()); + + Ok((Request::Rescan(uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + let updating_db = get_and_parse_property!(parts, "updating_db", Text); + + Ok(RescanResponse { updating_db }) + } +} diff --git a/src/commands/music_database/search.rs b/src/commands/music_database/search.rs new file mode 100644 index 0000000..0f7b99c --- /dev/null +++ b/src/commands/music_database/search.rs @@ -0,0 +1,51 @@ +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + filter::parse_filter, +}; + +pub struct Search; + +pub struct SearchResponse {} + +impl Command for Search { + type Response = SearchResponse; + const COMMAND: &'static str = "search"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let filter = parse_filter(&mut parts)?; + + let mut sort_or_window = parts.next(); + let mut sort = None; + if let Some("sort") = sort_or_window { + sort = Some( + parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(), + ); + sort_or_window = parts.next(); + } + + let mut window = None; + if let Some("window") = sort_or_window { + let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + window = Some( + w.parse() + .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?, + ); + } + + debug_assert!(parts.next().is_none()); + + Ok((Request::Search(filter, sort, window), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/searchadd.rs b/src/commands/music_database/searchadd.rs new file mode 100644 index 0000000..f82931d --- /dev/null +++ b/src/commands/music_database/searchadd.rs @@ -0,0 +1,61 @@ +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + filter::parse_filter, +}; + +pub struct SearchAdd; + +pub struct SearchAddResponse {} + +impl Command for SearchAdd { + type Response = SearchAddResponse; + const COMMAND: &'static str = "searchadd"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let filter = parse_filter(&mut parts)?; + + let mut sort_or_window_or_position = parts.next(); + let mut sort = None; + if let Some("sort") = sort_or_window_or_position { + sort = Some( + parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(), + ); + sort_or_window_or_position = parts.next(); + } + + let mut window = None; + if let Some("window") = sort_or_window_or_position { + let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + window = Some( + w.parse() + .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?, + ); + sort_or_window_or_position = parts.next(); + } + + let mut position = None; + if let Some("position") = sort_or_window_or_position { + let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + position = Some( + p.parse() + .map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?, + ); + } + + debug_assert!(parts.next().is_none()); + + Ok((Request::SearchAdd(filter, sort, window, position), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/searchaddpl.rs b/src/commands/music_database/searchaddpl.rs new file mode 100644 index 0000000..4ee59ed --- /dev/null +++ b/src/commands/music_database/searchaddpl.rs @@ -0,0 +1,69 @@ +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + filter::parse_filter, +}; + +pub struct SearchAddPl; + +pub struct SearchAddPlResponse {} + +impl Command for SearchAddPl { + type Response = SearchAddPlResponse; + const COMMAND: &'static str = "searchaddpl"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + let filter = parse_filter(&mut parts)?; + + let mut sort_or_window_or_position = parts.next(); + let mut sort = None; + if let Some("sort") = sort_or_window_or_position { + sort = Some( + parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(), + ); + sort_or_window_or_position = parts.next(); + } + + let mut window = None; + if let Some("window") = sort_or_window_or_position { + let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + window = Some( + w.parse() + .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?, + ); + sort_or_window_or_position = parts.next(); + } + + let mut position = None; + if let Some("position") = sort_or_window_or_position { + let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + position = Some( + p.parse() + .map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?, + ); + } + + debug_assert!(parts.next().is_none()); + + Ok(( + Request::SearchAddPl(playlist_name, filter, sort, window, position), + "", + )) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/music_database/searchcount.rs b/src/commands/music_database/searchcount.rs new file mode 100644 index 0000000..b0edd2b --- /dev/null +++ b/src/commands/music_database/searchcount.rs @@ -0,0 +1,52 @@ +use std::collections::HashMap; + +use crate::{ + commands::{ + get_and_parse_property, Command, Request, RequestParserError, RequestParserResult, + ResponseAttributes, ResponseParserError, + }, + filter::parse_filter, +}; + +pub struct SearchCount; + +pub struct SearchCountResponse { + pub songs: usize, + pub playtime: u64, +} + +impl Command for SearchCount { + type Response = SearchCountResponse; + const COMMAND: &'static str = "searchcount"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let filter = parse_filter(&mut parts)?; + + let group = if let Some("group") = parts.next() { + Some( + parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .parse() + .map_err(|_| RequestParserError::SyntaxError(1, "group".to_owned()))?, + ) + } else { + None + }; + + debug_assert!(parts.next().is_none()); + + Ok((Request::SearchCount(filter, group), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + let songs = get_and_parse_property!(parts, "songs", Text); + let playtime = get_and_parse_property!(parts, "playtime", Text); + + Ok(SearchCountResponse { songs, playtime }) + } +} diff --git a/src/commands/music_database/update.rs b/src/commands/music_database/update.rs new file mode 100644 index 0000000..7b9232d --- /dev/null +++ b/src/commands/music_database/update.rs @@ -0,0 +1,35 @@ +use std::collections::HashMap; + +use crate::commands::{ + get_and_parse_property, Command, Request, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct Update; + +pub struct UpdateResponse { + updating_db: usize, +} + +impl Command for Update { + type Response = UpdateResponse; + const COMMAND: &'static str = "update"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = parts.next().map(|s| s.to_string()); + + debug_assert!(parts.next().is_none()); + + Ok((Request::Update(uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + let updating_db = get_and_parse_property!(parts, "updating_db", Text); + + Ok(UpdateResponse { updating_db }) + } +} diff --git a/src/request.rs b/src/request.rs index 825b11b..8966707 100644 --- a/src/request.rs +++ b/src/request.rs @@ -103,12 +103,12 @@ pub enum Request { Option, Option, ), - List(Tag, Filter, Option), + List(TagName, Filter, Option), #[deprecated] ListAll(Option), #[deprecated] ListAllInfo(Option), - ListFiles(Uri), + ListFiles(Option), LsInfo(Option), ReadComments(Uri), ReadPicture(Uri, Offset), @@ -120,6 +120,7 @@ pub enum Request { Option, ), SearchAddPl( + PlaylistName, Filter, Option, Option, @@ -294,6 +295,28 @@ pub enum GroupType { Any, } +impl FromStr for GroupType { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "artist" => Ok(Self::Artist), + "album" => Ok(Self::Album), + "albumartist" => Ok(Self::AlbumArtist), + "date" => Ok(Self::Date), + "genre" => Ok(Self::Genre), + "track" => Ok(Self::Track), + "composer" => Ok(Self::Composer), + "performer" => Ok(Self::Performer), + "conductor" => Ok(Self::Conductor), + "comment" => Ok(Self::Comment), + "disc" => Ok(Self::Disc), + "filename" => Ok(Self::Filename), + "any" => Ok(Self::Any), + _ => Err(()), + } + } +} + // trait RequestCommand { // // The command name used within the protocol // const COMMAND: &'static str; @@ -426,6 +449,24 @@ impl Request { Save::COMMAND => Save::parse_request(parts), /* music database */ + AlbumArt::COMMAND => AlbumArt::parse_request(parts), + Count::COMMAND => Count::parse_request(parts), + GetFingerprint::COMMAND => GetFingerprint::parse_request(parts), + Find::COMMAND => Find::parse_request(parts), + FindAdd::COMMAND => FindAdd::parse_request(parts), + List::COMMAND => List::parse_request(parts), + ListAll::COMMAND => ListAll::parse_request(parts), + ListAllInfo::COMMAND => ListAllInfo::parse_request(parts), + ListFiles::COMMAND => ListFiles::parse_request(parts), + LsInfo::COMMAND => LsInfo::parse_request(parts), + ReadComments::COMMAND => ReadComments::parse_request(parts), + ReadPicture::COMMAND => ReadPicture::parse_request(parts), + Search::COMMAND => Search::parse_request(parts), + SearchAdd::COMMAND => SearchAdd::parse_request(parts), + SearchAddPl::COMMAND => SearchAddPl::parse_request(parts), + SearchCount::COMMAND => SearchCount::parse_request(parts), + Update::COMMAND => Update::parse_request(parts), + Rescan::COMMAND => Rescan::parse_request(parts), /* mounts and neighbors */ Mount::COMMAND => Mount::parse_request(parts),