Implement some more commands

This commit is contained in:
Oystein Kristoffer Tveit 2024-11-30 17:12:49 +01:00
parent 1561ae2e80
commit 08104b3537
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
31 changed files with 926 additions and 10 deletions

View File

@ -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;

View File

@ -29,7 +29,7 @@ impl Command for Outputs {
}
fn parse_response(
parts: ResponseAttributes<'_>,
_parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}

View File

@ -87,4 +87,4 @@ mod tests {
})
);
}
}
}

View File

@ -15,7 +15,7 @@ impl Command for GetVol {
}
fn parse_response(
parts: ResponseAttributes<'_>,
_parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
// let volume = get_property!(parts, Volume, "volume");

View File

@ -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 {

View File

@ -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<Self::Response, ResponseParserError> {
unimplemented!()
}

22
src/commands/queue.rs Normal file
View File

@ -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;

40
src/commands/queue/add.rs Normal file
View File

@ -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::<SongPosition>()
.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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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::<SongPosition>()
.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<Self::Response, ResponseParserError> {
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 })
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
unimplemented!()
}
}

View File

@ -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<Self::Response, ResponseParserError> {
unimplemented!()
}
}

View File

@ -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<Self::Response, ResponseParserError> {
unimplemented!()
}
}

View File

@ -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<Self::Response, ResponseParserError> {
unimplemented!()
}
}

View File

@ -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<Self::Response, ResponseParserError> {
unimplemented!()
}
}

View File

@ -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<Self::Response, ResponseParserError> {
unimplemented!()
}
}

View File

@ -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<Self::Response, ResponseParserError> {
unimplemented!()
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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::<Result<Vec<u32>, RequestParserError>>()?;
debug_assert!(parts.next().is_none());
Ok((Request::PrioId(prio, songids), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

View File

@ -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<Self, Self::Err> {
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<SongPosition>);
pub type WindowRange = (Option<SongPosition>, Option<SongPosition>);
#[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<Self, Self::Err> {
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<SongPosition>,
}
impl FromStr for WindowRange {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<TimeWithFractions>,
pub end: Option<TimeWithFractions>,
}
impl FromStr for TimeInterval {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
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;

View File

@ -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<Sort>, Option<WindowRange>),
PlaylistId(SongId),
PlaylistInfo(OneOrRange),
PlaylistInfo(Option<OneOrRange>),
PlaylistSearch(Filter, Option<Sort>, Option<WindowRange>),
// TODO: which type of range?
PlChanges(Version, Option<WindowRange>),
@ -66,7 +68,7 @@ pub enum Request {
// TODO: which type of range?
Prio(Priority, WindowRange),
PrioId(Priority, Vec<SongId>),
RangeId(SongId, WindowRange),
RangeId(SongId, TimeInterval),
Shuffle(Option<OneOrRange>),
Swap(SongPosition, SongPosition),
SwapId(SongId, SongId),