From 2ee6bbc58249433a7e08dc2be187eb51109a9cc8 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Sun, 1 Dec 2024 20:06:01 +0100 Subject: [PATCH] Add more commands --- src/commands.rs | 13 ++++ src/commands/audio_output_devices.rs | 2 +- src/commands/connection_settings.rs | 37 ++++++++++++ .../connection_settings/binary_limit.rs | 27 +++++++++ src/commands/connection_settings/close.rs | 22 +++++++ src/commands/connection_settings/kill.rs | 22 +++++++ src/commands/connection_settings/password.rs | 27 +++++++++ src/commands/connection_settings/ping.rs | 22 +++++++ src/commands/connection_settings/protocol.rs | 22 +++++++ .../connection_settings/protocol_all.rs | 23 ++++++++ .../connection_settings/protocol_available.rs | 22 +++++++ .../connection_settings/protocol_clear.rs | 23 ++++++++ .../connection_settings/protocol_disable.rs | 34 +++++++++++ .../connection_settings/protocol_enable.rs | 34 +++++++++++ src/commands/connection_settings/tag_types.rs | 42 +++++++++++++ .../connection_settings/tag_types_all.rs | 23 ++++++++ .../tag_types_available.rs | 22 +++++++ .../connection_settings/tag_types_clear.rs | 23 ++++++++ .../connection_settings/tag_types_disable.rs | 34 +++++++++++ .../connection_settings/tag_types_enable.rs | 34 +++++++++++ .../connection_settings/tag_types_reset.rs | 34 +++++++++++ src/commands/mounts_and_neighbors.rs | 9 +++ .../mounts_and_neighbors/listmounts.rs | 49 +++++++++++++++ .../mounts_and_neighbors/listneighbors.rs | 24 ++++++++ src/commands/mounts_and_neighbors/mount.rs | 36 +++++++++++ src/commands/mounts_and_neighbors/unmount.rs | 31 ++++++++++ src/commands/partition_commands.rs | 11 ++++ .../partition_commands/delpartition.rs | 29 +++++++++ .../partition_commands/listpartitions.rs | 43 ++++++++++++++ src/commands/partition_commands/moveoutput.rs | 29 +++++++++ .../partition_commands/newpartition.rs | 29 +++++++++ src/commands/partition_commands/partition.rs | 29 +++++++++ src/commands/playback_options/volume.rs | 11 ++-- src/commands/reflection.rs | 11 ++++ src/commands/reflection/commands.rs | 21 +++++++ src/commands/reflection/config.rs | 42 +++++++++++++ src/commands/reflection/decoders.rs | 29 +++++++++ src/commands/reflection/not_commands.rs | 21 +++++++ src/commands/reflection/url_handlers.rs | 40 +++++++++++++ src/commands/stored_playlists.rs | 27 +++++++++ src/commands/stored_playlists/listplaylist.rs | 53 +++++++++++++++++ .../stored_playlists/listplaylistinfo.rs | 39 ++++++++++++ .../stored_playlists/listplaylists.rs | 21 +++++++ src/commands/stored_playlists/load.rs | 46 +++++++++++++++ src/commands/stored_playlists/playlistadd.rs | 42 +++++++++++++ .../stored_playlists/playlistclear.rs | 29 +++++++++ .../stored_playlists/playlistdelete.rs | 35 +++++++++++ .../stored_playlists/playlistlength.rs | 40 +++++++++++++ src/commands/stored_playlists/playlistmove.rs | 46 +++++++++++++++ src/commands/stored_playlists/rename.rs | 34 +++++++++++ src/commands/stored_playlists/rm.rs | 29 +++++++++ src/commands/stored_playlists/save.rs | 37 ++++++++++++ .../stored_playlists/searchplaylist.rs | 44 ++++++++++++++ src/request.rs | 59 ++++++++++++++++--- 54 files changed, 1605 insertions(+), 12 deletions(-) create mode 100644 src/commands/connection_settings.rs create mode 100644 src/commands/connection_settings/binary_limit.rs create mode 100644 src/commands/connection_settings/close.rs create mode 100644 src/commands/connection_settings/kill.rs create mode 100644 src/commands/connection_settings/password.rs create mode 100644 src/commands/connection_settings/ping.rs create mode 100644 src/commands/connection_settings/protocol.rs create mode 100644 src/commands/connection_settings/protocol_all.rs create mode 100644 src/commands/connection_settings/protocol_available.rs create mode 100644 src/commands/connection_settings/protocol_clear.rs create mode 100644 src/commands/connection_settings/protocol_disable.rs create mode 100644 src/commands/connection_settings/protocol_enable.rs create mode 100644 src/commands/connection_settings/tag_types.rs create mode 100644 src/commands/connection_settings/tag_types_all.rs create mode 100644 src/commands/connection_settings/tag_types_available.rs create mode 100644 src/commands/connection_settings/tag_types_clear.rs create mode 100644 src/commands/connection_settings/tag_types_disable.rs create mode 100644 src/commands/connection_settings/tag_types_enable.rs create mode 100644 src/commands/connection_settings/tag_types_reset.rs create mode 100644 src/commands/mounts_and_neighbors.rs create mode 100644 src/commands/mounts_and_neighbors/listmounts.rs create mode 100644 src/commands/mounts_and_neighbors/listneighbors.rs create mode 100644 src/commands/mounts_and_neighbors/mount.rs create mode 100644 src/commands/mounts_and_neighbors/unmount.rs create mode 100644 src/commands/partition_commands.rs create mode 100644 src/commands/partition_commands/delpartition.rs create mode 100644 src/commands/partition_commands/listpartitions.rs create mode 100644 src/commands/partition_commands/moveoutput.rs create mode 100644 src/commands/partition_commands/newpartition.rs create mode 100644 src/commands/partition_commands/partition.rs create mode 100644 src/commands/reflection.rs create mode 100644 src/commands/reflection/commands.rs create mode 100644 src/commands/reflection/config.rs create mode 100644 src/commands/reflection/decoders.rs create mode 100644 src/commands/reflection/not_commands.rs create mode 100644 src/commands/reflection/url_handlers.rs create mode 100644 src/commands/stored_playlists.rs create mode 100644 src/commands/stored_playlists/listplaylist.rs create mode 100644 src/commands/stored_playlists/listplaylistinfo.rs create mode 100644 src/commands/stored_playlists/listplaylists.rs create mode 100644 src/commands/stored_playlists/load.rs create mode 100644 src/commands/stored_playlists/playlistadd.rs create mode 100644 src/commands/stored_playlists/playlistclear.rs create mode 100644 src/commands/stored_playlists/playlistdelete.rs create mode 100644 src/commands/stored_playlists/playlistlength.rs create mode 100644 src/commands/stored_playlists/playlistmove.rs create mode 100644 src/commands/stored_playlists/rename.rs create mode 100644 src/commands/stored_playlists/rm.rs create mode 100644 src/commands/stored_playlists/save.rs create mode 100644 src/commands/stored_playlists/searchplaylist.rs diff --git a/src/commands.rs b/src/commands.rs index beb6d44..ff9008e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4,17 +4,27 @@ use crate::Request; mod audio_output_devices; mod client_to_client; +mod connection_settings; mod controlling_playback; +mod mounts_and_neighbors; +mod partition_commands; mod playback_options; mod querying_mpd_status; mod queue; +mod reflection; +mod stored_playlists; pub use audio_output_devices::*; pub use client_to_client::*; +pub use connection_settings::*; pub use controlling_playback::*; +pub use mounts_and_neighbors::*; +pub use partition_commands::*; pub use playback_options::*; pub use querying_mpd_status::*; pub use queue::*; +pub use reflection::*; +pub use stored_playlists::*; pub trait Command { type Response; @@ -87,10 +97,13 @@ pub enum RequestParserError { MissingNewline, } +// TODO: should these be renamed to fit the mpd docs? +// "Attribute" instead of "Property"? #[derive(Debug, Clone, PartialEq)] pub enum ResponseParserError<'a> { MissingProperty(&'a str), UnexpectedPropertyType(&'a str, &'a str), + UnexpectedProperty(&'a str), InvalidProperty(&'a str, &'a str), SyntaxError(u64, &'a str), UnexpectedEOF, diff --git a/src/commands/audio_output_devices.rs b/src/commands/audio_output_devices.rs index 87c4bf0..411bb72 100644 --- a/src/commands/audio_output_devices.rs +++ b/src/commands/audio_output_devices.rs @@ -8,4 +8,4 @@ pub use disableoutput::DisableOutput; pub use enableoutput::EnableOutput; pub use outputs::Outputs; pub use outputset::OutputSet; -pub use toggleoutput::ToggleOutput; \ No newline at end of file +pub use toggleoutput::ToggleOutput; diff --git a/src/commands/connection_settings.rs b/src/commands/connection_settings.rs new file mode 100644 index 0000000..4c1579c --- /dev/null +++ b/src/commands/connection_settings.rs @@ -0,0 +1,37 @@ +pub mod binary_limit; +pub mod close; +pub mod kill; +pub mod password; +pub mod ping; +pub mod protocol; +pub mod protocol_all; +pub mod protocol_available; +pub mod protocol_clear; +pub mod protocol_disable; +pub mod protocol_enable; +pub mod tag_types; +pub mod tag_types_all; +pub mod tag_types_available; +pub mod tag_types_clear; +pub mod tag_types_disable; +pub mod tag_types_enable; +pub mod tag_types_reset; + +pub use binary_limit::BinaryLimit; +pub use close::Close; +pub use kill::Kill; +pub use password::Password; +pub use ping::Ping; +pub use protocol::Protocol; +pub use protocol_all::ProtocolAll; +pub use protocol_available::ProtocolAvailable; +pub use protocol_clear::ProtocolClear; +pub use protocol_disable::ProtocolDisable; +pub use protocol_enable::ProtocolEnable; +pub use tag_types::TagTypes; +pub use tag_types_all::TagTypesAll; +pub use tag_types_available::TagTypesAvailable; +pub use tag_types_clear::TagTypesClear; +pub use tag_types_disable::TagTypesDisable; +pub use tag_types_enable::TagTypesEnable; +pub use tag_types_reset::TagTypesReset; diff --git a/src/commands/connection_settings/binary_limit.rs b/src/commands/connection_settings/binary_limit.rs new file mode 100644 index 0000000..438ad30 --- /dev/null +++ b/src/commands/connection_settings/binary_limit.rs @@ -0,0 +1,27 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct BinaryLimit; + +impl Command for BinaryLimit { + type Response = (); + const COMMAND: &'static str = "binarylimit"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let limit = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let limit = limit + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, limit.to_string()))?; + debug_assert!(parts.next().is_none()); + Ok((Request::BinaryLimit(limit), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/close.rs b/src/commands/connection_settings/close.rs new file mode 100644 index 0000000..b46d3d0 --- /dev/null +++ b/src/commands/connection_settings/close.rs @@ -0,0 +1,22 @@ +use crate::commands::{ + Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, +}; + +pub struct Close; + +impl Command for Close { + type Response = (); + const COMMAND: &'static str = "close"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Close, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/kill.rs b/src/commands/connection_settings/kill.rs new file mode 100644 index 0000000..01d2a6a --- /dev/null +++ b/src/commands/connection_settings/kill.rs @@ -0,0 +1,22 @@ +use crate::commands::{ + Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, +}; + +pub struct Kill; + +impl Command for Kill { + type Response = (); + const COMMAND: &'static str = "kill"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Kill, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/password.rs b/src/commands/connection_settings/password.rs new file mode 100644 index 0000000..56ecdc9 --- /dev/null +++ b/src/commands/connection_settings/password.rs @@ -0,0 +1,27 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct Password; + +impl Command for Password { + type Response = (); + const COMMAND: &'static str = "password"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let password = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + debug_assert!(parts.next().is_none()); + Ok((Request::Password(password), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/ping.rs b/src/commands/connection_settings/ping.rs new file mode 100644 index 0000000..600594c --- /dev/null +++ b/src/commands/connection_settings/ping.rs @@ -0,0 +1,22 @@ +use crate::commands::{ + Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, +}; + +pub struct Ping; + +impl Command for Ping { + type Response = (); + const COMMAND: &'static str = "ping"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Ping, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/protocol.rs b/src/commands/connection_settings/protocol.rs new file mode 100644 index 0000000..fe16f16 --- /dev/null +++ b/src/commands/connection_settings/protocol.rs @@ -0,0 +1,22 @@ +use crate::{ + commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError}, + Request, +}; + +pub struct Protocol; + +impl Command for Protocol { + type Response = (); + const COMMAND: &'static str = "protocol"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Protocol, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/connection_settings/protocol_all.rs b/src/commands/connection_settings/protocol_all.rs new file mode 100644 index 0000000..318657e --- /dev/null +++ b/src/commands/connection_settings/protocol_all.rs @@ -0,0 +1,23 @@ +use crate::{ + commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError}, + Request, +}; + +pub struct ProtocolAll; + +impl Command for ProtocolAll { + type Response = (); + const COMMAND: &'static str = "protocol all"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::ProtocolAll, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/protocol_available.rs b/src/commands/connection_settings/protocol_available.rs new file mode 100644 index 0000000..02dab77 --- /dev/null +++ b/src/commands/connection_settings/protocol_available.rs @@ -0,0 +1,22 @@ +use crate::{ + commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError}, + Request, +}; + +pub struct ProtocolAvailable; + +impl Command for ProtocolAvailable { + type Response = (); + const COMMAND: &'static str = "protocol available"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::ProtocolAvailable, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/connection_settings/protocol_clear.rs b/src/commands/connection_settings/protocol_clear.rs new file mode 100644 index 0000000..b698d3f --- /dev/null +++ b/src/commands/connection_settings/protocol_clear.rs @@ -0,0 +1,23 @@ +use crate::{ + commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError}, + Request, +}; + +pub struct ProtocolClear; + +impl Command for ProtocolClear { + type Response = (); + const COMMAND: &'static str = "protocol clear"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::ProtocolClear, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/protocol_disable.rs b/src/commands/connection_settings/protocol_disable.rs new file mode 100644 index 0000000..07da518 --- /dev/null +++ b/src/commands/connection_settings/protocol_disable.rs @@ -0,0 +1,34 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct ProtocolDisable; + +impl Command for ProtocolDisable { + type Response = (); + const COMMAND: &'static str = "protocol disable"; + + fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let mut parts = parts.peekable(); + if parts.peek().is_none() { + return Err(RequestParserError::UnexpectedEOF); + } + + let mut protocols = Vec::new(); + for protocol in parts { + protocols.push(protocol.to_string()); + } + + Ok((Request::ProtocolDisable(protocols), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/protocol_enable.rs b/src/commands/connection_settings/protocol_enable.rs new file mode 100644 index 0000000..6fb1363 --- /dev/null +++ b/src/commands/connection_settings/protocol_enable.rs @@ -0,0 +1,34 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct ProtocolEnable; + +impl Command for ProtocolEnable { + type Response = (); + const COMMAND: &'static str = "protocol enable"; + + fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let mut parts = parts.peekable(); + if parts.peek().is_none() { + return Err(RequestParserError::UnexpectedEOF); + } + + let mut protocols = Vec::new(); + for protocol in parts { + protocols.push(protocol.to_string()); + } + + Ok((Request::ProtocolEnable(protocols), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/tag_types.rs b/src/commands/connection_settings/tag_types.rs new file mode 100644 index 0000000..9596149 --- /dev/null +++ b/src/commands/connection_settings/tag_types.rs @@ -0,0 +1,42 @@ +use crate::commands::{ + Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct TagTypes; + +pub type TagTypesResponse = Vec; + +impl Command for TagTypes { + type Response = TagTypesResponse; + const COMMAND: &'static str = "tagtypes"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::TagTypes, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: Vec<_> = parts.into(); + + let mut tagtypes = Vec::with_capacity(parts.len()); + for (key, value) in parts.into_iter() { + debug_assert_eq!(key, "tagtype"); + + let tagtype = match value { + GenericResponseValue::Text(name) => name.to_string(), + GenericResponseValue::Binary(_) => { + return Err(ResponseParserError::UnexpectedPropertyType( + "tagtype", "Binary", + )) + } + }; + + tagtypes.push(tagtype); + } + + Ok(tagtypes) + } +} diff --git a/src/commands/connection_settings/tag_types_all.rs b/src/commands/connection_settings/tag_types_all.rs new file mode 100644 index 0000000..cb12ac3 --- /dev/null +++ b/src/commands/connection_settings/tag_types_all.rs @@ -0,0 +1,23 @@ +use crate::{ + commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError}, + Request, +}; + +pub struct TagTypesAll; + +impl Command for TagTypesAll { + type Response = (); + const COMMAND: &'static str = "tagtypes all"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::TagTypesAll, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/tag_types_available.rs b/src/commands/connection_settings/tag_types_available.rs new file mode 100644 index 0000000..1628cac --- /dev/null +++ b/src/commands/connection_settings/tag_types_available.rs @@ -0,0 +1,22 @@ +use crate::{ + commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError}, + Request, +}; + +pub struct TagTypesAvailable; + +impl Command for TagTypesAvailable { + type Response = (); + const COMMAND: &'static str = "tagtypes available"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::TagTypesAvailable, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/connection_settings/tag_types_clear.rs b/src/commands/connection_settings/tag_types_clear.rs new file mode 100644 index 0000000..44fc672 --- /dev/null +++ b/src/commands/connection_settings/tag_types_clear.rs @@ -0,0 +1,23 @@ +use crate::{ + commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError}, + Request, +}; + +pub struct TagTypesClear; + +impl Command for TagTypesClear { + type Response = (); + const COMMAND: &'static str = "tagtypes clear"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::TagTypesClear, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/tag_types_disable.rs b/src/commands/connection_settings/tag_types_disable.rs new file mode 100644 index 0000000..ddf932a --- /dev/null +++ b/src/commands/connection_settings/tag_types_disable.rs @@ -0,0 +1,34 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct TagTypesDisable; + +impl Command for TagTypesDisable { + type Response = (); + const COMMAND: &'static str = "tagtypes disable"; + + fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let mut parts = parts.peekable(); + if parts.peek().is_none() { + return Err(RequestParserError::UnexpectedEOF); + } + + let mut tag_types = Vec::new(); + for tag_type in parts { + tag_types.push(tag_type.to_string()); + } + + Ok((Request::TagTypesDisable(tag_types), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/tag_types_enable.rs b/src/commands/connection_settings/tag_types_enable.rs new file mode 100644 index 0000000..0184b78 --- /dev/null +++ b/src/commands/connection_settings/tag_types_enable.rs @@ -0,0 +1,34 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct TagTypesEnable; + +impl Command for TagTypesEnable { + type Response = (); + const COMMAND: &'static str = "tagtypes enable"; + + fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let mut parts = parts.peekable(); + if parts.peek().is_none() { + return Err(RequestParserError::UnexpectedEOF); + } + + let mut tag_types = Vec::new(); + for tag_type in parts { + tag_types.push(tag_type.to_string()); + } + + Ok((Request::TagTypesEnable(tag_types), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/connection_settings/tag_types_reset.rs b/src/commands/connection_settings/tag_types_reset.rs new file mode 100644 index 0000000..844e28b --- /dev/null +++ b/src/commands/connection_settings/tag_types_reset.rs @@ -0,0 +1,34 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct TagTypesReset; + +impl Command for TagTypesReset { + type Response = (); + const COMMAND: &'static str = "tagtypes reset"; + + fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let mut parts = parts.peekable(); + if parts.peek().is_none() { + return Err(RequestParserError::UnexpectedEOF); + } + + let mut tag_types = Vec::new(); + for tag_type in parts { + tag_types.push(tag_type.to_string()); + } + + Ok((Request::TagTypesReset(tag_types), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/mounts_and_neighbors.rs b/src/commands/mounts_and_neighbors.rs new file mode 100644 index 0000000..09f378d --- /dev/null +++ b/src/commands/mounts_and_neighbors.rs @@ -0,0 +1,9 @@ +pub mod listmounts; +pub mod listneighbors; +pub mod mount; +pub mod unmount; + +pub use listmounts::ListMounts; +pub use listneighbors::ListNeighbors; +pub use mount::Mount; +pub use unmount::Unmount; diff --git a/src/commands/mounts_and_neighbors/listmounts.rs b/src/commands/mounts_and_neighbors/listmounts.rs new file mode 100644 index 0000000..3f39d24 --- /dev/null +++ b/src/commands/mounts_and_neighbors/listmounts.rs @@ -0,0 +1,49 @@ +use crate::{ + commands::{ + Command, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +// pub struct Unmount; + +// impl Command for Unmount { +// type Response = (); +// const COMMAND: &'static str = "unmount"; + +// fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { +// let path = parts +// .next() +// .ok_or(RequestParserError::UnexpectedEOF)? +// .to_string(); + +// debug_assert!(parts.next().is_none()); + +// Ok((Request::Unmount(path), "")) +// } + +// fn parse_response( +// parts: ResponseAttributes<'_>, +// ) -> Result { +// debug_assert!(parts.is_empty()); +// Ok(()) +// } +// } + +pub struct ListMounts; + +impl Command for ListMounts { + type Response = Vec<(String, String)>; + const COMMAND: &'static str = "listmounts"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::ListMounts, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/mounts_and_neighbors/listneighbors.rs b/src/commands/mounts_and_neighbors/listneighbors.rs new file mode 100644 index 0000000..e8a5ddf --- /dev/null +++ b/src/commands/mounts_and_neighbors/listneighbors.rs @@ -0,0 +1,24 @@ +use crate::{ + commands::{ + Command, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct ListNeighbors; + +impl Command for ListNeighbors { + type Response = Vec<(String, String)>; + const COMMAND: &'static str = "listneighbors"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::ListNeighbors, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/mounts_and_neighbors/mount.rs b/src/commands/mounts_and_neighbors/mount.rs new file mode 100644 index 0000000..8df78cb --- /dev/null +++ b/src/commands/mounts_and_neighbors/mount.rs @@ -0,0 +1,36 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct Mount; + +impl Command for Mount { + type Response = (); + const COMMAND: &'static str = "mount"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let path = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + let uri = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::Mount(path, uri), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/mounts_and_neighbors/unmount.rs b/src/commands/mounts_and_neighbors/unmount.rs new file mode 100644 index 0000000..74ea281 --- /dev/null +++ b/src/commands/mounts_and_neighbors/unmount.rs @@ -0,0 +1,31 @@ +use crate::{ + commands::{ + Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError, + }, + Request, +}; + +pub struct Unmount; + +impl Command for Unmount { + type Response = (); + const COMMAND: &'static str = "unmount"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let path = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::Unmount(path), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/partition_commands.rs b/src/commands/partition_commands.rs new file mode 100644 index 0000000..1145ec9 --- /dev/null +++ b/src/commands/partition_commands.rs @@ -0,0 +1,11 @@ +pub mod delpartition; +pub mod listpartitions; +pub mod moveoutput; +pub mod newpartition; +pub mod partition; + +pub use delpartition::DelPartition; +pub use listpartitions::ListPartitions; +pub use moveoutput::MoveOutput; +pub use newpartition::NewPartition; +pub use partition::Partition; diff --git a/src/commands/partition_commands/delpartition.rs b/src/commands/partition_commands/delpartition.rs new file mode 100644 index 0000000..d961b7b --- /dev/null +++ b/src/commands/partition_commands/delpartition.rs @@ -0,0 +1,29 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct DelPartition; + +impl Command for DelPartition { + type Response = (); + const COMMAND: &'static str = "delpartition"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let partition = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::DelPartition(partition), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/partition_commands/listpartitions.rs b/src/commands/partition_commands/listpartitions.rs new file mode 100644 index 0000000..1fa9e5d --- /dev/null +++ b/src/commands/partition_commands/listpartitions.rs @@ -0,0 +1,43 @@ +use crate::commands::{ + Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct ListPartitions; + +pub type ListPartitionsResponse = Vec; + +impl Command for ListPartitions { + type Response = ListPartitionsResponse; + const COMMAND: &'static str = "listpartitions"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::ListPartitions, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: Vec<_> = parts.into(); + + let mut partitions = Vec::with_capacity(parts.len()); + for (key, value) in parts.into_iter() { + debug_assert_eq!(key, "partition"); + + let partition = match value { + GenericResponseValue::Text(name) => name.to_string(), + GenericResponseValue::Binary(_) => { + return Err(ResponseParserError::UnexpectedPropertyType( + "partition", + "Binary", + )) + } + }; + + partitions.push(partition); + } + + Ok(partitions) + } +} diff --git a/src/commands/partition_commands/moveoutput.rs b/src/commands/partition_commands/moveoutput.rs new file mode 100644 index 0000000..0fa8188 --- /dev/null +++ b/src/commands/partition_commands/moveoutput.rs @@ -0,0 +1,29 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct MoveOutput; + +impl Command for MoveOutput { + type Response = (); + const COMMAND: &'static str = "moveoutput"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let output_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::MoveOutput(output_name), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/partition_commands/newpartition.rs b/src/commands/partition_commands/newpartition.rs new file mode 100644 index 0000000..42afd92 --- /dev/null +++ b/src/commands/partition_commands/newpartition.rs @@ -0,0 +1,29 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct NewPartition; + +impl Command for NewPartition { + type Response = (); + const COMMAND: &'static str = "newpartition"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let partition = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::NewPartition(partition), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/partition_commands/partition.rs b/src/commands/partition_commands/partition.rs new file mode 100644 index 0000000..9a2584e --- /dev/null +++ b/src/commands/partition_commands/partition.rs @@ -0,0 +1,29 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct Partition; + +impl Command for Partition { + type Response = (); + const COMMAND: &'static str = "partition"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let partition = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::Partition(partition), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/playback_options/volume.rs b/src/commands/playback_options/volume.rs index f6baba9..87a77e7 100644 --- a/src/commands/playback_options/volume.rs +++ b/src/commands/playback_options/volume.rs @@ -1,9 +1,12 @@ use std::str::FromStr; -use crate::{commands::{ - Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, - ResponseParserError, -}, request::VolumeValue}; +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + request::VolumeValue, +}; pub struct Volume; diff --git a/src/commands/reflection.rs b/src/commands/reflection.rs new file mode 100644 index 0000000..eb7f155 --- /dev/null +++ b/src/commands/reflection.rs @@ -0,0 +1,11 @@ +pub mod commands; +pub mod config; +pub mod decoders; +pub mod not_commands; +pub mod url_handlers; + +pub use commands::Commands; +pub use config::Config; +pub use decoders::Decoders; +pub use not_commands::NotCommands; +pub use url_handlers::UrlHandlers; diff --git a/src/commands/reflection/commands.rs b/src/commands/reflection/commands.rs new file mode 100644 index 0000000..94d9eb8 --- /dev/null +++ b/src/commands/reflection/commands.rs @@ -0,0 +1,21 @@ +use crate::commands::{ + Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, +}; + +pub struct Commands; + +impl Command for Commands { + type Response = (); + const COMMAND: &'static str = "commands"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Commands, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/reflection/config.rs b/src/commands/reflection/config.rs new file mode 100644 index 0000000..5ee4c4d --- /dev/null +++ b/src/commands/reflection/config.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; + +use crate::commands::{ + get_and_parse_property, get_property, Command, Request, RequestParserResult, + ResponseAttributes, ResponseParserError, +}; + +pub struct Config; + +pub struct ConfigResponse { + pub music_directory: String, + pub playlist_directory: String, + pub pcre: bool, +} + +impl Command for Config { + type Response = ConfigResponse; + const COMMAND: &'static str = "config"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Config, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: HashMap<_, _> = parts.into(); + + let music_directory = get_property!(parts, "music_directory", Text).to_string(); + let playlist_directory = get_property!(parts, "playlist_directory", Text).to_string(); + + // TODO: is this parsed correctly? + let pcre = get_and_parse_property!(parts, "pcre", Text); + + Ok(ConfigResponse { + music_directory, + playlist_directory, + pcre, + }) + } +} diff --git a/src/commands/reflection/decoders.rs b/src/commands/reflection/decoders.rs new file mode 100644 index 0000000..95f9301 --- /dev/null +++ b/src/commands/reflection/decoders.rs @@ -0,0 +1,29 @@ +use crate::commands::{ + Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, +}; + +pub struct Decoders; + +pub struct Decoder { + pub plugin: String, + pub suffixes: Vec, + pub mime_types: Vec, +} + +pub type DecodersResponse = Vec; + +impl Command for Decoders { + type Response = DecodersResponse; + const COMMAND: &'static str = "decoders"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::Decoders, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/reflection/not_commands.rs b/src/commands/reflection/not_commands.rs new file mode 100644 index 0000000..af79fe9 --- /dev/null +++ b/src/commands/reflection/not_commands.rs @@ -0,0 +1,21 @@ +use crate::commands::{ + Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, +}; + +pub struct NotCommands; + +impl Command for NotCommands { + type Response = (); + const COMMAND: &'static str = "not_commands"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::NotCommands, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/reflection/url_handlers.rs b/src/commands/reflection/url_handlers.rs new file mode 100644 index 0000000..5f1c087 --- /dev/null +++ b/src/commands/reflection/url_handlers.rs @@ -0,0 +1,40 @@ +use crate::commands::{ + Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct UrlHandlers; + +pub type UrlHandlersResponse = Vec; + +impl Command for UrlHandlers { + type Response = UrlHandlersResponse; + const COMMAND: &'static str = "urlhandlers"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::UrlHandlers, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: Vec<_> = parts.into(); + let mut url_handlers = Vec::new(); + for (key, value) in parts.into_iter() { + if key != "handler" { + return Err(ResponseParserError::UnexpectedProperty(key)); + } + let value = match value { + GenericResponseValue::Text(value) => value, + GenericResponseValue::Binary(_) => { + return Err(ResponseParserError::UnexpectedPropertyType( + "handler", "Binary", + )) + } + }; + url_handlers.push(value.to_string()); + } + Ok(url_handlers) + } +} diff --git a/src/commands/stored_playlists.rs b/src/commands/stored_playlists.rs new file mode 100644 index 0000000..3ac181e --- /dev/null +++ b/src/commands/stored_playlists.rs @@ -0,0 +1,27 @@ +pub mod listplaylist; +pub mod listplaylistinfo; +pub mod listplaylists; +pub mod load; +pub mod playlistadd; +pub mod playlistclear; +pub mod playlistdelete; +pub mod playlistlength; +pub mod playlistmove; +pub mod rename; +pub mod rm; +pub mod save; +pub mod searchplaylist; + +pub use listplaylist::ListPlaylist; +pub use listplaylistinfo::ListPlaylistInfo; +pub use listplaylists::ListPlaylists; +pub use load::Load; +pub use playlistadd::PlaylistAdd; +pub use playlistclear::PlaylistClear; +pub use playlistdelete::PlaylistDelete; +pub use playlistlength::PlaylistLength; +pub use playlistmove::PlaylistMove; +pub use rename::Rename; +pub use rm::Rm; +pub use save::Save; +pub use searchplaylist::SearchPlaylist; diff --git a/src/commands/stored_playlists/listplaylist.rs b/src/commands/stored_playlists/listplaylist.rs new file mode 100644 index 0000000..4d7c2fb --- /dev/null +++ b/src/commands/stored_playlists/listplaylist.rs @@ -0,0 +1,53 @@ +use crate::{ + commands::{ + Command, GenericResponseValue, Request, RequestParserError, RequestParserResult, + ResponseAttributes, ResponseParserError, + }, + common::PlaylistName, +}; + +pub struct ListPlaylist; + +pub type ListPlaylistResponse = Vec; + +impl Command for ListPlaylist { + type Response = ListPlaylistResponse; + const COMMAND: &'static str = "listplaylist"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = name + .parse::() + .map_err(|_| RequestParserError::SyntaxError(0, name.to_owned()))?; + + let range = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_owned())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::ListPlaylist(name.to_string(), range), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + let parts: Vec<_> = parts.into(); + parts + .into_iter() + .map(|(name, value)| { + debug_assert_eq!(name, "file"); + match value { + GenericResponseValue::Text(value) => Ok(value.to_string()), + GenericResponseValue::Binary(_) => Err( + ResponseParserError::UnexpectedPropertyType("file", "Binary"), + ), + } + }) + .collect::, _>>() + } +} diff --git a/src/commands/stored_playlists/listplaylistinfo.rs b/src/commands/stored_playlists/listplaylistinfo.rs new file mode 100644 index 0000000..3c9ea6e --- /dev/null +++ b/src/commands/stored_playlists/listplaylistinfo.rs @@ -0,0 +1,39 @@ +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + common::PlaylistName, +}; + +pub struct ListPlaylistInfo; + +impl Command for ListPlaylistInfo { + type Response = (); + const COMMAND: &'static str = "listplaylistinfo"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = name + .parse::() + .map_err(|_| RequestParserError::SyntaxError(0, name.to_owned()))?; + + let range = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_owned())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::ListPlaylistInfo(name, range), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/stored_playlists/listplaylists.rs b/src/commands/stored_playlists/listplaylists.rs new file mode 100644 index 0000000..4593a13 --- /dev/null +++ b/src/commands/stored_playlists/listplaylists.rs @@ -0,0 +1,21 @@ +use crate::commands::{ + Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError, +}; + +pub struct ListPlaylists; + +impl Command for ListPlaylists { + type Response = (); + const COMMAND: &'static str = "listplaylists"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + debug_assert!(parts.next().is_none()); + Ok((Request::ListPlaylists, "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/commands/stored_playlists/load.rs b/src/commands/stored_playlists/load.rs new file mode 100644 index 0000000..f5dac56 --- /dev/null +++ b/src/commands/stored_playlists/load.rs @@ -0,0 +1,46 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct Load; + +impl Command for Load { + type Response = (); + const COMMAND: &'static str = "load"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + let mut range = None; + let mut pos = None; + + for _ in 0..2 { + if let Some(range_or_pos) = parts.next() { + if range_or_pos.contains(':') { + range = Some(range_or_pos.parse().map_err(|_| { + RequestParserError::SyntaxError(0, range_or_pos.to_owned()) + })?); + } else { + pos = Some(range_or_pos.parse().map_err(|_| { + RequestParserError::SyntaxError(0, range_or_pos.to_owned()) + })?); + } + } + } + + debug_assert!(parts.next().is_none()); + + Ok((Request::Load(playlist_name, range, pos), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/stored_playlists/playlistadd.rs b/src/commands/stored_playlists/playlistadd.rs new file mode 100644 index 0000000..a979317 --- /dev/null +++ b/src/commands/stored_playlists/playlistadd.rs @@ -0,0 +1,42 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct PlaylistAdd; + +impl Command for PlaylistAdd { + type Response = (); + const COMMAND: &'static str = "playlistadd"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + let uri = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + let position = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_owned())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::PlaylistAdd(playlist_name, uri, position), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/stored_playlists/playlistclear.rs b/src/commands/stored_playlists/playlistclear.rs new file mode 100644 index 0000000..d4cd1a8 --- /dev/null +++ b/src/commands/stored_playlists/playlistclear.rs @@ -0,0 +1,29 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct PlaylistClear; + +impl Command for PlaylistClear { + type Response = (); + const COMMAND: &'static str = "playlistclear"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::PlaylistClear(playlist_name), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/stored_playlists/playlistdelete.rs b/src/commands/stored_playlists/playlistdelete.rs new file mode 100644 index 0000000..0a95334 --- /dev/null +++ b/src/commands/stored_playlists/playlistdelete.rs @@ -0,0 +1,35 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct PlaylistDelete; + +impl Command for PlaylistDelete { + type Response = (); + const COMMAND: &'static str = "playlistdelete"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + // TODO: this can be a range, according to docs + let position = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let position = position + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, position.to_string()))?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::PlaylistDelete(playlist_name, position), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/stored_playlists/playlistlength.rs b/src/commands/stored_playlists/playlistlength.rs new file mode 100644 index 0000000..e234f2d --- /dev/null +++ b/src/commands/stored_playlists/playlistlength.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; + +use crate::commands::{ + get_and_parse_property, Command, Request, RequestParserError, RequestParserResult, + ResponseAttributes, ResponseParserError, +}; + +pub struct PlaylistLength; + +pub struct PlaylistLengthResponse { + pub songs: u64, + pub playtime: u64, +} + +impl Command for PlaylistLength { + type Response = PlaylistLengthResponse; + const COMMAND: &'static str = "playlistlength"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::PlaylistLength(playlist_name), "")) + } + + 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(PlaylistLengthResponse { songs, playtime }) + } +} diff --git a/src/commands/stored_playlists/playlistmove.rs b/src/commands/stored_playlists/playlistmove.rs new file mode 100644 index 0000000..1d806e3 --- /dev/null +++ b/src/commands/stored_playlists/playlistmove.rs @@ -0,0 +1,46 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct PlaylistMove; + +impl Command for PlaylistMove { + type Response = (); + const COMMAND: &'static str = "playlistmove"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + let mut from = None; + let from_or_range_or_to = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + + let to = parts.next(); + + debug_assert!(parts.next().is_none()); + + let to = if let Some(to) = to { + from = Some(from_or_range_or_to.parse().map_err(|_| { + RequestParserError::SyntaxError(0, from_or_range_or_to.to_string()) + })?); + to.parse() + .map_err(|_| RequestParserError::SyntaxError(0, to.to_string()))? + } else { + from_or_range_or_to + .parse() + .map_err(|_| RequestParserError::SyntaxError(0, from_or_range_or_to.to_string()))? + }; + + Ok((Request::PlaylistMove(playlist_name, from, to), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/stored_playlists/rename.rs b/src/commands/stored_playlists/rename.rs new file mode 100644 index 0000000..0ed7951 --- /dev/null +++ b/src/commands/stored_playlists/rename.rs @@ -0,0 +1,34 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct Rename; + +impl Command for Rename { + type Response = (); + const COMMAND: &'static str = "rename"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let old_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + let new_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::Rename(old_name, new_name), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/stored_playlists/rm.rs b/src/commands/stored_playlists/rm.rs new file mode 100644 index 0000000..4ab2579 --- /dev/null +++ b/src/commands/stored_playlists/rm.rs @@ -0,0 +1,29 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct Rm; + +impl Command for Rm { + type Response = (); + const COMMAND: &'static str = "rm"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + debug_assert!(parts.next().is_none()); + + Ok((Request::Rm(playlist_name), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/stored_playlists/save.rs b/src/commands/stored_playlists/save.rs new file mode 100644 index 0000000..b10ac41 --- /dev/null +++ b/src/commands/stored_playlists/save.rs @@ -0,0 +1,37 @@ +use crate::commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, +}; + +pub struct Save; + +impl Command for Save { + type Response = (); + const COMMAND: &'static str = "save"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let playlist_name = parts + .next() + .ok_or(RequestParserError::UnexpectedEOF)? + .to_string(); + + let mode = parts + .next() + .map(|m| { + m.parse() + .map_err(|_| RequestParserError::SyntaxError(0, m.to_string())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::Save(playlist_name, mode), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + debug_assert!(parts.is_empty()); + Ok(()) + } +} diff --git a/src/commands/stored_playlists/searchplaylist.rs b/src/commands/stored_playlists/searchplaylist.rs new file mode 100644 index 0000000..69f56cf --- /dev/null +++ b/src/commands/stored_playlists/searchplaylist.rs @@ -0,0 +1,44 @@ +use crate::{ + commands::{ + Command, Request, RequestParserError, RequestParserResult, ResponseAttributes, + ResponseParserError, + }, + common::{Filter, PlaylistName}, +}; + +pub struct SearchPlaylist; + +impl Command for SearchPlaylist { + type Response = (); + const COMMAND: &'static str = "searchplaylist"; + + fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> { + let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let name = name + .parse::() + .map_err(|_| RequestParserError::SyntaxError(0, name.to_owned()))?; + + let filter = parts.next().ok_or(RequestParserError::UnexpectedEOF)?; + let filter = filter + .parse::() + .map_err(|_| RequestParserError::SyntaxError(0, filter.to_owned()))?; + + let range = parts + .next() + .map(|s| { + s.parse() + .map_err(|_| RequestParserError::SyntaxError(0, s.to_owned())) + }) + .transpose()?; + + debug_assert!(parts.next().is_none()); + + Ok((Request::SearchPlaylist(name, filter, range), "")) + } + + fn parse_response( + parts: ResponseAttributes<'_>, + ) -> Result { + unimplemented!() + } +} diff --git a/src/request.rs b/src/request.rs index e69a082..c82b617 100644 --- a/src/request.rs +++ b/src/request.rs @@ -80,13 +80,13 @@ pub enum Request { ListPlaylistInfo(PlaylistName, Option), SearchPlaylist(PlaylistName, Filter, Option), ListPlaylists, - Load(PlaylistName, Option, SongPosition), - PlaylistAdd(PlaylistName, Uri, SongPosition), + Load(PlaylistName, Option, Option), + PlaylistAdd(PlaylistName, Uri, Option), PlaylistClear(PlaylistName), PlaylistDelete(PlaylistName, OneOrRange), PlaylistLength(PlaylistName), // TODO: which type of range? - PlaylistMove(PlaylistName, OneOrRange, SongPosition), + PlaylistMove(PlaylistName, Option, SongPosition), Rename(PlaylistName, PlaylistName), Rm(PlaylistName), Save(PlaylistName, Option), @@ -129,7 +129,7 @@ pub enum Request { Rescan(Option), // -- Mount and Neighbor Commands -- // - Mount(Option, Option), + Mount(Path, Uri), Unmount(Path), ListMounts, ListNeighbors, @@ -159,12 +159,12 @@ pub enum Request { Ping, BinaryLimit(u64), TagTypes, - TagTypesDisable(Vec), - TagTypesEnable(Vec), + TagTypesDisable(Vec), + TagTypesEnable(Vec), TagTypesClear, TagTypesAll, TagTypesAvailable, - TagTypesReset(Vec), + TagTypesReset(Vec), Protocol, ProtocolDisable(Vec), ProtocolEnable(Vec), @@ -410,16 +410,56 @@ impl Request { ClearTagId::COMMAND => ClearTagId::parse_request(parts), /* stored playlists */ + ListPlaylist::COMMAND => ListPlaylist::parse_request(parts), + ListPlaylistInfo::COMMAND => ListPlaylistInfo::parse_request(parts), + SearchPlaylist::COMMAND => SearchPlaylist::parse_request(parts), + ListPlaylists::COMMAND => ListPlaylists::parse_request(parts), + Load::COMMAND => Load::parse_request(parts), + PlaylistAdd::COMMAND => PlaylistAdd::parse_request(parts), + PlaylistClear::COMMAND => PlaylistClear::parse_request(parts), + PlaylistDelete::COMMAND => PlaylistDelete::parse_request(parts), + PlaylistLength::COMMAND => PlaylistLength::parse_request(parts), + PlaylistMove::COMMAND => PlaylistMove::parse_request(parts), + Rename::COMMAND => Rename::parse_request(parts), + Rm::COMMAND => Rm::parse_request(parts), + Save::COMMAND => Save::parse_request(parts), /* music database */ /* mounts and neighbors */ + Mount::COMMAND => Mount::parse_request(parts), + Unmount::COMMAND => Unmount::parse_request(parts), + ListMounts::COMMAND => ListMounts::parse_request(parts), + ListNeighbors::COMMAND => ListNeighbors::parse_request(parts), /* stickers */ /* connection settings */ + Close::COMMAND => Close::parse_request(parts), + Kill::COMMAND => Kill::parse_request(parts), + Password::COMMAND => Password::parse_request(parts), + Ping::COMMAND => Ping::parse_request(parts), + BinaryLimit::COMMAND => BinaryLimit::parse_request(parts), + TagTypes::COMMAND => TagTypes::parse_request(parts), + TagTypesDisable::COMMAND => TagTypesDisable::parse_request(parts), + TagTypesEnable::COMMAND => TagTypesEnable::parse_request(parts), + TagTypesClear::COMMAND => TagTypesClear::parse_request(parts), + TagTypesAll::COMMAND => TagTypesAll::parse_request(parts), + TagTypesAvailable::COMMAND => TagTypesAvailable::parse_request(parts), + TagTypesReset::COMMAND => TagTypesReset::parse_request(parts), + Protocol::COMMAND => Protocol::parse_request(parts), + ProtocolDisable::COMMAND => ProtocolDisable::parse_request(parts), + ProtocolEnable::COMMAND => ProtocolEnable::parse_request(parts), + ProtocolClear::COMMAND => ProtocolClear::parse_request(parts), + ProtocolAll::COMMAND => ProtocolAll::parse_request(parts), + ProtocolAvailable::COMMAND => ProtocolAvailable::parse_request(parts), /* partition commands */ + Partition::COMMAND => Partition::parse_request(parts), + ListPartitions::COMMAND => ListPartitions::parse_request(parts), + NewPartition::COMMAND => NewPartition::parse_request(parts), + DelPartition::COMMAND => DelPartition::parse_request(parts), + MoveOutput::COMMAND => MoveOutput::parse_request(parts), /* audio output devices */ DisableOutput::COMMAND => DisableOutput::parse_request(parts), @@ -429,6 +469,11 @@ impl Request { OutputSet::COMMAND => OutputSet::parse_request(parts), /* reflection */ + Config::COMMAND => Config::parse_request(parts), + Commands::COMMAND => Commands::parse_request(parts), + NotCommands::COMMAND => NotCommands::parse_request(parts), + UrlHandlers::COMMAND => UrlHandlers::parse_request(parts), + Decoders::COMMAND => Decoders::parse_request(parts), /* client to client */ Subscribe::COMMAND => Subscribe::parse_request(parts),