417 lines
16 KiB
Rust
417 lines
16 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::common::*;
|
|
|
|
use crate::commands::*;
|
|
use crate::filter::Filter;
|
|
|
|
// TODO: SingleLineString
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum Request {
|
|
CommandList(Vec<Request>),
|
|
|
|
// -- Query Commands -- //
|
|
ClearError,
|
|
CurrentSong,
|
|
Idle(Option<Vec<SubSystem>>),
|
|
Status,
|
|
Stats,
|
|
|
|
// -- Playback Commands -- //
|
|
Consume(BoolOrOneshot),
|
|
Crossfade(Seconds),
|
|
MixRampDb(f32),
|
|
MixRampDelay(Seconds),
|
|
Random(bool),
|
|
Repeat(bool),
|
|
SetVol(VolumeValue),
|
|
GetVol,
|
|
Single(BoolOrOneshot),
|
|
ReplayGainMode(ReplayGainModeMode),
|
|
ReplayGainStatus,
|
|
Volume(VolumeValue),
|
|
|
|
// -- Playback Control Commands -- //
|
|
Next,
|
|
Pause(Option<bool>),
|
|
Play(SongPosition),
|
|
PlayId(SongId),
|
|
Previous,
|
|
Seek(SongPosition, TimeWithFractions),
|
|
SeekId(SongId, TimeWithFractions),
|
|
SeekCur(SeekMode, TimeWithFractions),
|
|
Stop,
|
|
|
|
// -- Queue Commands -- //
|
|
// TODO: relative mode
|
|
Add(String, Option<SongPosition>),
|
|
// TODO: relative mode
|
|
AddId(String, Option<SongPosition>),
|
|
Clear,
|
|
Delete(OneOrRange),
|
|
DeleteId(SongId),
|
|
// 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(Option<OneOrRange>),
|
|
PlaylistSearch(Filter, Option<Sort>, Option<WindowRange>),
|
|
// TODO: which type of range?
|
|
PlChanges(Version, Option<WindowRange>),
|
|
// TODO: which type of range?
|
|
PlChangesPosId(Version, Option<WindowRange>),
|
|
// TODO: which type of range?
|
|
Prio(Priority, WindowRange),
|
|
PrioId(Priority, Vec<SongId>),
|
|
RangeId(SongId, TimeInterval),
|
|
Shuffle(Option<OneOrRange>),
|
|
Swap(SongPosition, SongPosition),
|
|
SwapId(SongId, SongId),
|
|
AddTagId(SongId, TagName, TagValue),
|
|
ClearTagId(SongId, TagName),
|
|
|
|
// -- Stored Playlist Commands -- //
|
|
ListPlaylist(PlaylistName, Option<WindowRange>),
|
|
ListPlaylistInfo(PlaylistName, Option<WindowRange>),
|
|
SearchPlaylist(PlaylistName, Filter, Option<WindowRange>),
|
|
ListPlaylists,
|
|
Load(PlaylistName, Option<WindowRange>, Option<SongPosition>),
|
|
PlaylistAdd(PlaylistName, Uri, Option<SongPosition>),
|
|
PlaylistClear(PlaylistName),
|
|
PlaylistDelete(PlaylistName, OneOrRange),
|
|
PlaylistLength(PlaylistName),
|
|
// TODO: which type of range?
|
|
PlaylistMove(PlaylistName, Option<OneOrRange>, SongPosition),
|
|
Rename(PlaylistName, PlaylistName),
|
|
Rm(PlaylistName),
|
|
Save(PlaylistName, Option<SaveMode>),
|
|
|
|
// -- Music Database Commands -- //
|
|
AlbumArt(Uri, Offset),
|
|
Count(Filter, Option<GroupType>),
|
|
GetFingerprint(Uri),
|
|
Find(Filter, Option<Sort>, Option<WindowRange>),
|
|
FindAdd(
|
|
Filter,
|
|
Option<Sort>,
|
|
Option<WindowRange>,
|
|
Option<SongPosition>,
|
|
),
|
|
List(TagName, Filter, Option<GroupType>),
|
|
#[deprecated]
|
|
ListAll(Option<Uri>),
|
|
#[deprecated]
|
|
ListAllInfo(Option<Uri>),
|
|
ListFiles(Option<Uri>),
|
|
LsInfo(Option<Uri>),
|
|
ReadComments(Uri),
|
|
ReadPicture(Uri, Offset),
|
|
Search(Filter, Option<Sort>, Option<WindowRange>),
|
|
SearchAdd(
|
|
Filter,
|
|
Option<Sort>,
|
|
Option<WindowRange>,
|
|
Option<SongPosition>,
|
|
),
|
|
SearchAddPl(
|
|
PlaylistName,
|
|
Filter,
|
|
Option<Sort>,
|
|
Option<WindowRange>,
|
|
Option<SongPosition>,
|
|
),
|
|
SearchCount(Filter, Option<GroupType>),
|
|
Update(Option<Uri>),
|
|
Rescan(Option<Uri>),
|
|
|
|
// -- Mount and Neighbor Commands -- //
|
|
Mount(Path, Uri),
|
|
Unmount(Path),
|
|
ListMounts,
|
|
ListNeighbors,
|
|
|
|
// -- Sticker Commands -- //
|
|
StickerGet(StickerType, Uri, String),
|
|
StickerSet(StickerType, Uri, String, String),
|
|
StickerInc(StickerType, Uri, String, usize),
|
|
StickerDec(StickerType, Uri, String, usize),
|
|
StickerDelete(StickerType, Uri, String),
|
|
StickerList(StickerType, Uri),
|
|
StickerFind(StickerType, Uri, String, Option<Sort>, Option<WindowRange>),
|
|
StickerFindValue(
|
|
StickerType,
|
|
Uri,
|
|
String,
|
|
String,
|
|
Option<Sort>,
|
|
Option<WindowRange>,
|
|
),
|
|
StickerNames,
|
|
StickerTypes,
|
|
StickerNamesTypes(Option<StickerType>),
|
|
|
|
// -- Connection Commands -- //
|
|
Close,
|
|
Kill,
|
|
Password(String),
|
|
Ping,
|
|
BinaryLimit(u64),
|
|
TagTypes,
|
|
TagTypesDisable(Vec<TagName>),
|
|
TagTypesEnable(Vec<TagName>),
|
|
TagTypesClear,
|
|
TagTypesAll,
|
|
TagTypesAvailable,
|
|
TagTypesReset(Vec<TagName>),
|
|
Protocol,
|
|
ProtocolDisable(Vec<Feature>),
|
|
ProtocolEnable(Vec<Feature>),
|
|
ProtocolClear,
|
|
ProtocolAll,
|
|
ProtocolAvailable,
|
|
|
|
// -- Partition Commands -- //
|
|
Partition(PartitionName),
|
|
ListPartitions,
|
|
NewPartition(PartitionName),
|
|
DelPartition(PartitionName),
|
|
MoveOutput(String),
|
|
|
|
// -- Audio Output Commands -- //
|
|
DisableOutput(AudioOutputId),
|
|
EnableOutput(AudioOutputId),
|
|
ToggleOutput(AudioOutputId),
|
|
Outputs,
|
|
OutputSet(AudioOutputId, String, String),
|
|
|
|
// -- Reflection Commands -- //
|
|
Config,
|
|
Commands,
|
|
NotCommands,
|
|
UrlHandlers,
|
|
Decoders,
|
|
|
|
// -- Client to Client Commands -- //
|
|
Subscribe(ChannelName),
|
|
Unsubscribe(ChannelName),
|
|
Channels,
|
|
ReadMessages,
|
|
SendMessage(ChannelName, String),
|
|
}
|
|
|
|
// pub enum RequestParserError {
|
|
// SyntaxError(u64, String),
|
|
// MissingCommandListEnd(u64),
|
|
// NestedCommandList(u64),
|
|
// UnexpectedCommandListEnd(u64),
|
|
// UnexpectedEOF,
|
|
// MissingNewline,
|
|
// }
|
|
|
|
// TODO: upon encountering an error, there should be a function that lets you skip to the next OK,
|
|
// and continue execution. Maybe "parse_next_or_skip(&str) -> RequestParserResponse", which
|
|
// could skip stuff internally? Or do we want to report back the error with the entire command
|
|
// and let the library user decide what to do?
|
|
|
|
impl Request {
|
|
pub fn parse_next(raw: &str) -> RequestParserResult<'_> {
|
|
let (line, rest) = raw
|
|
.split_once('\n')
|
|
.ok_or(RequestParserError::UnexpectedEOF)?;
|
|
let mut parts = line.split_whitespace();
|
|
|
|
match parts
|
|
.next()
|
|
.ok_or(RequestParserError::SyntaxError(0, line.to_string()))?
|
|
.trim()
|
|
{
|
|
"command_list_begin" => {
|
|
let mut commands = Vec::new();
|
|
let mut i = 1;
|
|
loop {
|
|
i += 1;
|
|
let (line, rest) = rest
|
|
.split_once('\n')
|
|
.ok_or(RequestParserError::MissingCommandListEnd(i))?;
|
|
match line.trim() {
|
|
"command_list_begin" => {
|
|
return Err(RequestParserError::NestedCommandList(i))
|
|
}
|
|
"command_list_end" => {
|
|
return Ok((Request::CommandList(commands), rest));
|
|
}
|
|
input => {
|
|
let (command, _) = Request::parse_next(input)?;
|
|
commands.push(command);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"command_list_end" => Err(RequestParserError::UnexpectedCommandListEnd(0)),
|
|
|
|
/* querying mpd status */
|
|
ClearError::COMMAND => ClearError::parse_request(parts),
|
|
CurrentSong::COMMAND => CurrentSong::parse_request(parts),
|
|
Idle::COMMAND => Idle::parse_request(parts),
|
|
Status::COMMAND => Status::parse_request(parts),
|
|
Stats::COMMAND => Stats::parse_request(parts),
|
|
|
|
/* playback options */
|
|
Consume::COMMAND => Consume::parse_request(parts),
|
|
Crossfade::COMMAND => Crossfade::parse_request(parts),
|
|
MixRampDb::COMMAND => MixRampDb::parse_request(parts),
|
|
MixRampDelay::COMMAND => MixRampDelay::parse_request(parts),
|
|
Random::COMMAND => Random::parse_request(parts),
|
|
Repeat::COMMAND => Repeat::parse_request(parts),
|
|
SetVol::COMMAND => SetVol::parse_request(parts),
|
|
GetVol::COMMAND => GetVol::parse_request(parts),
|
|
Single::COMMAND => Single::parse_request(parts),
|
|
ReplayGainMode::COMMAND => ReplayGainMode::parse_request(parts),
|
|
ReplayGainStatus::COMMAND => ReplayGainStatus::parse_request(parts),
|
|
Volume::COMMAND => Volume::parse_request(parts),
|
|
|
|
/* playback control */
|
|
Next::COMMAND => Next::parse_request(parts),
|
|
Pause::COMMAND => Pause::parse_request(parts),
|
|
Play::COMMAND => Play::parse_request(parts),
|
|
PlayId::COMMAND => PlayId::parse_request(parts),
|
|
Previous::COMMAND => Previous::parse_request(parts),
|
|
Seek::COMMAND => Seek::parse_request(parts),
|
|
SeekId::COMMAND => SeekId::parse_request(parts),
|
|
SeekCur::COMMAND => SeekCur::parse_request(parts),
|
|
Stop::COMMAND => Stop::parse_request(parts),
|
|
|
|
/* queue */
|
|
Add::COMMAND => Add::parse_request(parts),
|
|
AddId::COMMAND => AddId::parse_request(parts),
|
|
Clear::COMMAND => Clear::parse_request(parts),
|
|
Delete::COMMAND => Delete::parse_request(parts),
|
|
DeleteId::COMMAND => DeleteId::parse_request(parts),
|
|
Move::COMMAND => Move::parse_request(parts),
|
|
MoveId::COMMAND => MoveId::parse_request(parts),
|
|
Playlist::COMMAND => Playlist::parse_request(parts),
|
|
PlaylistFind::COMMAND => PlaylistFind::parse_request(parts),
|
|
PlaylistId::COMMAND => PlaylistId::parse_request(parts),
|
|
PlaylistInfo::COMMAND => PlaylistInfo::parse_request(parts),
|
|
PlaylistSearch::COMMAND => PlaylistSearch::parse_request(parts),
|
|
PlChanges::COMMAND => PlChanges::parse_request(parts),
|
|
PlChangesPosId::COMMAND => PlChangesPosId::parse_request(parts),
|
|
Prio::COMMAND => Prio::parse_request(parts),
|
|
PrioId::COMMAND => PrioId::parse_request(parts),
|
|
RangeId::COMMAND => RangeId::parse_request(parts),
|
|
Shuffle::COMMAND => Shuffle::parse_request(parts),
|
|
Swap::COMMAND => Swap::parse_request(parts),
|
|
SwapId::COMMAND => SwapId::parse_request(parts),
|
|
AddTagId::COMMAND => AddTagId::parse_request(parts),
|
|
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 */
|
|
AlbumArt::COMMAND => AlbumArt::parse_request(parts),
|
|
Count::COMMAND => Count::parse_request(parts),
|
|
GetFingerprint::COMMAND => GetFingerprint::parse_request(parts),
|
|
Find::COMMAND => Find::parse_request(parts),
|
|
FindAdd::COMMAND => FindAdd::parse_request(parts),
|
|
List::COMMAND => List::parse_request(parts),
|
|
ListAll::COMMAND => ListAll::parse_request(parts),
|
|
ListAllInfo::COMMAND => ListAllInfo::parse_request(parts),
|
|
ListFiles::COMMAND => ListFiles::parse_request(parts),
|
|
LsInfo::COMMAND => LsInfo::parse_request(parts),
|
|
ReadComments::COMMAND => ReadComments::parse_request(parts),
|
|
ReadPicture::COMMAND => ReadPicture::parse_request(parts),
|
|
Search::COMMAND => Search::parse_request(parts),
|
|
SearchAdd::COMMAND => SearchAdd::parse_request(parts),
|
|
SearchAddPl::COMMAND => SearchAddPl::parse_request(parts),
|
|
SearchCount::COMMAND => SearchCount::parse_request(parts),
|
|
Update::COMMAND => Update::parse_request(parts),
|
|
Rescan::COMMAND => Rescan::parse_request(parts),
|
|
|
|
/* mounts and neighbors */
|
|
Mount::COMMAND => Mount::parse_request(parts),
|
|
Unmount::COMMAND => Unmount::parse_request(parts),
|
|
ListMounts::COMMAND => ListMounts::parse_request(parts),
|
|
ListNeighbors::COMMAND => ListNeighbors::parse_request(parts),
|
|
|
|
/* 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 */
|
|
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),
|
|
EnableOutput::COMMAND => EnableOutput::parse_request(parts),
|
|
ToggleOutput::COMMAND => ToggleOutput::parse_request(parts),
|
|
Outputs::COMMAND => Outputs::parse_request(parts),
|
|
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),
|
|
Unsubscribe::COMMAND => Unsubscribe::parse_request(parts),
|
|
Channels::COMMAND => Channels::parse_request(parts),
|
|
ReadMessages::COMMAND => ReadMessages::parse_request(parts),
|
|
SendMessage::COMMAND => SendMessage::parse_request(parts),
|
|
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
}
|