Implement some more commands

This commit is contained in:
Oystein Kristoffer Tveit 2024-12-14 00:14:02 +01:00
parent abacb54c8d
commit 9cef59eb88
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
13 changed files with 586 additions and 0 deletions

View File

@ -16,6 +16,7 @@ mod playback_options;
mod querying_mpd_status; mod querying_mpd_status;
mod queue; mod queue;
mod reflection; mod reflection;
mod stickers;
mod stored_playlists; mod stored_playlists;
pub use audio_output_devices::*; pub use audio_output_devices::*;
@ -29,6 +30,7 @@ pub use playback_options::*;
pub use querying_mpd_status::*; pub use querying_mpd_status::*;
pub use queue::*; pub use queue::*;
pub use reflection::*; pub use reflection::*;
pub use stickers::*;
pub use stored_playlists::*; pub use stored_playlists::*;
pub trait Command { pub trait Command {

21
src/commands/stickers.rs Normal file
View File

@ -0,0 +1,21 @@
pub mod sticker_dec;
pub mod sticker_delete;
pub mod sticker_find;
pub mod sticker_get;
pub mod sticker_inc;
pub mod sticker_list;
pub mod sticker_set;
pub mod stickernames;
pub mod stickernamestypes;
pub mod stickertypes;
pub use sticker_dec::StickerDec;
pub use sticker_delete::StickerDelete;
pub use sticker_find::StickerFind;
pub use sticker_get::StickerGet;
pub use sticker_inc::StickerInc;
pub use sticker_list::StickerList;
pub use sticker_set::StickerSet;
pub use stickernames::StickerNames;
pub use stickernamestypes::StickerNamesTypes;
pub use stickertypes::StickerTypes;

View File

@ -0,0 +1,45 @@
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct StickerDec;
impl Command for StickerDec {
type Response = ();
const COMMAND: &'static str = "sticker dec";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let sticker_type = sticker_type
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, sticker_type.to_owned()))?;
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let name = name
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?;
let value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let value = value
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, value.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok((Request::StickerDec(sticker_type, uri, name, value), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -0,0 +1,40 @@
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct StickerDelete;
impl Command for StickerDelete {
type Response = ();
const COMMAND: &'static str = "sticker delete";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let sticker_type = sticker_type
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, sticker_type.to_owned()))?;
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let name = name
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok((Request::StickerDelete(sticker_type, uri, name), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -0,0 +1,120 @@
use crate::commands::{
Command, GenericResponseValue, Request, RequestParserError, RequestParserResult,
ResponseAttributes, ResponseParserError,
};
pub struct StickerFind;
pub struct StickerFindResponseEntry {
pub uri: String,
pub name: String,
pub value: String,
}
pub type StickerFindResponse = Vec<StickerFindResponseEntry>;
impl Command for StickerFind {
type Response = StickerFindResponse;
const COMMAND: &'static str = "sticker find";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let sticker_type = sticker_type
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, sticker_type.to_owned()))?;
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let name = name
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?;
// TODO: handle the case for this command as well:
// sticker find {TYPE} {URI} {NAME} = {VALUE} [sort {SORTTYPE}] [window {START:END}]
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::StickerFind(sticker_type, uri, name, sort, window),
"",
))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
let mut stickers = Vec::new();
for sticker_uri_pair in parts.chunks_exact(2) {
// TODO: don't assume that the order of the properties is fixed
let uri = sticker_uri_pair
.first()
.ok_or(ResponseParserError::UnexpectedEOF)?;
let sticker = sticker_uri_pair
.get(1)
.ok_or(ResponseParserError::UnexpectedEOF)?;
debug_assert!(sticker.0 == "sticker");
// TODO: debug assert that this is a valid sticker type
// debug_assert!(uri.0 == "");
let uri = match uri.1 {
GenericResponseValue::Text(s) => s.to_string(),
GenericResponseValue::Binary(_) => {
return Err(ResponseParserError::UnexpectedPropertyType(uri.0, "Binary"))
}
};
let sticker = match sticker.1 {
GenericResponseValue::Text(s) => s,
GenericResponseValue::Binary(_) => {
return Err(ResponseParserError::UnexpectedPropertyType(
"sticker", "Binary",
))
}
};
// TODO: This assumes the first = is the only one.
// See: https://github.com/MusicPlayerDaemon/MPD/issues/2166
let mut split = sticker.split("=");
let name = split
.next()
.ok_or(ResponseParserError::SyntaxError(0, sticker))?
.to_string();
let value = split
.next()
.ok_or(ResponseParserError::SyntaxError(1, sticker))?
.to_string();
stickers.push(StickerFindResponseEntry { uri, name, value });
}
Ok(stickers)
}
}

View File

@ -0,0 +1,57 @@
use crate::commands::{
Command, GenericResponseValue, Request, RequestParserError, RequestParserResult,
ResponseAttributes, ResponseParserError,
};
pub struct StickerGet;
pub type StickerGetResponse = String;
impl Command for StickerGet {
type Response = StickerGetResponse;
const COMMAND: &'static str = "sticker get";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let sticker_type = sticker_type
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, sticker_type.to_owned()))?;
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let name = name
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok((Request::StickerGet(sticker_type, uri, name), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
let mut parts = parts.into_iter();
let sticker = parts.next().ok_or(ResponseParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
debug_assert!(sticker.0 == "sticker");
let sticker = match sticker.1 {
GenericResponseValue::Text(s) => s.to_string(),
GenericResponseValue::Binary(_) => {
return Err(ResponseParserError::UnexpectedPropertyType(
"sticker", "Binary",
))
}
};
Ok(sticker)
}
}

View File

@ -0,0 +1,45 @@
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct StickerInc;
impl Command for StickerInc {
type Response = ();
const COMMAND: &'static str = "sticker inc";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let sticker_type = sticker_type
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, sticker_type.to_owned()))?;
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let name = name
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?;
let value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let value = value
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, value.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok((Request::StickerInc(sticker_type, uri, name, value), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -0,0 +1,60 @@
use std::collections::HashMap;
use crate::commands::{
Command, GenericResponseValue, Request, RequestParserError, RequestParserResult,
ResponseAttributes, ResponseParserError,
};
pub struct StickerList;
pub type StickerListResponse = HashMap<String, String>;
impl Command for StickerList {
type Response = StickerListResponse;
const COMMAND: &'static str = "sticker list";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let sticker_type = sticker_type
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, "sticker_type".to_owned()))?;
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, "uri".to_owned()))?;
debug_assert!(parts.next().is_none());
Ok((Request::StickerList(sticker_type, uri), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
debug_assert!(parts.iter().all(|(k, _)| *k == "sticker"));
let result = parts
.iter()
.map(|(_, v)| match v {
GenericResponseValue::Text(value) => Ok(value),
GenericResponseValue::Binary(_) => Err(
ResponseParserError::UnexpectedPropertyType("sticker", "Binary"),
),
})
.collect::<Result<Vec<_>, ResponseParserError>>()?;
result
.into_iter()
.map(|v| {
// TODO: This assumes the first = is the only one.
// See: https://github.com/MusicPlayerDaemon/MPD/issues/2166
let mut split = v.split('=');
let key = split.next().ok_or(ResponseParserError::SyntaxError(0, v))?;
let value = split.next().ok_or(ResponseParserError::SyntaxError(1, v))?;
Ok((key.to_string(), value.to_string()))
})
.collect::<Result<HashMap<_, _>, ResponseParserError>>()
}
}

View File

@ -0,0 +1,45 @@
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct StickerSet;
impl Command for StickerSet {
type Response = ();
const COMMAND: &'static str = "sticker set";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let sticker_type = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let sticker_type = sticker_type
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, sticker_type.to_owned()))?;
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
let name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let name = name
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, name.to_owned()))?;
let value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let value = value
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, value.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok((Request::StickerSet(sticker_type, uri, name, value), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -0,0 +1,38 @@
use crate::commands::{
Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct StickerNames;
pub type StickerNamesResponse = Vec<String>;
impl Command for StickerNames {
type Response = StickerNamesResponse;
const COMMAND: &'static str = "stickernames";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::StickerNames, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.0.iter().all(|(k, _)| *k == "name"));
let list = parts
.0
.iter()
.map(|(_, v)| match v {
GenericResponseValue::Text(value) => Ok(value.to_string()),
GenericResponseValue::Binary(_) => Err(
ResponseParserError::UnexpectedPropertyType("name", "Binary"),
),
})
.collect::<Result<Vec<_>, ResponseParserError>>()?;
Ok(list)
}
}

View File

@ -0,0 +1,63 @@
use std::collections::HashMap;
use crate::commands::{
Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct StickerNamesTypes;
pub type StickerNamesTypesResponse = HashMap<String, Vec<String>>;
impl Command for StickerNamesTypes {
type Response = StickerNamesTypesResponse;
const COMMAND: &'static str = "stickernamestypes";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let sticker_type = parts.next().map(|s| s.to_string());
debug_assert!(parts.next().is_none());
Ok((Request::StickerNamesTypes(sticker_type), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
let mut result = HashMap::new();
for name_type_pair in parts.chunks_exact(2) {
// TODO: don't depend on order, just make sure we have both
let (name_key, name_value) = name_type_pair[0];
let (type_key, type_value) = name_type_pair[1];
debug_assert!(name_key == "name");
debug_assert!(type_key == "type");
let name = match name_value {
GenericResponseValue::Text(s) => s.to_string(),
GenericResponseValue::Binary(_) => {
return Err(ResponseParserError::UnexpectedPropertyType(
"name", "Binary",
))
}
};
let sticker_type = match type_value {
GenericResponseValue::Text(s) => s.to_string(),
GenericResponseValue::Binary(_) => {
return Err(ResponseParserError::UnexpectedPropertyType(
"type", "Binary",
))
}
};
result
.entry(name)
.or_insert_with(Vec::new)
.push(sticker_type);
}
Ok(result)
}
}

View File

@ -0,0 +1,38 @@
use crate::commands::{
Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct StickerTypes;
pub type StickerTypesResponse = Vec<String>;
impl Command for StickerTypes {
type Response = StickerTypesResponse;
const COMMAND: &'static str = "stickertypes";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::StickerTypes, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.0.iter().all(|(k, _)| *k == "stickertype"));
let list = parts
.0
.iter()
.map(|(_, v)| match v {
GenericResponseValue::Text(value) => Ok(value.to_string()),
GenericResponseValue::Binary(_) => Err(
ResponseParserError::UnexpectedPropertyType("stickertype", "Binary"),
),
})
.collect::<Result<Vec<_>, ResponseParserError>>()?;
Ok(list)
}
}

View File

@ -139,6 +139,8 @@ pub enum Request {
// -- Sticker Commands -- // // -- Sticker Commands -- //
StickerGet(StickerType, Uri, String), StickerGet(StickerType, Uri, String),
StickerSet(StickerType, Uri, String, String), StickerSet(StickerType, Uri, String, String),
StickerInc(StickerType, Uri, String, usize),
StickerDec(StickerType, Uri, String, usize),
StickerDelete(StickerType, Uri, String), StickerDelete(StickerType, Uri, String),
StickerList(StickerType, Uri), StickerList(StickerType, Uri),
StickerFind(StickerType, Uri, String, Option<Sort>, Option<WindowRange>), StickerFind(StickerType, Uri, String, Option<Sort>, Option<WindowRange>),
@ -475,6 +477,16 @@ impl Request {
ListNeighbors::COMMAND => ListNeighbors::parse_request(parts), ListNeighbors::COMMAND => ListNeighbors::parse_request(parts),
/* stickers */ /* stickers */
StickerGet::COMMAND => StickerGet::parse_request(parts),
StickerSet::COMMAND => StickerSet::parse_request(parts),
StickerInc::COMMAND => StickerInc::parse_request(parts),
StickerDec::COMMAND => StickerDec::parse_request(parts),
StickerDelete::COMMAND => StickerDelete::parse_request(parts),
StickerList::COMMAND => StickerList::parse_request(parts),
StickerFind::COMMAND => StickerFind::parse_request(parts),
StickerNames::COMMAND => StickerNames::parse_request(parts),
StickerTypes::COMMAND => StickerTypes::parse_request(parts),
StickerNamesTypes::COMMAND => StickerNamesTypes::parse_request(parts),
/* connection settings */ /* connection settings */
Close::COMMAND => Close::parse_request(parts), Close::COMMAND => Close::parse_request(parts),