From 08104b3537caa328f4c49eafd9f0c09a3d821735 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Sat, 30 Nov 2024 17:12:49 +0100 Subject: [PATCH] Implement some more commands --- src/commands.rs | 1 + src/commands/audio_output_devices/outputs.rs | 2 +- src/commands/client_to_client/readmessages.rs | 2 +- src/commands/playback_options/getvol.rs | 2 +- .../querying_mpd_status/clearerror.rs | 1 + .../querying_mpd_status/currentsong.rs | 3 +- src/commands/queue.rs | 22 +++++ src/commands/queue/add.rs | 40 +++++++++ src/commands/queue/addid.rs | 55 +++++++++++++ src/commands/queue/addtagid.rs | 41 ++++++++++ src/commands/queue/clear.rs | 22 +++++ src/commands/queue/cleartagid.rs | 36 ++++++++ src/commands/queue/delete.rs | 33 ++++++++ src/commands/queue/deleteid.rs | 31 +++++++ src/commands/queue/move_.rs | 36 ++++++++ src/commands/queue/moveid.rs | 36 ++++++++ src/commands/queue/playlist.rs | 22 +++++ src/commands/queue/playlistfind.rs | 49 +++++++++++ src/commands/queue/playlistid.rs | 30 +++++++ src/commands/queue/playlistinfo.rs | 33 ++++++++ src/commands/queue/playlistsearch.rs | 51 ++++++++++++ src/commands/queue/plchanges.rs | 38 +++++++++ src/commands/queue/plchangesposid.rs | 38 +++++++++ src/commands/queue/prio.rs | 36 ++++++++ src/commands/queue/prioid.rs | 41 ++++++++++ src/commands/queue/rangeid.rs | 36 ++++++++ src/commands/queue/shuffle.rs | 35 ++++++++ src/commands/queue/swap.rs | 36 ++++++++ src/commands/queue/swapid.rs | 36 ++++++++ src/common.rs | 82 ++++++++++++++++++- src/request.rs | 10 ++- 31 files changed, 926 insertions(+), 10 deletions(-) create mode 100644 src/commands/queue.rs create mode 100644 src/commands/queue/add.rs create mode 100644 src/commands/queue/addid.rs create mode 100644 src/commands/queue/addtagid.rs create mode 100644 src/commands/queue/clear.rs create mode 100644 src/commands/queue/cleartagid.rs create mode 100644 src/commands/queue/delete.rs create mode 100644 src/commands/queue/deleteid.rs create mode 100644 src/commands/queue/move_.rs create mode 100644 src/commands/queue/moveid.rs create mode 100644 src/commands/queue/playlist.rs create mode 100644 src/commands/queue/playlistfind.rs create mode 100644 src/commands/queue/playlistid.rs create mode 100644 src/commands/queue/playlistinfo.rs create mode 100644 src/commands/queue/playlistsearch.rs create mode 100644 src/commands/queue/plchanges.rs create mode 100644 src/commands/queue/plchangesposid.rs create mode 100644 src/commands/queue/prio.rs create mode 100644 src/commands/queue/prioid.rs create mode 100644 src/commands/queue/rangeid.rs create mode 100644 src/commands/queue/shuffle.rs create mode 100644 src/commands/queue/swap.rs create mode 100644 src/commands/queue/swapid.rs diff --git a/src/commands.rs b/src/commands.rs index a9e26f5..ef650b3 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -7,6 +7,7 @@ mod client_to_client; mod controlling_playback; mod playback_options; mod querying_mpd_status; +mod queue; pub use querying_mpd_status::clearerror::ClearError; pub use querying_mpd_status::idle::Idle; diff --git a/src/commands/audio_output_devices/outputs.rs b/src/commands/audio_output_devices/outputs.rs index 1ac7171..5cd9aeb 100644 --- a/src/commands/audio_output_devices/outputs.rs +++ b/src/commands/audio_output_devices/outputs.rs @@ -29,7 +29,7 @@ impl Command for Outputs { } fn parse_response( - parts: ResponseAttributes<'_>, + _parts: ResponseAttributes<'_>, ) -> Result { unimplemented!() } diff --git a/src/commands/client_to_client/readmessages.rs b/src/commands/client_to_client/readmessages.rs index 9cd4776..3c3a37c 100644 --- a/src/commands/client_to_client/readmessages.rs +++ b/src/commands/client_to_client/readmessages.rs @@ -87,4 +87,4 @@ mod tests { }) ); } -} \ No newline at end of file +} diff --git a/src/commands/playback_options/getvol.rs b/src/commands/playback_options/getvol.rs index 28f92b6..cf6271b 100644 --- a/src/commands/playback_options/getvol.rs +++ b/src/commands/playback_options/getvol.rs @@ -15,7 +15,7 @@ impl Command for GetVol { } fn parse_response( - parts: ResponseAttributes<'_>, + _parts: ResponseAttributes<'_>, ) -> Result { unimplemented!() // let volume = get_property!(parts, Volume, "volume"); diff --git a/src/commands/querying_mpd_status/clearerror.rs b/src/commands/querying_mpd_status/clearerror.rs index 76a84b3..4fcb4fe 100644 --- a/src/commands/querying_mpd_status/clearerror.rs +++ b/src/commands/querying_mpd_status/clearerror.rs @@ -2,6 +2,7 @@ use crate::commands::{ Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, }; +/// Clears the current error message in status (this is also accomplished by any command that starts playback) pub struct ClearError; impl Command for ClearError { diff --git a/src/commands/querying_mpd_status/currentsong.rs b/src/commands/querying_mpd_status/currentsong.rs index 673814e..668b34d 100644 --- a/src/commands/querying_mpd_status/currentsong.rs +++ b/src/commands/querying_mpd_status/currentsong.rs @@ -4,6 +4,7 @@ use crate::commands::{ Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, }; +/// Displays the song info of the current song (same song that is identified in status) pub struct CurrentSong; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -20,7 +21,7 @@ impl Command for CurrentSong { } fn parse_response( - parts: ResponseAttributes<'_>, + _parts: ResponseAttributes<'_>, ) -> Result { unimplemented!() } diff --git a/src/commands/queue.rs b/src/commands/queue.rs new file mode 100644 index 0000000..4e151ab --- /dev/null +++ b/src/commands/queue.rs @@ -0,0 +1,22 @@ +pub mod add; +pub mod addid; +pub mod addtagid; +pub mod clear; +pub mod cleartagid; +pub mod delete; +pub mod deleteid; +pub mod move_; +pub mod moveid; +pub mod playlist; +pub mod playlistfind; +pub mod playlistid; +pub mod playlistinfo; +pub mod playlistsearch; +pub mod plchanges; +pub mod plchangesposid; +pub mod prio; +pub mod prioid; +pub mod rangeid; +pub mod shuffle; +pub mod swap; +pub mod swapid; diff --git a/src/commands/queue/add.rs b/src/commands/queue/add.rs new file mode 100644 index 0000000..3d2a203 --- /dev/null +++ b/src/commands/queue/add.rs @@ -0,0 +1,40 @@ +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + common::SongPosition, +}; + +pub struct Add; + +impl Command for Add { + type Response = (); + const COMMAND: &'static str = "add"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = match parts.next() { + Some(s) => s, + None => return Err(RequestParserError::UnexpectedEOF), + }; + + let position = match parts.next() { + Some(s) => Some( + s.parse::() + .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?, + ), + None => None, + }; + + debug_assert!(parts.next().is_none()); + + Ok((Request::Add(uri.to_string(), position), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/addid.rs b/src/commands/queue/addid.rs new file mode 100644 index 0000000..42fc089 --- /dev/null +++ b/src/commands/queue/addid.rs @@ -0,0 +1,55 @@ +use crate::{ + commands::{ + Command, GenericResponseValue, Request, RequestParserError, RequestParserResult, + ResponseAttributes, ResponseParserError, + }, + common::{SongId, SongPosition}, +}; + +pub struct AddId; + +pub struct AddIdResponse { + pub id: SongId, +} + +impl Command for AddId { + type Response = AddIdResponse; + const COMMAND: &'static str = "addid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let uri = match parts.next() { + Some(s) => s, + None => return Err(RequestParserError::UnexpectedEOF), + }; + + let position = match parts.next() { + Some(s) => Some( + s.parse::() + .map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?, + ), + None => None, + }; + + debug_assert!(parts.next().is_none()); + + Ok((Request::AddId(uri.to_string(), position), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: Vec<_> = parts.into(); + let (key, value) = parts.first().ok_or(ResponseParserError::UnexpectedEOF)?; + debug_assert!(key == &"Id"); + let value = match value { + GenericResponseValue::Text(value) => value, + GenericResponseValue::Binary(_) => { + return Err(ResponseParserError::UnexpectedPropertyType("Id", "Binary")) + } + }; + let id = value + .parse() + .map_err(|_| ResponseParserError::InvalidProperty("Id", value.to_owned()))?; + Ok(AddIdResponse { id }) + } +} diff --git a/src/commands/queue/addtagid.rs b/src/commands/queue/addtagid.rs new file mode 100644 index 0000000..c55eaa7 --- /dev/null +++ b/src/commands/queue/addtagid.rs @@ -0,0 +1,41 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct AddTagId; + +impl Command for AddTagId { + type Response = (); + const COMMAND: &'static str = "addtagid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let songid = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songid = songid + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, songid.to_string()))?; + + let tag_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let tag_name = tag_name + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, tag_name.to_string()))?; + + let tag_value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let tag_value = tag_value + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, tag_value.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::AddTagId(songid, tag_name, tag_value), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/clear.rs b/src/commands/queue/clear.rs new file mode 100644 index 0000000..28d80d5 --- /dev/null +++ b/src/commands/queue/clear.rs @@ -0,0 +1,22 @@ +use crate::commands::{ + Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, +}; + +pub struct Clear; + +impl Command for Clear { + type Response = (); + const COMMAND: &'static str = "clear"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Clear, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/cleartagid.rs b/src/commands/queue/cleartagid.rs new file mode 100644 index 0000000..c3bed99 --- /dev/null +++ b/src/commands/queue/cleartagid.rs @@ -0,0 +1,36 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct ClearTagId; + +impl Command for ClearTagId { + type Response = (); + const COMMAND: &'static str = "cleartagid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let songid = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songid = songid + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, songid.to_string()))?; + + let tag_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let tag_name = tag_name + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, tag_name.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::ClearTagId(songid, tag_name), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/delete.rs b/src/commands/queue/delete.rs new file mode 100644 index 0000000..35228ec --- /dev/null +++ b/src/commands/queue/delete.rs @@ -0,0 +1,33 @@ +use std::str::FromStr; + +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + common::OneOrRange, + Request, +}; + +pub struct Delete; + +impl Command for Delete { + type Response = (); + const COMMAND: &'static str = "delete"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let pos_or_range = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let one_or_range = OneOrRange::from_str(pos_or_range) + .map_err(|_| RequestParserError::SyntaxError(0, pos_or_range.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::Delete(one_or_range), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/deleteid.rs b/src/commands/queue/deleteid.rs new file mode 100644 index 0000000..e3223bf --- /dev/null +++ b/src/commands/queue/deleteid.rs @@ -0,0 +1,31 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct DeleteId; + +impl Command for DeleteId { + type Response = (); + const COMMAND: &'static str = "deleteid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let id = id + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, id.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::DeleteId(id), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/move_.rs b/src/commands/queue/move_.rs new file mode 100644 index 0000000..5ec2b17 --- /dev/null +++ b/src/commands/queue/move_.rs @@ -0,0 +1,36 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct Move; + +impl Command for Move { + type Response = (); + const COMMAND: &'static str = "move"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let from_or_range = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let from_or_range = from_or_range + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, from_or_range.to_string()))?; + + let to = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let to = to + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, to.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::Move(from_or_range, to), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/moveid.rs b/src/commands/queue/moveid.rs new file mode 100644 index 0000000..5a7d4ad --- /dev/null +++ b/src/commands/queue/moveid.rs @@ -0,0 +1,36 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct MoveId; + +impl Command for MoveId { + type Response = (); + const COMMAND: &'static str = "moveid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let id = id + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, id.to_string()))?; + + let to = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let to = to + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, to.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::MoveId(id, to), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/playlist.rs b/src/commands/queue/playlist.rs new file mode 100644 index 0000000..91529ee --- /dev/null +++ b/src/commands/queue/playlist.rs @@ -0,0 +1,22 @@ +use crate::{ + commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError}, + Request, +}; + +pub struct Playlist; + +impl Command for Playlist { + type Response = (); + const COMMAND: &'static str = "playlist"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Playlist, "")) + } + + fn parse_response( + _parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/queue/playlistfind.rs b/src/commands/queue/playlistfind.rs new file mode 100644 index 0000000..d91ef30 --- /dev/null +++ b/src/commands/queue/playlistfind.rs @@ -0,0 +1,49 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct PlaylistFind; + +impl Command for PlaylistFind { + type Response = (); + const COMMAND: &'static str = "playlistfind"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let filter = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let filter = filter.to_string(); + + 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::PlaylistFind(filter, sort, window), "")) + } + + fn parse_response( + _parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/queue/playlistid.rs b/src/commands/queue/playlistid.rs new file mode 100644 index 0000000..290574a --- /dev/null +++ b/src/commands/queue/playlistid.rs @@ -0,0 +1,30 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct PlaylistId; + +impl Command for PlaylistId { + type Response = (); + const COMMAND: &'static str = "playlistid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let id = id + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, id.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::PlaylistId(id), "")) + } + + fn parse_response( + _parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/queue/playlistinfo.rs b/src/commands/queue/playlistinfo.rs new file mode 100644 index 0000000..898a91c --- /dev/null +++ b/src/commands/queue/playlistinfo.rs @@ -0,0 +1,33 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct PlaylistInfo; + +impl Command for PlaylistInfo { + type Response = (); + const COMMAND: &'static str = "playlistinfo"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let one_or_range = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::PlaylistInfo(one_or_range), "")) + } + + fn parse_response( + _parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/queue/playlistsearch.rs b/src/commands/queue/playlistsearch.rs new file mode 100644 index 0000000..fac05d4 --- /dev/null +++ b/src/commands/queue/playlistsearch.rs @@ -0,0 +1,51 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct PlaylistSearch; + +impl Command for PlaylistSearch { + type Response = (); + const COMMAND: &'static str = "playlistsearch"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let filter = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let filter = filter + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, filter.to_string()))?; + + 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::PlaylistSearch(filter, sort, window), "")) + } + + fn parse_response( + _parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/queue/plchanges.rs b/src/commands/queue/plchanges.rs new file mode 100644 index 0000000..f80e535 --- /dev/null +++ b/src/commands/queue/plchanges.rs @@ -0,0 +1,38 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct PlChanges; + +impl Command for PlChanges { + type Response = (); + const COMMAND: &'static str = "plchanges"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let version = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let version = version + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, version.to_string()))?; + + let window = parts + .next() + .map(|w| { + w.parse() + .map_err(|_| RequestParserError::SyntaxError(0, w.to_string())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::PlChanges(version, window), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/queue/plchangesposid.rs b/src/commands/queue/plchangesposid.rs new file mode 100644 index 0000000..445e74b --- /dev/null +++ b/src/commands/queue/plchangesposid.rs @@ -0,0 +1,38 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct PlChangesPosId; + +impl Command for PlChangesPosId { + type Response = (); + const COMMAND: &'static str = "plchangesposid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let version = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let version = version + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, version.to_string()))?; + + let window = parts + .next() + .map(|w| { + w.parse() + .map_err(|_| RequestParserError::SyntaxError(0, w.to_string())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::PlChangesPosId(version, window), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/queue/prio.rs b/src/commands/queue/prio.rs new file mode 100644 index 0000000..dda0455 --- /dev/null +++ b/src/commands/queue/prio.rs @@ -0,0 +1,36 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct Prio; + +impl Command for Prio { + type Response = (); + const COMMAND: &'static str = "prio"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let prio = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let prio = prio + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, prio.to_string()))?; + + let window = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let window = window + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, window.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::Prio(prio, window), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/prioid.rs b/src/commands/queue/prioid.rs new file mode 100644 index 0000000..42a93f9 --- /dev/null +++ b/src/commands/queue/prioid.rs @@ -0,0 +1,41 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct PrioId; + +impl Command for PrioId { + type Response = (); + const COMMAND: &'static str = "prioid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let prio = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let prio = prio + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, prio.to_string()))?; + + let songids = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + // TODO: This is just a guess to satisfy compilation, double check it + let songids = songids + .split(',') + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_string())) + }) + .collect::, RequestParserError>>()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::PrioId(prio, songids), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/rangeid.rs b/src/commands/queue/rangeid.rs new file mode 100644 index 0000000..38c0681 --- /dev/null +++ b/src/commands/queue/rangeid.rs @@ -0,0 +1,36 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct RangeId; + +impl Command for RangeId { + type Response = (); + const COMMAND: &'static str = "rangeid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let songid = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songid = songid + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, songid.to_string()))?; + + let timeinterval = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let timeinterval = timeinterval + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, timeinterval.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::RangeId(songid, timeinterval), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/shuffle.rs b/src/commands/queue/shuffle.rs new file mode 100644 index 0000000..d96b45d --- /dev/null +++ b/src/commands/queue/shuffle.rs @@ -0,0 +1,35 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct Shuffle; + +impl Command for Shuffle { + type Response = (); + const COMMAND: &'static str = "shuffle"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let range = parts + .next() + .map(|range| { + range + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, range.to_string())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::Shuffle(range), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/swap.rs b/src/commands/queue/swap.rs new file mode 100644 index 0000000..de0bdd7 --- /dev/null +++ b/src/commands/queue/swap.rs @@ -0,0 +1,36 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct Swap; + +impl Command for Swap { + type Response = (); + const COMMAND: &'static str = "swap"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let songpos1 = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songpos1 = songpos1 + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, songpos1.to_string()))?; + + let songpos2 = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songpos2 = songpos2 + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, songpos2.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::Swap(songpos1, songpos2), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/queue/swapid.rs b/src/commands/queue/swapid.rs new file mode 100644 index 0000000..6218ded --- /dev/null +++ b/src/commands/queue/swapid.rs @@ -0,0 +1,36 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct SwapId; + +impl Command for SwapId { + type Response = (); + const COMMAND: &'static str = "swapid"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let songid1 = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songid1 = songid1 + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, songid1.to_string()))?; + + let songid2 = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let songid2 = songid2 + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, songid2.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::SwapId(songid1, songid2), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/common.rs b/src/common.rs index 282934b..32890cf 100644 --- a/src/common.rs +++ b/src/common.rs @@ -3,11 +3,89 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; pub type SongPosition = u32; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum AbsouluteRelativeSongPosition { + Absolute(SongPosition), + RelativePlus(SongPosition), + RelativeMinus(SongPosition), +} + +impl FromStr for AbsouluteRelativeSongPosition { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + s if s.starts_with('+') => Self::RelativePlus(s[1..].parse().map_err(|_| ())?), + s if s.starts_with('-') => Self::RelativeMinus(s[1..].parse().map_err(|_| ())?), + s => Self::Absolute(s.parse().map_err(|_| ())?), + }) + } +} + pub type SongId = u32; pub type Seconds = u32; pub type TimeWithFractions = f64; -pub type OneOrRange = (SongPosition, Option); -pub type WindowRange = (Option, Option); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum OneOrRange { + One(SongPosition), + Range(SongPosition, SongPosition), +} + +impl FromStr for OneOrRange { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let start = parts.next().ok_or(())?.parse().map_err(|_| ())?; + Ok(match parts.next() { + Some(end) => Self::Range(start, end.parse().map_err(|_| ())?), + None => Self::One(start), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct WindowRange { + pub start: SongPosition, + pub end: Option, +} + +impl FromStr for WindowRange { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let start = parts.next().ok_or(())?.parse().map_err(|_| ())?; + let end = parts.next().map(|s| s.parse().map_err(|_| ())); + Ok(Self { + start, + end: end.transpose()?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TimeInterval { + pub start: Option, + pub end: Option, +} + +impl FromStr for TimeInterval { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let start = parts.next().map(|s| s.parse().map_err(|_| ())); + let end = parts.next().map(|s| s.parse().map_err(|_| ())); + Ok(Self { + start: start.transpose()?, + end: end.transpose()?, + }) + } +} + pub type Priority = u8; pub type PlaylistName = String; pub type Offset = u32; diff --git a/src/request.rs b/src/request.rs index 23a635b..8cc8adf 100644 --- a/src/request.rs +++ b/src/request.rs @@ -52,12 +52,14 @@ pub enum Request { Clear, Delete(OneOrRange), DeleteId(SongId), - Move(OneOrRange, SongPosition), - MoveId(SongId, SongPosition), + // TODO: account for relative moves + Move(OneOrRange, AbsouluteRelativeSongPosition), + // TODO: account for relative moves + MoveId(SongId, AbsouluteRelativeSongPosition), Playlist, PlaylistFind(Filter, Option, Option), PlaylistId(SongId), - PlaylistInfo(OneOrRange), + PlaylistInfo(Option), PlaylistSearch(Filter, Option, Option), // TODO: which type of range? PlChanges(Version, Option), @@ -66,7 +68,7 @@ pub enum Request { // TODO: which type of range? Prio(Priority, WindowRange), PrioId(Priority, Vec), - RangeId(SongId, WindowRange), + RangeId(SongId, TimeInterval), Shuffle(Option), Swap(SongPosition, SongPosition), SwapId(SongId, SongId),