1 Commits

Author SHA1 Message Date
e469a8eb3c WIP 2025-05-07 10:54:28 +02:00
186 changed files with 4063 additions and 9950 deletions

View File

@@ -7,20 +7,20 @@ on:
jobs: jobs:
build: build:
runs-on: debian-latest-slim runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install rust toolchain - name: Install rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Build - name: Build
run: cargo build --verbose --release run: cargo build --all-features --verbose --release
check: check:
runs-on: debian-latest-slim runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install rust toolchain - name: Install rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
@@ -31,13 +31,12 @@ jobs:
run: cargo fmt -- --check run: cargo fmt -- --check
- name: Check clippy - name: Check clippy
# run: cargo clippy -- --deny warnings run: cargo clippy --all-features -- --deny warnings
run: cargo clippy
test: test:
runs-on: debian-latest-slim runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: cargo-bins/cargo-binstall@main - uses: cargo-bins/cargo-binstall@main
- name: Install rust toolchain - name: Install rust toolchain
@@ -50,7 +49,7 @@ jobs:
- name: Run tests - name: Run tests
run: | run: |
cargo nextest run --release --no-fail-fast cargo nextest run --all-features --release --no-fail-fast
env: env:
RUST_LOG: "trace" RUST_LOG: "trace"
RUSTFLAGS: "-Cinstrument-coverage" RUSTFLAGS: "-Cinstrument-coverage"
@@ -84,13 +83,13 @@ jobs:
target: ${{ gitea.ref_name }}/coverage/ target: ${{ gitea.ref_name }}/coverage/
username: gitea-web username: gitea-web
ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }} ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }}
host: pages.pvv.ntnu.no host: bekkalokk.pvv.ntnu.no
known-hosts: "pages.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2QjfFB+city1SYqltkVqWACfo1j37k+oQQfj13mtgg" known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"
docs: docs:
runs-on: debian-latest-slim runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v3
- name: Install latest nightly toolchain - name: Install latest nightly toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -99,7 +98,7 @@ jobs:
override: true override: true
- name: Build docs - name: Build docs
run: cargo doc --document-private-items --release run: cargo doc --all-features --document-private-items --release
- name: Transfer files - name: Transfer files
uses: https://git.pvv.ntnu.no/Projects/rsync-action@v1 uses: https://git.pvv.ntnu.no/Projects/rsync-action@v1
@@ -108,5 +107,6 @@ jobs:
target: ${{ gitea.ref_name }}/docs/ target: ${{ gitea.ref_name }}/docs/
username: gitea-web username: gitea-web
ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }} ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }}
host: pages.pvv.ntnu.no host: bekkalokk.pvv.ntnu.no
known-hosts: "pages.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2QjfFB+city1SYqltkVqWACfo1j37k+oQQfj13mtgg" known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"

2
.gitignore vendored
View File

@@ -1,5 +1,3 @@
/target /target
result result
result-* result-*
Cargo.lock

95
Cargo.lock generated Normal file
View File

@@ -0,0 +1,95 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "empidee"
version = "0.1.0"
dependencies = [
"indoc",
"pretty_assertions",
"serde",
]
[[package]]
name = "indoc"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"

View File

@@ -11,24 +11,8 @@ edition = "2024"
rust-version = "1.85.0" rust-version = "1.85.0"
[dependencies] [dependencies]
chrono = { version = "0.4.42", features = ["serde"] } serde = { version = "1.0.210", features = ["derive"] }
futures-util = { version = "0.3.31", optional = true, features = ["io"] }
lalrpop-util = { version = "0.22.2", features = ["lexer"] }
paste = "1.0.15"
serde = { version = "1.0.228", features = ["derive"] }
thiserror = "2.0.17"
tokio = { version = "1.48.0", optional = true, features = ["io-util"] }
[features]
default = ["tokio"]
futures = ["dep:futures-util"]
tokio = ["dep:tokio"]
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.100" indoc = "2.0.5"
indoc = "2.0.7"
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
tokio = { version = "1.48.0", features = ["macros", "net", "rt"] }
[build-dependencies]
lalrpop = "0.22.2"

View File

@@ -1,8 +0,0 @@
fn main() {
lalrpop::process_root().unwrap();
// let debug_mode = std::env::var("PROFILE").unwrap() == "debug";
// lalrpop::Configuration::new()
// .emit_comments(debug_mode)
// .process()
// .unwrap();
}

View File

@@ -1,14 +1,3 @@
use empidee::MpdClient; fn main() {
todo!()
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
let socket = tokio::net::TcpSocket::new_v4()?;
let mut stream = socket.connect("127.0.0.1:6600".parse()?).await?;
let mut client = MpdClient::new(&mut stream);
println!("{}", client.read_initial_mpd_version().await?);
client.play(None).await?;
Ok(())
} }

12
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1764950072, "lastModified": 1746461020,
"narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=", "narHash": "sha256-7+pG1I9jvxNlmln4YgnlW4o+w0TZX24k688mibiFDUE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f61125a668a320878494449750330ca58b78c557", "rev": "3730d8a308f94996a9ba7c7138ede69c1b9ac4ae",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -29,11 +29,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1765075567, "lastModified": 1746585402,
"narHash": "sha256-KFDCdQcHJ0hE3Nt5Gm5enRIhmtEifAjpxgUQ3mzSJpA=", "narHash": "sha256-Pf+ufu6bYNA1+KQKHnGMNEfTwpD9ZIcAeLoE2yPWIP0=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "769156779b41e8787a46ca3d7d76443aaf68be6f", "rev": "72dd969389583664f87aa348b3458f2813693617",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -36,7 +36,6 @@
default = pkgs.mkShell { default = pkgs.mkShell {
nativeBuildInputs = [ nativeBuildInputs = [
toolchain toolchain
pkgs.cargo-edit
]; ];
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";

View File

@@ -1,105 +0,0 @@
//! A high-level client for interacting with an Mpd server.
//!
//! The client provides methods for common operations such as playing, pausing, and
//! managing the playlist, and returns the expected response types directly
//! from its methods.
use crate::{Request, commands::*, types::SongPosition};
#[cfg(feature = "futures")]
use futures_util::{
AsyncBufReadExt,
io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader},
};
use thiserror::Error;
#[cfg(feature = "tokio")]
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
pub struct MpdClient<'a, T>
where
T: AsyncWrite + AsyncRead + Unpin,
{
connection: &'a mut T,
}
#[derive(Error, Debug)]
pub enum MpdClientError {
#[error("Connection error: {0}")]
ConnectionError(#[from] std::io::Error),
#[error("Failed to parse MPD response: {0}")]
ResponseParseError(#[from] crate::commands::ResponseParserError),
#[error("MPD returned an error: {0}")]
MpdError(#[from] crate::response::MpdError),
}
impl<'a, T> MpdClient<'a, T>
where
T: AsyncWrite + AsyncRead + Unpin,
{
pub fn new(connection: &'a mut T) -> Self {
MpdClient { connection }
}
pub async fn read_initial_mpd_version(&mut self) -> Result<String, MpdClientError> {
let mut reader = BufReader::new(&mut self.connection);
let mut version_line = String::new();
reader
.read_line(&mut version_line)
.await
.map_err(MpdClientError::ConnectionError)?;
Ok(version_line.trim().to_string())
}
async fn read_response(&mut self) -> Result<Vec<u8>, MpdClientError> {
let mut response = Vec::new();
let mut reader = BufReader::new(&mut self.connection);
loop {
let mut line = Vec::new();
let bytes_read = reader
.read_until(b'\n', &mut line)
.await
.map_err(MpdClientError::ConnectionError)?;
if bytes_read == 0 {
break; // EOF reached
}
response.extend_from_slice(&line);
if line == b"OK\n" || line.starts_with(b"ACK ") {
break; // End of response
}
}
Ok(response)
}
pub async fn play(
&mut self,
position: Option<SongPosition>,
) -> Result<PlayResponse, MpdClientError> {
let message = Request::Play(position);
let payload = message.serialize();
self.connection
.write_all(payload.as_bytes())
.await
.map_err(MpdClientError::ConnectionError)?;
self.connection
.flush()
.await
.map_err(MpdClientError::ConnectionError)?;
let response_bytes = self.read_response().await?;
let response = PlayResponse::parse_raw(&response_bytes)?;
Ok(response)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
mod disableoutput; pub mod disableoutput;
mod enableoutput; pub mod enableoutput;
mod outputs; pub mod outputs;
mod outputset; pub mod outputset;
mod toggleoutput; pub mod toggleoutput;
pub use disableoutput::*; pub use disableoutput::DisableOutput;
pub use enableoutput::*; pub use enableoutput::EnableOutput;
pub use outputs::*; pub use outputs::Outputs;
pub use outputset::*; pub use outputset::OutputSet;
pub use toggleoutput::*; pub use toggleoutput::ToggleOutput;

View File

@@ -1,15 +1,26 @@
use crate::{ use crate::commands::{
commands::{Command, empty_command_response, single_item_command_request}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::AudioOutputId, ResponseParserError,
}; };
pub struct DisableOutput; pub struct DisableOutput;
single_item_command_request!(DisableOutput, "disableoutput", AudioOutputId);
empty_command_response!(DisableOutput);
impl Command for DisableOutput { impl Command for DisableOutput {
type Request = DisableOutputRequest; type Response = ();
type Response = DisableOutputResponse; const COMMAND: &'static str = "disableoutput";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::DisableOutput(output_id.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,26 @@
use crate::{ use crate::commands::{
commands::{Command, empty_command_response, single_item_command_request}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::AudioOutputId, ResponseParserError,
}; };
pub struct EnableOutput; pub struct EnableOutput;
single_item_command_request!(EnableOutput, "enableoutput", AudioOutputId);
empty_command_response!(EnableOutput);
impl Command for EnableOutput { impl Command for EnableOutput {
type Request = EnableOutputRequest; type Response = ();
type Response = EnableOutputResponse; const COMMAND: &'static str = "enableoutput";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::EnableOutput(output_id.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -2,171 +2,35 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::commands::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
response_tokenizer::{ResponseAttributes, expect_property_type},
types::AudioOutputId,
}; };
pub struct Outputs; pub struct Outputs;
empty_command_request!(Outputs, "outputs");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OutputsResponse(Vec<Output>);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Output { pub struct Output {
pub id: AudioOutputId, pub id: u64,
pub name: String, pub name: String,
pub plugin: String, pub plugin: String,
pub enabled: bool, pub enabled: bool,
pub attribute: HashMap<String, String>, pub attribute: HashMap<String, String>,
} }
impl CommandResponse for OutputsResponse { pub type OutputsResponse = Vec<Output>;
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts = parts.into_vec()?;
let result_len = parts.iter().filter(|(k, _)| *k == "outputid").count();
let mut outputs = Vec::with_capacity(result_len);
let mut id: Option<AudioOutputId> = None;
let mut name: Option<String> = None;
let mut plugin: Option<String> = None;
let mut enabled: Option<bool> = None;
let mut attributes: HashMap<String, String> = HashMap::new();
for (k, v) in parts.into_iter() {
match k {
"outputid" => {
// Reset and store the previous output if all fields are present
if let (Some(id), Some(name), Some(plugin), Some(enabled)) =
(id.take(), name.take(), plugin.take(), enabled.take())
{
outputs.push(Output {
id,
name,
plugin,
enabled,
attribute: attributes,
});
}
attributes = HashMap::new();
let id_s = expect_property_type!(Some(v), k, Text);
id = Some(
id_s.parse()
.map_err(|_| ResponseParserError::SyntaxError(0, id_s.to_string()))?,
);
}
"outputname" => {
name = Some(expect_property_type!(Some(v), k, Text).to_string());
}
"plugin" => {
plugin = Some(expect_property_type!(Some(v), k, Text).to_string());
}
"outputenabled" => {
let val_s = expect_property_type!(Some(v), k, Text);
let val: u64 = val_s
.parse::<u64>()
.map_err(|_| ResponseParserError::SyntaxError(0, val_s.to_string()))?;
enabled = Some(val != 0);
}
"attribute" => {
let value = expect_property_type!(Some(v), k, Text);
let mut parts = value.splitn(2, '=');
let attr_key = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, value.to_string()))?
.to_string();
let attr_value = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, value.to_string()))?
.to_string();
attributes.insert(attr_key, attr_value);
}
_ => {
return Err(ResponseParserError::UnexpectedProperty(k.to_string()));
}
}
}
// Store the last output if all fields are present
if let (Some(id), Some(name), Some(plugin), Some(enabled)) =
(id.take(), name.take(), plugin.take(), enabled.take())
{
outputs.push(Output {
id,
name,
plugin,
enabled,
attribute: attributes,
});
}
Ok(OutputsResponse(outputs))
}
}
impl Command for Outputs { impl Command for Outputs {
type Request = OutputsRequest;
type Response = OutputsResponse; type Response = OutputsResponse;
} const COMMAND: &'static str = "outputs";
#[cfg(test)] fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
mod tests { debug_assert!(parts.next().is_none());
use indoc::indoc; Ok((Request::Outputs, ""))
}
use super::*; fn parse_response(
_parts: ResponseAttributes<'_>,
#[test] ) -> Result<Self::Response, ResponseParserError> {
fn test_parse_response() { unimplemented!()
let input = indoc! {"
outputid: 0
outputname: PipeWire Sound Server
plugin: pipewire
outputenabled: 1
outputid: 1
outputname: Visualizer feed
plugin: fifo
outputenabled: 1
attribute: fifo_path=/tmp/empidee-visualizer.fifo
OK
"};
let result = Outputs::parse_raw_response(input.as_bytes());
assert_eq!(
result,
Ok(OutputsResponse(vec![
Output {
id: 0,
name: "PipeWire Sound Server".to_string(),
plugin: "pipewire".to_string(),
enabled: true,
attribute: HashMap::new(),
},
Output {
id: 1,
name: "Visualizer feed".to_string(),
plugin: "fifo".to_string(),
enabled: true,
attribute: {
let mut map = HashMap::new();
map.insert(
"fifo_path".to_string(),
"/tmp/empidee-visualizer.fifo".to_string(),
);
map
},
},
])),
);
} }
} }

View File

@@ -1,87 +1,35 @@
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
use crate::{ ResponseParserError,
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::AudioOutputId,
}; };
pub struct OutputSet; pub struct OutputSet;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OutputSetRequest {
pub output_id: AudioOutputId,
pub attribute_name: String,
pub attribute_value: String,
}
impl OutputSetRequest {
fn new(output_id: AudioOutputId, attribute_name: String, attribute_value: String) -> Self {
Self {
output_id,
attribute_name,
attribute_value,
}
}
}
impl CommandRequest for OutputSetRequest {
const COMMAND: &'static str = "outputset";
const MIN_ARGS: u32 = 3;
const MAX_ARGS: Option<u32> = Some(3);
fn into_request_enum(self) -> crate::Request {
crate::Request::OutputSet(self.output_id, self.attribute_name, self.attribute_value)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::OutputSet(output_id, attribute_name, attribute_value) => {
Some(OutputSetRequest {
output_id,
attribute_name,
attribute_value,
})
}
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {} {} {}",
Self::COMMAND,
self.output_id,
self.attribute_name,
self.attribute_value
)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let output_id = parts.next().ok_or(Self::missing_arguments_error(0))?;
let output_id = output_id
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "AudioOutputId",
raw_input: output_id.to_string(),
})?;
let attribute_name = parts.next().ok_or(Self::missing_arguments_error(1))?;
let attribute_value = parts.next().ok_or(Self::missing_arguments_error(2))?;
Self::throw_if_too_many_arguments(parts)?;
Ok(OutputSetRequest {
output_id,
attribute_name: attribute_name.to_string(),
attribute_value: attribute_value.to_string(),
})
}
}
empty_command_response!(OutputSet);
impl Command for OutputSet { impl Command for OutputSet {
type Request = OutputSetRequest; type Response = ();
type Response = OutputSetResponse; const COMMAND: &'static str = "outputset";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let attribute_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let attribute_value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((
Request::OutputSet(
output_id.to_string(),
attribute_name.to_string(),
attribute_value.to_string(),
),
"",
))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,26 @@
use crate::{ use crate::commands::{
commands::{Command, empty_command_response, single_item_command_request}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::AudioOutputId, ResponseParserError,
}; };
pub struct ToggleOutput; pub struct ToggleOutput;
single_item_command_request!(ToggleOutput, "toggleoutput", AudioOutputId);
empty_command_response!(ToggleOutput);
impl Command for ToggleOutput { impl Command for ToggleOutput {
type Request = ToggleOutputRequest; type Response = ();
type Response = ToggleOutputResponse; const COMMAND: &'static str = "toggleoutput";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::ToggleOutput(output_id.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,11 +1,11 @@
mod channels; pub mod channels;
mod readmessages; pub mod readmessages;
mod sendmessage; pub mod sendmessage;
mod subscribe; pub mod subscribe;
mod unsubscribe; pub mod unsubscribe;
pub use channels::*; pub use channels::Channels;
pub use readmessages::*; pub use readmessages::ReadMessages;
pub use sendmessage::*; pub use sendmessage::SendMessage;
pub use subscribe::*; pub use subscribe::Subscribe;
pub use unsubscribe::*; pub use unsubscribe::Unsubscribe;

View File

@@ -1,39 +1,36 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::commands::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
response_tokenizer::{ResponseAttributes, expect_property_type}, expect_property_type,
types::ChannelName,
}; };
pub struct Channels; pub struct Channels;
empty_command_request!(Channels, "channels");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChannelsResponse { pub struct ChannelsResponse {
pub channels: Vec<ChannelName>, pub channels: Vec<String>,
} }
impl CommandResponse for ChannelsResponse { impl Command for Channels {
fn into_response_enum(self) -> crate::Response { type Response = ChannelsResponse;
todo!() const COMMAND: &'static str = "channels";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Channels, ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
let mut channel_names = Vec::with_capacity(parts.len()); let mut channel_names = Vec::with_capacity(parts.len());
for (key, value) in parts { for (key, value) in parts {
debug_assert!(key == "channels"); debug_assert!(key == "channels");
let channel_name = expect_property_type!(Some(value), "channels", Text); let channel_name = expect_property_type!(Some(value), "channels", Text);
let channel_name = channel_name channel_names.push(channel_name.to_string());
.parse()
.map_err(|_| ResponseParserError::SyntaxError(0, channel_name.to_string()))?;
channel_names.push(channel_name);
} }
Ok(ChannelsResponse { Ok(ChannelsResponse {
@@ -42,11 +39,6 @@ impl CommandResponse for ChannelsResponse {
} }
} }
impl Command for Channels {
type Request = ChannelsRequest;
type Response = ChannelsResponse;
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -61,15 +53,11 @@ mod tests {
channels: baz channels: baz
OK OK
"}; "};
let response = Channels::parse_raw_response(response.as_bytes()).unwrap(); let response = Channels::parse_raw_response(response).unwrap();
assert_eq!( assert_eq!(
response, response,
ChannelsResponse { ChannelsResponse {
channels: vec![ channels: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
"foo".parse().unwrap(),
"bar".parse().unwrap(),
"baz".parse().unwrap(),
]
} }
); );
} }

View File

@@ -1,35 +1,31 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::commands::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
response_tokenizer::{ResponseAttributes, expect_property_type}, expect_property_type,
types::ChannelName,
}; };
pub struct ReadMessages; pub struct ReadMessages;
empty_command_request!(ReadMessages, "readmessages");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadMessagesResponse(Vec<ReadMessagesResponseEntry>); pub struct ReadMessagesResponse {
pub messages: Vec<(String, String)>,
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadMessagesResponseEntry {
channel: ChannelName,
message: String,
} }
impl CommandResponse for ReadMessagesResponse { impl Command for ReadMessages {
fn into_response_enum(self) -> crate::Response { type Response = ReadMessagesResponse;
todo!() const COMMAND: &'static str = "readmessages";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ReadMessages, ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
debug_assert!(parts.len() % 2 == 0); debug_assert!(parts.len() % 2 == 0);
let mut messages = Vec::with_capacity(parts.len() / 2); let mut messages = Vec::with_capacity(parts.len() / 2);
@@ -38,28 +34,23 @@ impl CommandResponse for ReadMessagesResponse {
let (ckey, cvalue) = channel_message_pair[0]; let (ckey, cvalue) = channel_message_pair[0];
let (mkey, mvalue) = channel_message_pair[1]; let (mkey, mvalue) = channel_message_pair[1];
debug_assert!(ckey == "channel"); if ckey != "channel" {
debug_assert!(mkey == "message"); return Err(ResponseParserError::UnexpectedProperty(ckey));
}
let channel = expect_property_type!(Some(cvalue), "channel", Text); if mkey != "message" {
let channel = channel return Err(ResponseParserError::UnexpectedProperty(mkey));
.parse() }
.map_err(|_| ResponseParserError::SyntaxError(0, channel.to_string()))?;
let channel = expect_property_type!(Some(cvalue), "channel", Text).to_string();
let message = expect_property_type!(Some(mvalue), "message", Text).to_string(); let message = expect_property_type!(Some(mvalue), "message", Text).to_string();
messages.push(ReadMessagesResponseEntry { channel, message }); messages.push((channel, message));
} }
Ok(ReadMessagesResponse(messages)) Ok(ReadMessagesResponse { messages })
} }
} }
impl Command for ReadMessages {
type Request = ReadMessagesRequest;
type Response = ReadMessagesResponse;
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use indoc::indoc; use indoc::indoc;
@@ -75,19 +66,15 @@ mod tests {
message: message2 message: message2
OK OK
"}; "};
let result = ReadMessages::parse_raw_response(input.as_bytes()); let result = ReadMessages::parse_raw_response(input);
assert_eq!( assert_eq!(
result, result,
Ok(ReadMessagesResponse(vec![ Ok(ReadMessagesResponse {
ReadMessagesResponseEntry { messages: vec![
channel: "channel1".parse().unwrap(), ("channel1".to_string(), "message1".to_string()),
message: "message1".to_string(), ("channel2".to_string(), "message2".to_string()),
}, ]
ReadMessagesResponseEntry { })
channel: "channel2".parse().unwrap(),
message: "message2".to_string(),
},
]))
); );
} }
} }

View File

@@ -1,67 +1,29 @@
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
use crate::{ ResponseParserError,
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::ChannelName,
}; };
pub struct SendMessage; pub struct SendMessage;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for SendMessage {
pub struct SendMessageRequest { type Response = ();
pub channel: ChannelName,
pub message: String,
}
impl SendMessageRequest {
fn new(channel: ChannelName, message: String) -> Self {
Self { channel, message }
}
}
impl CommandRequest for SendMessageRequest {
const COMMAND: &'static str = "sendmessage"; const COMMAND: &'static str = "sendmessage";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::SendMessage(self.channel, self.message) let channel = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SendMessage(channel, message) => {
Some(SendMessageRequest { channel, message })
}
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}", Self::COMMAND, self.channel, self.message)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let channel = parts.next().ok_or(Self::missing_arguments_error(0))?;
let channel = channel
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "ChannelName",
raw_input: channel.to_string(),
})?;
// TODO: SplitWhitespace::remainder() is unstable, use when stable // TODO: SplitWhitespace::remainder() is unstable, use when stable
let message = parts.collect::<Vec<_>>().join(" "); let message = parts.collect::<Vec<_>>().join(" ");
Ok(SendMessageRequest { channel, message }) debug_assert!(!message.is_empty());
Ok((Request::SendMessage(channel.to_string(), message), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(SendMessage);
impl Command for SendMessage {
type Request = SendMessageRequest;
type Response = SendMessageResponse;
}

View File

@@ -1,15 +1,26 @@
use crate::{ use crate::commands::{
commands::{Command, empty_command_response, single_item_command_request}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::ChannelName, ResponseParserError,
}; };
pub struct Subscribe; pub struct Subscribe;
single_item_command_request!(Subscribe, "subscribe", ChannelName);
empty_command_response!(Subscribe);
impl Command for Subscribe { impl Command for Subscribe {
type Request = SubscribeRequest; type Response = ();
type Response = SubscribeResponse; const COMMAND: &'static str = "subscribe";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let channel_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::Subscribe(channel_name.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,26 @@
use crate::{ use crate::commands::{
commands::{Command, empty_command_response, single_item_command_request}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::ChannelName, ResponseParserError,
}; };
pub struct Unsubscribe; pub struct Unsubscribe;
single_item_command_request!(Unsubscribe, "unsubscribe", ChannelName);
empty_command_response!(Unsubscribe);
impl Command for Unsubscribe { impl Command for Unsubscribe {
type Request = UnsubscribeRequest; type Response = ();
type Response = UnsubscribeResponse; const COMMAND: &'static str = "unsubscribe";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let channel_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::Unsubscribe(channel_name.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,37 +1,37 @@
mod binary_limit; pub mod binary_limit;
mod close; pub mod close;
mod kill; pub mod kill;
mod password; pub mod password;
mod ping; pub mod ping;
mod protocol; pub mod protocol;
mod protocol_all; pub mod protocol_all;
mod protocol_available; pub mod protocol_available;
mod protocol_clear; pub mod protocol_clear;
mod protocol_disable; pub mod protocol_disable;
mod protocol_enable; pub mod protocol_enable;
mod tag_types; pub mod tag_types;
mod tag_types_all; pub mod tag_types_all;
mod tag_types_available; pub mod tag_types_available;
mod tag_types_clear; pub mod tag_types_clear;
mod tag_types_disable; pub mod tag_types_disable;
mod tag_types_enable; pub mod tag_types_enable;
mod tag_types_reset; pub mod tag_types_reset;
pub use binary_limit::*; pub use binary_limit::BinaryLimit;
pub use close::*; pub use close::Close;
pub use kill::*; pub use kill::Kill;
pub use password::*; pub use password::Password;
pub use ping::*; pub use ping::Ping;
pub use protocol::*; pub use protocol::Protocol;
pub use protocol_all::*; pub use protocol_all::ProtocolAll;
pub use protocol_available::*; pub use protocol_available::ProtocolAvailable;
pub use protocol_clear::*; pub use protocol_clear::ProtocolClear;
pub use protocol_disable::*; pub use protocol_disable::ProtocolDisable;
pub use protocol_enable::*; pub use protocol_enable::ProtocolEnable;
pub use tag_types::*; pub use tag_types::TagTypes;
pub use tag_types_all::*; pub use tag_types_all::TagTypesAll;
pub use tag_types_available::*; pub use tag_types_available::TagTypesAvailable;
pub use tag_types_clear::*; pub use tag_types_clear::TagTypesClear;
pub use tag_types_disable::*; pub use tag_types_disable::TagTypesDisable;
pub use tag_types_enable::*; pub use tag_types_enable::TagTypesEnable;
pub use tag_types_reset::*; pub use tag_types_reset::TagTypesReset;

View File

@@ -1,12 +1,27 @@
use crate::commands::{Command, empty_command_response, single_item_command_request}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct BinaryLimit; pub struct BinaryLimit;
single_item_command_request!(BinaryLimit, "binarylimit", u64);
empty_command_response!(BinaryLimit);
impl Command for BinaryLimit { impl Command for BinaryLimit {
type Request = BinaryLimitRequest; type Response = ();
type Response = BinaryLimitResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Close; pub struct Close;
empty_command_request!(Close, "close");
empty_command_response!(Close);
impl Command for Close { impl Command for Close {
type Request = CloseRequest; type Response = ();
type Response = CloseResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Kill; pub struct Kill;
empty_command_request!(Kill, "kill");
empty_command_response!(Kill);
impl Command for Kill { impl Command for Kill {
type Request = KillRequest; type Response = ();
type Response = KillResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,12 +1,27 @@
use crate::commands::{Command, empty_command_response, single_item_command_request}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Password; pub struct Password;
single_item_command_request!(Password, "password", String);
empty_command_response!(Password);
impl Command for Password { impl Command for Password {
type Request = PasswordRequest; type Response = ();
type Response = PasswordResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Ping; pub struct Ping;
empty_command_request!(Ping, "ping");
empty_command_response!(Ping);
impl Command for Ping { impl Command for Ping {
type Request = PingRequest; type Response = ();
type Response = PingResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,22 @@
use crate::{ use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response}, Request,
response_tokenizer::expect_property_type, commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
}; };
pub struct Protocol; pub struct Protocol;
empty_command_request!(Protocol, "protocol");
multi_item_command_response!(Protocol, "feature", String);
impl Command for Protocol { impl Command for Protocol {
type Request = ProtocolRequest; type Response = ();
type Response = ProtocolResponse; 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<Self::Response, ResponseParserError> {
unimplemented!()
}
} }

View File

@@ -1,12 +1,23 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::{
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct ProtocolAll; pub struct ProtocolAll;
empty_command_request!(ProtocolAll, "protocol all");
empty_command_response!(ProtocolAll);
impl Command for ProtocolAll { impl Command for ProtocolAll {
type Request = ProtocolAllRequest; type Response = ();
type Response = ProtocolAllResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,22 @@
use crate::{ use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response}, Request,
response_tokenizer::expect_property_type, commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
}; };
pub struct ProtocolAvailable; pub struct ProtocolAvailable;
empty_command_request!(ProtocolAvailable, "protocol available");
multi_item_command_response!(ProtocolAvailable, "feature", String);
impl Command for ProtocolAvailable { impl Command for ProtocolAvailable {
type Request = ProtocolAvailableRequest; type Response = ();
type Response = ProtocolAvailableResponse; 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<Self::Response, ResponseParserError> {
unimplemented!()
}
} }

View File

@@ -1,12 +1,23 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::{
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct ProtocolClear; pub struct ProtocolClear;
empty_command_request!(ProtocolClear, "protocol clear");
empty_command_response!(ProtocolClear);
impl Command for ProtocolClear { impl Command for ProtocolClear {
type Request = ProtocolClearRequest; type Response = ();
type Response = ProtocolClearResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,70 +1,35 @@
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Request,
request_tokenizer::RequestTokenizer, commands::{
types::Feature, Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
}; };
pub struct ProtocolDisable; pub struct ProtocolDisable;
pub struct ProtocolDisableRequest(Vec<Feature>); impl Command for ProtocolDisable {
type Response = ();
impl ProtocolDisableRequest {
fn new(features: Vec<Feature>) -> Self {
Self(features)
}
}
impl CommandRequest for ProtocolDisableRequest {
const COMMAND: &'static str = "protocol disable"; const COMMAND: &'static str = "protocol disable";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request { fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::ProtocolDisable(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::ProtocolDisable(features) => Some(ProtocolDisableRequest(features)),
_ => None,
}
}
fn serialize(&self) -> String {
let features = self
.0
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>()
.join(" ");
format!("{} {}", Self::COMMAND, features)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable(); let mut parts = parts.peekable();
if parts.peek().is_none() { if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0)); return Err(RequestParserError::UnexpectedEOF);
} }
let features = parts // TODO: verify that the features are split by whitespace
.enumerate() let mut features = Vec::new();
.map(|(i, f)| { for feature in parts {
f.parse() features.push(feature.to_string());
.map_err(|_| RequestParserError::SubtypeParserError { }
argument_index: i.try_into().unwrap_or(u32::MAX),
expected_type: "Feature",
raw_input: f.to_owned(),
})
})
.collect::<Result<Vec<Feature>, RequestParserError>>()?;
Ok(ProtocolDisableRequest(features)) Ok((Request::ProtocolDisable(features), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(ProtocolDisable);
impl Command for ProtocolDisable {
type Request = ProtocolDisableRequest;
type Response = ProtocolDisableResponse;
}

View File

@@ -1,70 +1,35 @@
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Request,
request_tokenizer::RequestTokenizer, commands::{
types::Feature, Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
}; };
pub struct ProtocolEnable; pub struct ProtocolEnable;
pub struct ProtocolEnableRequest(Vec<Feature>); impl Command for ProtocolEnable {
type Response = ();
impl ProtocolEnableRequest {
fn new(features: Vec<Feature>) -> Self {
Self(features)
}
}
impl CommandRequest for ProtocolEnableRequest {
const COMMAND: &'static str = "protocol enable"; const COMMAND: &'static str = "protocol enable";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request { fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::ProtocolEnable(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::ProtocolEnable(features) => Some(ProtocolEnableRequest(features)),
_ => None,
}
}
fn serialize(&self) -> String {
let features = self
.0
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>()
.join(" ");
format!("{} {}", Self::COMMAND, features)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable(); let mut parts = parts.peekable();
if parts.peek().is_none() { if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0)); return Err(RequestParserError::UnexpectedEOF);
} }
let features = parts // TODO: verify that the features are split by whitespace
.enumerate() let mut features = Vec::new();
.map(|(i, f)| { for feature in parts {
f.parse() features.push(feature.to_string());
.map_err(|_| RequestParserError::SubtypeParserError { }
argument_index: i.try_into().unwrap_or(u32::MAX),
expected_type: "Feature",
raw_input: f.to_owned(),
})
})
.collect::<Result<Vec<Feature>, RequestParserError>>()?;
Ok(ProtocolEnableRequest(features)) Ok((Request::ProtocolEnable(features), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(ProtocolEnable);
impl Command for ProtocolEnable {
type Request = ProtocolEnableRequest;
type Response = ProtocolEnableResponse;
}

View File

@@ -1,15 +1,33 @@
use crate::{ use crate::commands::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response}, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
response_tokenizer::expect_property_type, expect_property_type,
}; };
pub struct TagTypes; pub struct TagTypes;
empty_command_request!(TagTypes, "tagtypes"); pub type TagTypesResponse = Vec<String>;
multi_item_command_response!(TagTypes, "tagtype", String);
impl Command for TagTypes { impl Command for TagTypes {
type Request = TagTypesRequest;
type Response = TagTypesResponse; 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<Self::Response, ResponseParserError> {
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 = expect_property_type!(Some(value), "tagtype", Text).to_string();
tagtypes.push(tagtype);
}
Ok(tagtypes)
}
} }

View File

@@ -1,12 +1,23 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::{
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct TagTypesAll; pub struct TagTypesAll;
empty_command_request!(TagTypesAll, "tagtypes all");
empty_command_response!(TagTypesAll);
impl Command for TagTypesAll { impl Command for TagTypesAll {
type Request = TagTypesAllRequest; type Response = ();
type Response = TagTypesAllResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,22 @@
use crate::{ use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response}, Request,
response_tokenizer::expect_property_type, commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
}; };
pub struct TagTypesAvailable; pub struct TagTypesAvailable;
empty_command_request!(TagTypesAvailable, "tagtypes available");
multi_item_command_response!(TagTypesAvailable, "tagtype", String);
impl Command for TagTypesAvailable { impl Command for TagTypesAvailable {
type Request = TagTypesAvailableRequest; type Response = ();
type Response = TagTypesAvailableResponse; 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<Self::Response, ResponseParserError> {
unimplemented!()
}
} }

View File

@@ -1,12 +1,23 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::{
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct TagTypesClear; pub struct TagTypesClear;
empty_command_request!(TagTypesClear, "tagtypes clear");
empty_command_response!(TagTypesClear);
impl Command for TagTypesClear { impl Command for TagTypesClear {
type Request = TagTypesClearRequest; type Response = ();
type Response = TagTypesClearResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,74 +1,35 @@
use std::u32;
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Request,
request_tokenizer::RequestTokenizer, commands::{
types::TagName, Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
}; };
pub struct TagTypesDisable; pub struct TagTypesDisable;
pub struct TagTypesDisableRequest(Vec<TagName>); impl Command for TagTypesDisable {
type Response = ();
impl TagTypesDisableRequest {
fn new(tagnames: Vec<TagName>) -> Self {
Self(tagnames)
}
}
impl CommandRequest for TagTypesDisableRequest {
const COMMAND: &'static str = "tagtypes disable"; const COMMAND: &'static str = "tagtypes disable";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request { fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::TagTypesDisable(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::TagTypesDisable(req) => Some(TagTypesDisableRequest(req)),
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {}",
Self::COMMAND,
self.0
.iter()
.map(|tag| tag.as_str())
.collect::<Vec<&str>>()
.join(" ")
)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable(); let mut parts = parts.peekable();
if parts.peek().is_none() { if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0)); return Err(RequestParserError::UnexpectedEOF);
} }
let tag_types = parts // TODO: verify that the tag types are split by whitespace
.enumerate() let mut tag_types = Vec::new();
.map(|(i, s)| { for tag_type in parts {
s.parse() tag_types.push(tag_type.to_string());
.map_err(|_| RequestParserError::SubtypeParserError { }
argument_index: i.try_into().unwrap_or(u32::MAX),
expected_type: "TagName",
raw_input: s.to_owned(),
})
})
.collect::<Result<Vec<TagName>, RequestParserError>>()?;
Ok(TagTypesDisableRequest(tag_types)) Ok((Request::TagTypesDisable(tag_types), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(TagTypesDisable);
impl Command for TagTypesDisable {
type Request = TagTypesDisableRequest;
type Response = TagTypesDisableResponse;
}

View File

@@ -1,72 +1,35 @@
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Request,
request_tokenizer::RequestTokenizer, commands::{
types::TagName, Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
}; };
pub struct TagTypesEnable; pub struct TagTypesEnable;
pub struct TagTypesEnableRequest(Vec<TagName>); impl Command for TagTypesEnable {
type Response = ();
impl TagTypesEnableRequest {
fn new(tagnames: Vec<TagName>) -> Self {
Self(tagnames)
}
}
impl CommandRequest for TagTypesEnableRequest {
const COMMAND: &'static str = "tagtypes enable"; const COMMAND: &'static str = "tagtypes enable";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request { fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::TagTypesEnable(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::TagTypesEnable(req) => Some(TagTypesEnableRequest(req)),
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {}",
Self::COMMAND,
self.0
.iter()
.map(|tag| tag.as_str())
.collect::<Vec<&str>>()
.join(" ")
)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable(); let mut parts = parts.peekable();
if parts.peek().is_none() { if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0)); return Err(RequestParserError::UnexpectedEOF);
} }
let tag_types = parts // TODO: verify that the tag types are split by whitespace
.enumerate() let mut tag_types = Vec::new();
.map(|(i, s)| { for tag_type in parts {
s.parse() tag_types.push(tag_type.to_string());
.map_err(|_| RequestParserError::SubtypeParserError { }
argument_index: i.try_into().unwrap_or(u32::MAX),
expected_type: "TagName",
raw_input: s.to_owned(),
})
})
.collect::<Result<Vec<TagName>, RequestParserError>>()?;
Ok(TagTypesEnableRequest(tag_types)) Ok((Request::TagTypesEnable(tag_types), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(TagTypesEnable);
impl Command for TagTypesEnable {
type Request = TagTypesEnableRequest;
type Response = TagTypesEnableResponse;
}

View File

@@ -1,72 +1,35 @@
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Request,
request_tokenizer::RequestTokenizer, commands::{
types::TagName, Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
}; };
pub struct TagTypesReset; pub struct TagTypesReset;
pub struct TagTypesResetRequest(Vec<TagName>); impl Command for TagTypesReset {
type Response = ();
impl TagTypesResetRequest {
fn new(tagnames: Vec<TagName>) -> Self {
Self(tagnames)
}
}
impl CommandRequest for TagTypesResetRequest {
const COMMAND: &'static str = "tagtypes reset"; const COMMAND: &'static str = "tagtypes reset";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request { fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::TagTypesReset(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::TagTypesReset(req) => Some(TagTypesResetRequest(req)),
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {}",
Self::COMMAND,
self.0
.iter()
.map(|tag| tag.as_str())
.collect::<Vec<&str>>()
.join(" ")
)
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts.peekable(); let mut parts = parts.peekable();
if parts.peek().is_none() { if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0)); return Err(RequestParserError::UnexpectedEOF);
} }
let tag_types = parts // TODO: verify that the tag types are split by whitespace
.enumerate() let mut tag_types = Vec::new();
.map(|(i, s)| { for tag_type in parts {
s.parse() tag_types.push(tag_type.to_string());
.map_err(|_| RequestParserError::SubtypeParserError { }
argument_index: i.try_into().unwrap_or(u32::MAX),
expected_type: "TagName",
raw_input: s.to_owned(),
})
})
.collect::<Result<Vec<TagName>, RequestParserError>>()?;
Ok(TagTypesResetRequest(tag_types)) Ok((Request::TagTypesReset(tag_types), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(TagTypesReset);
impl Command for TagTypesReset {
type Request = TagTypesResetRequest;
type Response = TagTypesResetResponse;
}

View File

@@ -1,19 +1,19 @@
mod next; pub mod next;
mod pause; pub mod pause;
mod play; pub mod play;
mod playid; pub mod playid;
mod previous; pub mod previous;
mod seek; pub mod seek;
mod seekcur; pub mod seekcur;
mod seekid; pub mod seekid;
mod stop; pub mod stop;
pub use next::*; pub use next::Next;
pub use pause::*; pub use pause::Pause;
pub use play::*; pub use play::Play;
pub use playid::*; pub use playid::PlayId;
pub use previous::*; pub use previous::Previous;
pub use seek::*; pub use seek::Seek;
pub use seekcur::*; pub use seekcur::SeekCur;
pub use seekid::*; pub use seekid::SeekId;
pub use stop::*; pub use stop::Stop;

View File

@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Next; pub struct Next;
empty_command_request!(Next, "next");
empty_command_response!(Next);
impl Command for Next { impl Command for Next {
type Request = NextRequest; type Response = ();
type Response = NextResponse; const COMMAND: &'static str = "next";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Next, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,63 +1,31 @@
use crate::{ use crate::commands::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError,
}; };
pub struct Pause; pub struct Pause;
pub struct PauseRequest(Option<bool>); impl Command for Pause {
type Response = ();
impl PauseRequest {
fn new(toggle: Option<bool>) -> Self {
Self(toggle)
}
}
impl CommandRequest for PauseRequest {
const COMMAND: &'static str = "pause"; const COMMAND: &'static str = "pause";
const MIN_ARGS: u32 = 0;
const MAX_ARGS: Option<u32> = Some(1);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::Pause(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Pause(value) => Some(PauseRequest(value)),
_ => None,
}
}
fn serialize(&self) -> String {
match self.0 {
Some(true) => format!("{} 1\n", Self::COMMAND),
Some(false) => format!("{} 0\n", Self::COMMAND),
None => Self::COMMAND.to_string() + "\n",
}
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let result = match parts.next() { let result = match parts.next() {
Some("0") => Ok(Some(false)), Some("0") => Ok((Request::Pause(Some(false)), "")),
Some("1") => Ok(Some(true)), Some("1") => Ok((Request::Pause(Some(true)), "")),
Some(s) => Err(RequestParserError::SubtypeParserError { Some(s) => Err(RequestParserError::SyntaxError(0, s.to_string())),
argument_index: 0, None => Ok((Request::Pause(None), "")),
expected_type: "Option<bool>",
raw_input: s.to_owned(),
}),
None => Ok(None),
}; };
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
result.map(PauseRequest) result
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(Pause);
impl Command for Pause {
type Request = PauseRequest;
type Response = PauseResponse;
}

View File

@@ -1,15 +1,34 @@
use crate::{ use crate::{
commands::{Command, empty_command_response, single_optional_item_command_request}, commands::{
types::SongPosition, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::SongPosition,
}; };
pub struct Play; pub struct Play;
single_optional_item_command_request!(Play, "play", SongPosition);
empty_command_response!(Play);
impl Command for Play { impl Command for Play {
type Request = PlayRequest; type Response = ();
type Response = PlayResponse; const COMMAND: &'static str = "play";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let songpos = match parts.next() {
Some(s) => s
.parse::<SongPosition>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Play(songpos), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,34 @@
use crate::{ use crate::{
commands::{Command, empty_command_response, single_optional_item_command_request}, commands::{
types::SongId, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::SongId,
}; };
pub struct PlayId; pub struct PlayId;
single_optional_item_command_request!(PlayId, "playid", SongId);
empty_command_response!(PlayId);
impl Command for PlayId { impl Command for PlayId {
type Request = PlayIdRequest; type Response = ();
type Response = PlayIdResponse; const COMMAND: &'static str = "playid";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let songid = match parts.next() {
Some(s) => s
.parse::<SongId>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::PlayId(songid), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Previous; pub struct Previous;
empty_command_request!(Previous, "previous");
empty_command_response!(Previous);
impl Command for Previous { impl Command for Previous {
type Request = PreviousRequest; type Response = ();
type Response = PreviousResponse; const COMMAND: &'static str = "previous";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Previous, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,73 +1,41 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, commands::{
request_tokenizer::RequestTokenizer, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::{SongPosition, TimeWithFractions}, ResponseParserError,
},
common::{SongPosition, TimeWithFractions},
}; };
pub struct Seek; pub struct Seek;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for Seek {
pub struct SeekRequest { type Response = ();
pub songpos: SongPosition,
pub time: TimeWithFractions,
}
impl CommandRequest for SeekRequest {
const COMMAND: &'static str = "seek"; const COMMAND: &'static str = "seek";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::Seek(self.songpos, self.time)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Seek(songpos, time) => Some(SeekRequest { songpos, time }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}\n", Self::COMMAND, self.songpos, self.time)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let songpos = match parts.next() { let songpos = match parts.next() {
Some(s) => { Some(s) => s
s.parse::<SongPosition>() .parse::<SongPosition>()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
argument_index: 0, None => return Err(RequestParserError::UnexpectedEOF),
expected_type: "SongPosition",
raw_input: s.to_owned(),
})?
}
None => return Err(Self::missing_arguments_error(0)),
}; };
let time = match parts.next() { let time = match parts.next() {
Some(t) => t.parse::<TimeWithFractions>().map_err(|_| { Some(t) => t
RequestParserError::SubtypeParserError { .parse::<TimeWithFractions>()
argument_index: 1, .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
expected_type: "TimeWithFractions", None => return Err(RequestParserError::UnexpectedEOF),
raw_input: t.to_owned(),
}
})?,
None => return Err(Self::missing_arguments_error(1)),
}; };
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(SeekRequest { songpos, time }) Ok((Request::Seek(songpos, time), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(Seek);
impl Command for Seek {
type Request = SeekRequest;
type Response = SeekResponse;
}

View File

@@ -1,96 +1,53 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, commands::{
request_tokenizer::RequestTokenizer, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::{SeekMode, TimeWithFractions}, ResponseParserError,
},
common::{SeekMode, TimeWithFractions},
}; };
pub struct SeekCur; pub struct SeekCur;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for SeekCur {
pub struct SeekCurRequest { type Response = ();
pub mode: SeekMode,
pub time: TimeWithFractions,
}
impl CommandRequest for SeekCurRequest {
const COMMAND: &'static str = "seekcur"; const COMMAND: &'static str = "seekcur";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(1);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::SeekCur(self.mode, self.time)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SeekCur(mode, time) => Some(SeekCurRequest { mode, time }),
_ => None,
}
}
fn serialize(&self) -> String {
let time_str = match self.mode {
SeekMode::Absolute => format!("{}", self.time),
SeekMode::Relative => format!("+{}", self.time),
SeekMode::RelativeReverse => format!("-{}", self.time),
};
format!("{} {}\n", Self::COMMAND, time_str)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let time_raw = match parts.next() { let time_raw = match parts.next() {
Some(t) => t, Some(t) => t,
None => return Err(Self::missing_arguments_error(0)), None => return Err(RequestParserError::UnexpectedEOF),
}; };
Self::throw_if_too_many_arguments(parts)?;
// TODO: DRY // TODO: DRY
let (mode, time) = match time_raw { let (mode, time) = match time_raw {
t if t.starts_with('+') => ( t if t.starts_with('+') => (
SeekMode::Relative, SeekMode::Relative,
t[1..].parse::<TimeWithFractions>().map_err(|_| { t[1..]
RequestParserError::SubtypeParserError { .parse::<TimeWithFractions>()
argument_index: 0, .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
expected_type: "TimeWithFractions",
raw_input: t[1..].to_owned(),
}
})?,
), ),
t if t.starts_with('-') => ( t if t.starts_with('-') => (
SeekMode::RelativeReverse, SeekMode::Relative,
t[1..].parse::<TimeWithFractions>().map_err(|_| { -t[1..]
RequestParserError::SubtypeParserError { .parse::<TimeWithFractions>()
argument_index: 0, .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
expected_type: "TimeWithFractions",
raw_input: t[1..].to_owned(),
}
})?,
), ),
t => ( t => (
SeekMode::Absolute, SeekMode::Absolute,
t.parse::<TimeWithFractions>().map_err(|_| { t.parse::<TimeWithFractions>()
RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
argument_index: 0,
expected_type: "TimeWithFractions",
raw_input: t.to_owned(),
}
})?,
), ),
}; };
debug_assert!(time >= 0.0); debug_assert!(parts.next().is_none());
Ok(SeekCurRequest { mode, time }) Ok((Request::SeekCur(mode, time), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(SeekCur);
impl Command for SeekCur {
type Request = SeekCurRequest;
type Response = SeekCurResponse;
}

View File

@@ -1,71 +1,41 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, commands::{
request_tokenizer::RequestTokenizer, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::{SongId, TimeWithFractions}, ResponseParserError,
},
common::{SongId, TimeWithFractions},
}; };
pub struct SeekId; pub struct SeekId;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for SeekId {
pub struct SeekIdRequest { type Response = ();
pub songid: SongId,
pub time: TimeWithFractions,
}
impl CommandRequest for SeekIdRequest {
const COMMAND: &'static str = "seekid"; const COMMAND: &'static str = "seekid";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::SeekId(self.songid, self.time)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SeekId(songid, time) => Some(SeekIdRequest { songid, time }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}\n", Self::COMMAND, self.songid, self.time)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let songid = match parts.next() { let songid = match parts.next() {
Some(s) => s Some(s) => s
.parse::<SongId>() .parse::<SongId>()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
argument_index: 0, None => return Err(RequestParserError::UnexpectedEOF),
expected_type: "SongId",
raw_input: s.to_owned(),
})?,
None => return Err(Self::missing_arguments_error(0)),
}; };
let time = match parts.next() { let time = match parts.next() {
Some(t) => t.parse::<TimeWithFractions>().map_err(|_| { Some(t) => t
RequestParserError::SubtypeParserError { .parse::<TimeWithFractions>()
argument_index: 1, .map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
expected_type: "TimeWithFractions", None => return Err(RequestParserError::UnexpectedEOF),
raw_input: t.to_owned(),
}
})?,
None => return Err(Self::missing_arguments_error(1)),
}; };
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(SeekIdRequest { songid, time }) Ok((Request::SeekId(songid, time), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(SeekId);
impl Command for SeekId {
type Request = SeekIdRequest;
type Response = SeekIdResponse;
}

View File

@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Stop; pub struct Stop;
empty_command_request!(Stop, "stop");
empty_command_response!(Stop);
impl Command for Stop { impl Command for Stop {
type Request = StopRequest; type Response = ();
type Response = StopResponse; const COMMAND: &'static str = "stop";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Stop, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,9 +1,9 @@
mod listmounts; pub mod listmounts;
mod listneighbors; pub mod listneighbors;
mod mount; pub mod mount;
mod unmount; pub mod unmount;
pub use listmounts::*; pub use listmounts::ListMounts;
pub use listneighbors::*; pub use listneighbors::ListNeighbors;
pub use mount::*; pub use mount::Mount;
pub use unmount::*; pub use unmount::Unmount;

View File

@@ -1,39 +1,22 @@
use crate::{ use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response}, Request,
response_tokenizer::expect_property_type, commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
}; };
pub struct ListMounts; pub struct ListMounts;
empty_command_request!(ListMounts, "listmounts");
multi_item_command_response!(ListMounts, "mount", String);
impl Command for ListMounts { impl Command for ListMounts {
type Request = ListMountsRequest; type Response = Vec<(String, String)>;
type Response = ListMountsResponse; const COMMAND: &'static str = "listmounts";
}
#[cfg(test)] fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
mod tests { debug_assert!(parts.next().is_none());
use indoc::indoc; Ok((Request::ListMounts, ""))
}
use super::*; fn parse_response(
parts: ResponseAttributes<'_>,
#[test] ) -> Result<Self::Response, ResponseParserError> {
fn test_parse_response() { unimplemented!()
let input = indoc! {"
mount:
mount: /mnt/music
OK
"};
let result = ListMounts::parse_raw_response(input.as_bytes());
assert_eq!(
result,
Ok(ListMountsResponse(vec![
"".to_string(),
"/mnt/music".to_string(),
]))
);
} }
} }

View File

@@ -1,52 +1,22 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, Request,
response_tokenizer::{ResponseAttributes, expect_property_type}, commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
}; };
pub struct ListNeighbors; pub struct ListNeighbors;
empty_command_request!(ListNeighbors, "listneighbors");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListNeighborsResponse(HashMap<String, String>);
impl CommandResponse for ListNeighborsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
debug_assert!(parts.len() % 2 == 0);
let mut result = HashMap::with_capacity(parts.len() / 2);
for channel_message_pair in parts.chunks_exact(2) {
let (neigh_key, neigh_value) = channel_message_pair[0];
let (name_key, name_value) = channel_message_pair[1];
debug_assert!(neigh_key == "neighbor");
debug_assert!(name_key == "name");
let neighbor = expect_property_type!(Some(neigh_value), "neighbor", Text).to_string();
let name = expect_property_type!(Some(name_value), "name", Text).to_string();
result.insert(neighbor, name);
}
Ok(ListNeighborsResponse(result))
}
}
impl Command for ListNeighbors { impl Command for ListNeighbors {
type Request = ListNeighborsRequest; type Response = Vec<(String, String)>;
type Response = ListNeighborsResponse; 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<Self::Response, ResponseParserError> {
unimplemented!()
}
} }

View File

@@ -1,69 +1,36 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Request,
request_tokenizer::RequestTokenizer, commands::{
types::{MountPath, Uri}, Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
}; };
pub struct Mount; pub struct Mount;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for Mount {
pub struct MountRequest { type Response = ();
pub path: MountPath,
pub uri: Uri,
}
impl CommandRequest for MountRequest {
const COMMAND: &'static str = "mount"; const COMMAND: &'static str = "mount";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::Mount(self.path, self.uri) let path = parts
} .next()
.ok_or(RequestParserError::UnexpectedEOF)?
fn from_request_enum(request: crate::Request) -> Option<Self> { .to_string();
match request {
crate::Request::Mount(path, uri) => Some(MountRequest { path, uri }),
_ => None,
}
}
fn serialize(&self) -> String {
debug_assert!(self.path.to_str().is_some());
format!(
"{} {} {}\n",
Self::COMMAND,
self.path.to_str().unwrap_or("<invalid path>"),
self.uri
)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let path = parts.next().ok_or(Self::missing_arguments_error(0))?;
let path = path
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "MountPath",
raw_input: path.to_string(),
})?;
let uri = parts let uri = parts
.next() .next()
.ok_or(Self::missing_arguments_error(1))? .ok_or(RequestParserError::UnexpectedEOF)?
.to_string(); .to_string();
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(MountRequest { path, uri }) Ok((Request::Mount(path, uri), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(Mount);
impl Command for Mount {
type Request = MountRequest;
type Response = MountResponse;
}

View File

@@ -1,59 +1,31 @@
use crate::{ use crate::{
Request,
commands::{ commands::{
Command, CommandRequest, RequestParserError, RequestTokenizer, empty_command_response, Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
}, },
types::MountPath,
}; };
pub struct Unmount; pub struct Unmount;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct UnmountRequest(MountPath);
impl CommandRequest for UnmountRequest {
const COMMAND: &'static str = "unmount";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(1);
fn into_request_enum(self) -> crate::Request {
crate::Request::Unmount(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Unmount(item) => Some(UnmountRequest(item)),
_ => None,
}
}
fn serialize(&self) -> String {
debug_assert!(self.0.to_str().is_some());
format!(
"{} {}\n",
Self::COMMAND,
self.0.to_str().unwrap_or("<invalid path>")
)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let path = parts.next().ok_or(Self::missing_arguments_error(0))?;
let path =
path.parse::<MountPath>()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "MountPath",
raw_input: path.to_string(),
})?;
Self::throw_if_too_many_arguments(parts)?;
Ok(UnmountRequest(path))
}
}
empty_command_response!(Unmount);
impl Command for Unmount { impl Command for Unmount {
type Request = UnmountRequest; type Response = ();
type Response = UnmountResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,37 +1,37 @@
mod albumart; pub mod albumart;
mod count; pub mod count;
mod find; pub mod find;
mod findadd; pub mod findadd;
mod getfingerprint; pub mod getfingerprint;
mod list; pub mod list;
mod listall; pub mod listall;
mod listallinfo; pub mod listallinfo;
mod listfiles; pub mod listfiles;
mod lsinfo; pub mod lsinfo;
mod readcomments; pub mod readcomments;
mod readpicture; pub mod readpicture;
mod rescan; pub mod rescan;
mod search; pub mod search;
mod searchadd; pub mod searchadd;
mod searchaddpl; pub mod searchaddpl;
mod searchcount; pub mod searchcount;
mod update; pub mod update;
pub use albumart::*; pub use albumart::AlbumArt;
pub use count::*; pub use count::Count;
pub use find::*; pub use find::Find;
pub use findadd::*; pub use findadd::FindAdd;
pub use getfingerprint::*; pub use getfingerprint::GetFingerprint;
pub use list::*; pub use list::List;
pub use listall::*; pub use listall::ListAll;
pub use listallinfo::*; pub use listallinfo::ListAllInfo;
pub use listfiles::*; pub use listfiles::ListFiles;
pub use lsinfo::*; pub use lsinfo::LsInfo;
pub use readcomments::*; pub use readcomments::ReadComments;
pub use readpicture::*; pub use readpicture::ReadPicture;
pub use rescan::*; pub use rescan::Rescan;
pub use search::*; pub use search::Search;
pub use searchadd::*; pub use searchadd::SearchAdd;
pub use searchaddpl::*; pub use searchaddpl::SearchAddPl;
pub use searchcount::*; pub use searchcount::SearchCount;
pub use update::*; pub use update::Update;

View File

@@ -1,85 +1,46 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, commands::{
request_tokenizer::RequestTokenizer, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
response_tokenizer::{ResponseAttributes, get_and_parse_property, get_property}, ResponseParserError, get_and_parse_property, get_property,
types::{Offset, Uri}, },
common::Offset,
}; };
pub struct AlbumArt; pub struct AlbumArt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlbumArtRequest {
uri: Uri,
offset: Offset,
}
impl CommandRequest for AlbumArtRequest {
const COMMAND: &'static str = "albumart";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request {
crate::Request::AlbumArt(self.uri, self.offset)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::AlbumArt(uri, offset) => Some(AlbumArtRequest { uri, offset }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}\n", Self::COMMAND, self.uri, self.offset)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(Self::missing_arguments_error(0)),
};
let offset = match parts.next() {
Some(s) => s
.parse::<Offset>()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "Offset",
raw_input: s.to_string(),
})?,
None => return Err(Self::missing_arguments_error(1)),
};
Self::throw_if_too_many_arguments(parts)?;
Ok(AlbumArtRequest {
uri: uri.to_string(),
offset,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlbumArtResponse { pub struct AlbumArtResponse {
pub size: usize, pub size: usize,
pub binary: Vec<u8>, pub binary: Vec<u8>,
} }
impl CommandResponse for AlbumArtResponse { impl Command for AlbumArt {
fn into_response_enum(self) -> crate::Response { type Response = AlbumArtResponse;
todo!() const COMMAND: &'static str = "albumart";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(RequestParserError::UnexpectedEOF),
};
let offset = match parts.next() {
Some(s) => s
.parse::<Offset>()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::AlbumArt(uri.to_string(), offset), ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let size = get_and_parse_property!(parts, "size", Text); let size = get_and_parse_property!(parts, "size", Text);
@@ -88,8 +49,3 @@ impl CommandResponse for AlbumArtResponse {
Ok(AlbumArtResponse { size, binary }) Ok(AlbumArtResponse { size, binary })
} }
} }
impl Command for AlbumArt {
type Request = AlbumArtRequest;
type Response = AlbumArtResponse;
}

View File

@@ -1,100 +1,48 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, commands::{
filter::Filter, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError, get_and_parse_property,
response_tokenizer::{ResponseAttributes, get_and_parse_property}, },
types::GroupType, filter::parse_filter,
}; };
pub struct Count; pub struct Count;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CountRequest {
filter: Filter,
group: Option<GroupType>,
}
impl CommandRequest for CountRequest {
const COMMAND: &'static str = "count";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request {
crate::Request::Count(self.filter, self.group)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Count(filter, group) => Some(CountRequest { filter, group }),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(group) = self.group.as_ref() {
cmd.push_str(&format!(" group {}", group));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let group = if let Some("group") = parts.next() {
let group = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
argument_index: 1,
keyword: "group",
})?;
Some(
group
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "GroupType",
raw_input: group.to_owned(),
})?,
)
} else {
None
};
Self::throw_if_too_many_arguments(parts)?;
Ok(CountRequest { filter, group })
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CountResponse { pub struct CountResponse {
pub songs: usize, pub songs: usize,
pub playtime: u64, pub playtime: u64,
} }
impl CommandResponse for CountResponse { impl Command for Count {
fn into_response_enum(self) -> crate::Response { type Response = CountResponse;
todo!() const COMMAND: &'static str = "count";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let filter = parse_filter(&mut parts)?;
let group = if let Some("group") = parts.next() {
let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
Some(
group
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?,
)
} else {
None
};
debug_assert!(parts.next().is_none());
Ok((Request::Count(filter, group), ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let songs = get_and_parse_property!(parts, "songs", Text); let songs = get_and_parse_property!(parts, "songs", Text);
let playtime = get_and_parse_property!(parts, "playtime", Text); let playtime = get_and_parse_property!(parts, "playtime", Text);
@@ -102,8 +50,3 @@ impl CommandResponse for CountResponse {
Ok(CountResponse { songs, playtime }) Ok(CountResponse { songs, playtime })
} }
} }
impl Command for Count {
type Request = CountRequest;
type Response = CountResponse;
}

View File

@@ -1,143 +1,51 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, commands::{
filter::Filter, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError,
response_tokenizer::ResponseAttributes, },
types::{DbSelectionPrintResponse, DbSongInfo, Sort, WindowRange}, filter::parse_filter,
}; };
pub struct Find; pub struct Find;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct FindResponse {}
pub struct FindRequest {
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
}
impl CommandRequest for FindRequest { impl Command for Find {
type Response = FindResponse;
const COMMAND: &'static str = "find"; const COMMAND: &'static str = "find";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(3);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::Find(self.filter, self.sort, self.window) let filter = parse_filter(&mut parts)?;
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Find(filter, sort, window) => Some(FindRequest {
filter,
sort,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let mut argument_index_counter = 0;
let mut sort_or_window = parts.next(); let mut sort_or_window = parts.next();
let mut sort = None; let mut sort = None;
if let Some("sort") = sort_or_window { if let Some("sort") = sort_or_window {
argument_index_counter += 1;
let s = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "sort",
argument_index: argument_index_counter,
})?;
sort = Some( sort = Some(
s.parse() parts
.map_err(|_| RequestParserError::SubtypeParserError { .next()
argument_index: argument_index_counter, .ok_or(RequestParserError::UnexpectedEOF)?
expected_type: "Sort", .to_string(),
raw_input: s.to_string(),
})?,
); );
sort_or_window = parts.next(); sort_or_window = parts.next();
} }
let mut window = None; let mut window = None;
if let Some("window") = sort_or_window { if let Some("window") = sort_or_window {
argument_index_counter += 1; let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
window = Some( window = Some(
w.parse() w.parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
); );
} }
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(FindRequest { Ok((Request::Find(filter, sort, window), ""))
filter, }
sort,
window, fn parse_response(
}) parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
} }
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FindResponse(Vec<DbSongInfo>);
impl CommandResponse for FindResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
DbSelectionPrintResponse::Song(db_song_info) => Ok(db_song_info),
DbSelectionPrintResponse::Directory(_db_directory_info) => Err(
ResponseParserError::UnexpectedProperty("directory".to_string()),
),
DbSelectionPrintResponse::Playlist(_db_playlist_info) => Err(
ResponseParserError::UnexpectedProperty("playlist".to_string()),
),
})
.collect::<Result<Vec<DbSongInfo>, ResponseParserError>>()
.map(FindResponse)
}
}
impl Command for Find {
type Request = FindRequest;
type Response = FindResponse;
}

View File

@@ -1,141 +1,60 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, commands::{
filter::Filter, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError,
types::{SongPosition, Sort, WindowRange}, },
filter::parse_filter,
}; };
pub struct FindAdd; pub struct FindAdd;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for FindAdd {
pub struct FindAddRequest { type Response = ();
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
}
impl CommandRequest for FindAddRequest {
const COMMAND: &'static str = "findadd"; const COMMAND: &'static str = "findadd";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(4);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::FindAdd(self.filter, self.sort, self.window, self.position) let filter = parse_filter(&mut parts)?;
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::FindAdd(filter, sort, window, position) => Some(FindAddRequest {
filter,
sort,
window,
position,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
if let Some(position) = &self.position {
cmd.push_str(&format!(" position {}", position));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let mut argument_index_counter = 0;
let mut sort_or_window_or_position = parts.next(); let mut sort_or_window_or_position = parts.next();
let mut sort = None; let mut sort = None;
if let Some("sort") = sort_or_window_or_position { if let Some("sort") = sort_or_window_or_position {
argument_index_counter += 1;
let s = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "sort",
argument_index: argument_index_counter,
})?;
sort = Some( sort = Some(
s.parse() parts
.map_err(|_| RequestParserError::SubtypeParserError { .next()
argument_index: argument_index_counter, .ok_or(RequestParserError::UnexpectedEOF)?
expected_type: "Sort", .to_string(),
raw_input: s.to_string(),
})?,
); );
sort_or_window_or_position = parts.next(); sort_or_window_or_position = parts.next();
} }
let mut window = None; let mut window = None;
if let Some("window") = sort_or_window_or_position { if let Some("window") = sort_or_window_or_position {
argument_index_counter += 1; let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
window = Some( window = Some(
w.parse() w.parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
); );
sort_or_window_or_position = parts.next(); sort_or_window_or_position = parts.next();
} }
let mut position = None; let mut position = None;
if let Some("position") = sort_or_window_or_position { if let Some("position") = sort_or_window_or_position {
argument_index_counter += 1; let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let p = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "position",
argument_index: argument_index_counter,
})?;
position = Some( position = Some(
p.parse() p.parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?,
argument_index: argument_index_counter,
expected_type: "SongPosition",
raw_input: p.to_string(),
})?,
); );
} }
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(FindAddRequest { Ok((Request::FindAdd(filter, sort, window, position), ""))
filter, }
sort,
window, fn parse_response(
position, parts: ResponseAttributes<'_>,
}) ) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(FindAdd);
impl Command for FindAdd {
type Request = FindAddRequest;
type Response = FindAddResponse;
}

View File

@@ -1,41 +1,38 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
use crate::{ ResponseParserError, get_and_parse_property,
commands::{Command, CommandResponse, ResponseParserError, single_item_command_request},
response_tokenizer::{ResponseAttributes, get_and_parse_property},
types::Uri,
}; };
pub struct GetFingerprint; pub struct GetFingerprint;
single_item_command_request!(GetFingerprint, "getfingerprint", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GetFingerprintResponse { pub struct GetFingerprintResponse {
pub chromaprint: String, pub chromaprint: String,
} }
impl CommandResponse for GetFingerprintResponse { impl Command for GetFingerprint {
fn into_response_enum(self) -> crate::Response { type Response = GetFingerprintResponse;
todo!() const COMMAND: &'static str = "getfingerprint";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
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::GetFingerprint(uri), ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let chromaprint = get_and_parse_property!(parts, "chromaprint", Text); let chromaprint = get_and_parse_property!(parts, "chromaprint", Text);
Ok(GetFingerprintResponse { chromaprint }) Ok(GetFingerprintResponse { chromaprint })
} }
} }
impl Command for GetFingerprint {
type Request = GetFingerprintRequest;
type Response = GetFingerprintResponse;
}

View File

@@ -1,169 +1,58 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, commands::{
filter::Filter, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError, expect_property_type,
response_tokenizer::{ResponseAttributes, expect_property_type}, },
types::{GroupType, TagName, WindowRange}, filter::parse_filter,
}; };
pub struct List; pub struct List;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub type ListResponse = Vec<String>;
pub struct ListRequest {
tagname: TagName,
filter: Option<Filter>,
groups: Vec<GroupType>,
window: Option<WindowRange>,
}
impl CommandRequest for ListRequest { impl Command for List {
type Response = ListResponse;
const COMMAND: &'static str = "list"; const COMMAND: &'static str = "list";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::List(self.tagname, self.filter, self.groups, self.window) let tagtype = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
} let tagtype = tagtype
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::List(tagname, filter, groups, window) => Some(ListRequest {
tagname,
filter,
groups,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = match &self.filter {
Some(f) => format!("{} {} {}", Self::COMMAND, self.tagname, f),
None => format!("{} {}", Self::COMMAND, self.tagname),
};
for group in &self.groups {
cmd.push_str(&format!(" group {}", group));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let tagname = parts.next().ok_or(Self::missing_arguments_error(0))?;
let tagname = tagname
.parse() .parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(1, tagtype.to_owned()))?;
argument_index: 0,
expected_type: "TagName",
raw_input: tagname.to_owned(),
})?;
let mut filter = None; // TODO: This should be optional
let mut groups = Vec::new(); let filter = parse_filter(&mut parts)?;
let mut window = None;
let mut next = parts.next(); let group = if let Some("group") = parts.next() {
let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let mut argument_index_counter = 0; Some(
if let Some(f) = next
&& f != "group"
&& f != "window"
{
argument_index_counter += 1;
let parsed_filter =
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?;
filter = Some(parsed_filter);
next = parts.next();
}
while let Some(g) = next
&& g == "group"
{
argument_index_counter += 1;
let group = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "group",
argument_index: argument_index_counter,
})?;
let parsed_group =
group group
.parse() .parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?,
argument_index: 2, )
expected_type: "GroupType", } else {
raw_input: group.to_owned(), None
})?; };
groups.push(parsed_group);
next = parts.next(); debug_assert!(parts.next().is_none());
Ok((Request::List(tagtype, filter, group), ""))
} }
if let Some(w) = next fn parse_response(
&& w == "window" parts: ResponseAttributes<'_>,
{ ) -> Result<Self::Response, ResponseParserError> {
let window_str = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
let parsed_window =
window_str
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 3,
expected_type: "WindowRange",
raw_input: window_str.to_owned(),
})?;
window = Some(parsed_window);
}
Self::throw_if_too_many_arguments(parts)?;
Ok(ListRequest {
tagname,
filter,
groups,
window,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListResponse(Vec<String>);
impl CommandResponse for ListResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts_: Vec<_> = parts.into_vec()?;
debug_assert!({ debug_assert!({
let key = parts_.first().map(|(k, _)| k); let key = parts.0.first().map(|(k, _)| k);
parts_.iter().all(|(k, _)| k == key.unwrap()) parts.0.iter().all(|(k, _)| k == key.unwrap())
}); });
let list = parts_ let list = parts
.0
.into_iter() .into_iter()
.map(|(k, v)| Ok(expect_property_type!(Some(v), k, Text).to_string())) .map(|(k, v)| Ok(expect_property_type!(Some(v), k, Text).to_string()))
.collect::<Result<Vec<_>, ResponseParserError>>()?; .collect::<Result<Vec<_>, ResponseParserError>>()?;
Ok(ListResponse(list)) Ok(list)
} }
} }
impl Command for List {
type Request = ListRequest;
type Response = ListResponse;
}

View File

@@ -1,163 +1,34 @@
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
use crate::{ ResponseParserError,
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::ResponseAttributes,
types::{DbSelectionPrintResponse, Uri},
}; };
pub struct ListAll; pub struct ListAll;
single_optional_item_command_request!(ListAll, "listall", Uri);
// TODO: This is supposed to be a tree-like structure, with directories containing files and playlists // TODO: This is supposed to be a tree-like structure, with directories containing files and playlists
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub type ListAllResponse = Vec<String>;
pub struct ListAllResponse(Vec<DbSelectionPrintResponse>);
impl CommandResponse for ListAllResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(ListAllResponse(result))
}
}
impl Command for ListAll { impl Command for ListAll {
type Request = ListAllRequest;
type Response = ListAllResponse; type Response = ListAllResponse;
} const COMMAND: &'static str = "listall";
#[cfg(test)] fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
mod tests { let uri = parts
use std::path::PathBuf; .next()
.map(|s| {
use crate::types::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo}; s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
use super::*;
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_response() {
let response = indoc! {"
directory: albums
directory: albums/a
file: albums/a/song1.mp3
file: albums/a/song2.mp3
file: albums/a/song3.mp3
playlist: albums/a/album a.m3u8
directory: albums/b
file: albums/b/song1.mp3
file: albums/b/song2.mp3
file: albums/b/song3.mp3
playlist: albums/b/album b.m3u8
OK
"};
let result = ListAll::parse_raw_response(response.as_bytes());
assert_eq!(
result,
Ok(ListAllResponse(vec![
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums"),
last_modified: None
}),
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums/a"),
last_modified: None
}),
DbSelectionPrintResponse::Song(DbSongInfo {
file: PathBuf::from("albums/a/song1.mp3"),
range: None,
last_modified: None,
added: None,
format: None,
tags: vec![],
time: None,
duration: None,
playlist: None
}),
DbSelectionPrintResponse::Song(DbSongInfo {
file: PathBuf::from("albums/a/song2.mp3"),
range: None,
last_modified: None,
added: None,
format: None,
tags: vec![],
time: None,
duration: None,
playlist: None
}),
DbSelectionPrintResponse::Song(DbSongInfo {
file: PathBuf::from("albums/a/song3.mp3"),
range: None,
last_modified: None,
added: None,
format: None,
tags: vec![],
time: None,
duration: None,
playlist: None
}),
DbSelectionPrintResponse::Playlist(DbPlaylistInfo {
playlist: PathBuf::from("albums/a/album a.m3u8"),
last_modified: None
}),
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums/b"),
last_modified: None
}),
DbSelectionPrintResponse::Song(DbSongInfo {
file: PathBuf::from("albums/b/song1.mp3"),
range: None,
last_modified: None,
added: None,
format: None,
tags: vec![],
time: None,
duration: None,
playlist: None
}),
DbSelectionPrintResponse::Song(DbSongInfo {
file: PathBuf::from("albums/b/song2.mp3"),
range: None,
last_modified: None,
added: None,
format: None,
tags: vec![],
time: None,
duration: None,
playlist: None
}),
DbSelectionPrintResponse::Song(DbSongInfo {
file: PathBuf::from("albums/b/song3.mp3"),
range: None,
last_modified: None,
added: None,
format: None,
tags: vec![],
time: None,
duration: None,
playlist: None
}),
DbSelectionPrintResponse::Playlist(DbPlaylistInfo {
playlist: PathBuf::from("albums/b/album b.m3u8"),
last_modified: None
}) })
])) .transpose()?;
)
debug_assert!(parts.next().is_none());
Ok((Request::ListAll(uri), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
} }
} }

View File

@@ -1,120 +1,35 @@
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
use crate::{ ResponseParserError,
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::ResponseAttributes,
types::{DbSelectionPrintResponse, Uri},
}; };
pub struct ListAllInfo; pub struct ListAllInfo;
single_optional_item_command_request!(ListAllInfo, "listallinfo", Uri);
// TODO: This is supposed to be a tree-like structure, with directories containing files and playlists // TODO: This is supposed to be a tree-like structure, with directories containing files and playlists
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // in addition to the metadata of each entry
pub struct ListAllInfoResponse(Vec<DbSelectionPrintResponse>); pub type ListAllInfoResponse = Vec<String>;
impl CommandResponse for ListAllInfoResponse {
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(ListAllInfoResponse(result))
}
}
impl Command for ListAllInfo { impl Command for ListAllInfo {
type Request = ListAllInfoRequest;
type Response = ListAllInfoResponse; type Response = ListAllInfoResponse;
} const COMMAND: &'static str = "listallinfo";
#[cfg(test)] fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
mod tests { let uri = parts
use std::path::PathBuf; .next()
.map(|s| {
use crate::types::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo, Tag}; s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
use super::*;
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_response() {
let response = indoc! {"
directory: albums
Last-Modified: 2024-10-01T10:00:00Z
directory: albums/a
Last-Modified: 2024-10-01T10:00:00Z
file: albums/a/song1.mp3
Last-Modified: 2022-12-31T09:00:00Z
Added: 2021-12-31T09:00:00Z
Format: 44100:16:2
Title: Song A
Artist: Artist A
Album: Album A
AlbumArtist: Artist A
Composer: Artist A
Genre: Pop
Track: 1
Disc: 1
Date: 2020-01-01
Time: 360
duration: 360.123
playlist: albums/a/album a.m3u8
Last-Modified: 2022-12-31T09:00:00Z
OK
"};
let result = ListAllInfo::parse_raw_response(response.as_bytes());
assert_eq!(
result,
Ok(ListAllInfoResponse(vec![
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums"),
last_modified: Some("2024-10-01T10:00:00Z".to_string()),
}),
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums/a"),
last_modified: Some("2024-10-01T10:00:00Z".to_string()),
}),
DbSelectionPrintResponse::Song(DbSongInfo {
file: PathBuf::from("albums/a/song1.mp3"),
range: None,
last_modified: Some("2022-12-31T09:00:00Z".to_string()),
added: Some("2021-12-31T09:00:00Z".to_string()),
format: Some("44100:16:2".to_string()),
tags: vec![
Tag::Album("Album A".to_string()),
Tag::AlbumArtist("Artist A".to_string()),
Tag::Artist("Artist A".to_string()),
Tag::Composer("Artist A".to_string()),
Tag::Date("2020-01-01".to_string()),
Tag::Disc("1".to_string()),
Tag::Genre("Pop".to_string()),
Tag::Title("Song A".to_string()),
Tag::Track("1".to_string()),
],
time: Some(360),
duration: Some(360.123),
playlist: None,
}),
DbSelectionPrintResponse::Playlist(DbPlaylistInfo {
playlist: PathBuf::from("albums/a/album a.m3u8"),
last_modified: Some("2022-12-31T09:00:00Z".to_string())
}) })
])), .transpose()?;
)
debug_assert!(parts.next().is_none());
Ok((Request::ListAllInfo(uri), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
} }
} }

View File

@@ -1,47 +1,34 @@
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
use crate::{ ResponseParserError,
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::ResponseAttributes,
types::{DbDirectoryInfo, DbSelectionPrintResponse, Uri},
}; };
pub struct ListFiles; pub struct ListFiles;
single_optional_item_command_request!(ListFiles, "listfiles", Uri); // TODO: fix this type
pub type ListFilesResponse = Vec<String>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListFilesResponse(Vec<DbDirectoryInfo>);
impl CommandResponse for ListFilesResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
DbSelectionPrintResponse::Directory(db_directory_info) => Ok(db_directory_info),
DbSelectionPrintResponse::Song(_db_song_info) => {
Err(ResponseParserError::UnexpectedProperty("file".to_string()))
}
DbSelectionPrintResponse::Playlist(_db_playlist_info) => Err(
ResponseParserError::UnexpectedProperty("playlist".to_string()),
),
})
.collect::<Result<Vec<_>, ResponseParserError>>()
.map(ListFilesResponse)
}
}
impl Command for ListFiles { impl Command for ListFiles {
type Request = ListFilesRequest;
type Response = ListFilesResponse; type Response = ListFilesResponse;
const COMMAND: &'static str = "listfiles";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let uri = parts
.next()
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.transpose()?;
debug_assert!(parts.next().is_none());
Ok((Request::ListFiles(uri), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
} }

View File

@@ -1,95 +1,34 @@
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
use crate::{ ResponseParserError,
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::ResponseAttributes,
types::{DbSelectionPrintResponse, Uri},
}; };
pub struct LsInfo; pub struct LsInfo;
single_optional_item_command_request!(LsInfo, "lsinfo", Uri); // TODO: fix this type
pub type LsInfoResponse = Vec<String>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LsInfoResponse(Vec<DbSelectionPrintResponse>);
impl CommandResponse for LsInfoResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(LsInfoResponse(result))
}
}
impl Command for LsInfo { impl Command for LsInfo {
type Request = LsInfoRequest;
type Response = LsInfoResponse; type Response = LsInfoResponse;
} const COMMAND: &'static str = "lsinfo";
#[cfg(test)] fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
mod tests { let uri = parts
use std::path::PathBuf; .next()
.map(|s| {
use crate::types::{DbDirectoryInfo, DbPlaylistInfo}; s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
use super::*;
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_response() {
let response = indoc! {"
directory: albums
Last-Modified: 2024-10-01T10:00:00Z
directory: albums/a
Last-Modified: 2024-10-01T10:00:00Z
playlist: albums/a/album a.m3u8
Last-Modified: 2022-12-31T09:00:00Z
directory: albums/b
Last-Modified: 2023-10-01T10:00:00Z
playlist: albums/b/album b.m3u8
Last-Modified: 2021-12-31T09:00:00Z
OK
"};
let result = LsInfo::parse_raw_response(response.as_bytes());
assert_eq!(
result,
Ok(LsInfoResponse(vec![
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums"),
last_modified: Some("2024-10-01T10:00:00Z".to_string())
}),
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums/a"),
last_modified: Some("2024-10-01T10:00:00Z".to_string())
}),
DbSelectionPrintResponse::Playlist(DbPlaylistInfo {
playlist: PathBuf::from("albums/a/album a.m3u8"),
last_modified: Some("2022-12-31T09:00:00Z".to_string())
}),
DbSelectionPrintResponse::Directory(DbDirectoryInfo {
directory: PathBuf::from("albums/b"),
last_modified: Some("2023-10-01T10:00:00Z".to_string())
}),
DbSelectionPrintResponse::Playlist(DbPlaylistInfo {
playlist: PathBuf::from("albums/b/album b.m3u8"),
last_modified: Some("2021-12-31T09:00:00Z".to_string())
}) })
])), .transpose()?;
);
debug_assert!(parts.next().is_none());
Ok((Request::LsInfo(uri), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
} }
} }

View File

@@ -1,47 +1,44 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, GenericResponseValue, Request, RequestParserError, RequestParserResult,
use crate::{ ResponseAttributes, ResponseParserError, expect_property_type,
commands::{Command, CommandResponse, ResponseParserError, single_item_command_request},
response_tokenizer::{GenericResponseValue, ResponseAttributes},
types::Uri,
}; };
pub struct ReadComments; pub struct ReadComments;
single_item_command_request!(ReadComments, "readcomments", Uri); pub type ReadCommentsResponse = HashMap<String, String>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for ReadComments {
pub struct ReadCommentsResponse(HashMap<String, String>); type Response = ReadCommentsResponse;
const COMMAND: &'static str = "readcomments";
impl CommandResponse for ReadCommentsResponse { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
fn into_response_enum(self) -> crate::Response { let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
todo!() let uri = uri
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
debug_assert!(parts.next().is_none());
Ok((Request::ReadComments(uri), ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let comments = parts let comments = parts
.iter() .into_iter()
.map(|(k, v)| match v { .map(|(k, v)| {
GenericResponseValue::Text(s) => Ok((k.to_string(), s.to_string())), Ok((
GenericResponseValue::Binary(_) => { k.to_string(),
Err(ResponseParserError::SyntaxError(1, k.to_string())) expect_property_type!(Some(v), k, Text).to_string(),
} ))
}) })
.collect::<Result<HashMap<_, _>, ResponseParserError>>()?; .collect::<Result<HashMap<_, _>, ResponseParserError>>()?;
Ok(ReadCommentsResponse(comments)) Ok(comments)
} }
} }
impl Command for ReadComments {
type Request = ReadCommentsRequest;
type Response = ReadCommentsResponse;
}

View File

@@ -1,109 +1,62 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, commands::{
request_tokenizer::RequestTokenizer, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
response_tokenizer::{ ResponseParserError, get_and_parse_property, get_optional_property, get_property,
ResponseAttributes, get_and_parse_property, get_optional_property, get_property,
}, },
types::{Offset, Uri}, common::Offset,
}; };
pub struct ReadPicture; pub struct ReadPicture;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadPictureRequest {
pub uri: Uri,
pub offset: Offset,
}
impl CommandRequest for ReadPictureRequest {
const COMMAND: &'static str = "readpicture";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request {
crate::Request::ReadPicture(self.uri, self.offset)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::ReadPicture(uri, offset) => Some(ReadPictureRequest { uri, offset }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}\n", Self::COMMAND, self.uri, self.offset)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(Self::missing_arguments_error(0)),
};
let offset = match parts.next() {
Some(s) => s
.parse::<Offset>()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "Offset",
raw_input: s.to_owned(),
})?,
None => return Err(Self::missing_arguments_error(1)),
};
Self::throw_if_too_many_arguments(parts)?;
Ok(ReadPictureRequest {
uri: uri.to_string(),
offset,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadPictureResponse { pub struct ReadPictureResponse {
pub size: usize, pub size: usize,
pub binary: Vec<u8>, pub binary: Vec<u8>,
pub mimetype: Option<String>, pub mimetype: Option<String>,
} }
impl CommandResponse for ReadPictureResponse { impl Command for ReadPicture {
fn into_response_enum(self) -> crate::Response { type Response = Option<ReadPictureResponse>;
todo!() const COMMAND: &'static str = "readpicture";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(RequestParserError::UnexpectedEOF),
};
let offset = match parts.next() {
Some(s) => s
.parse::<Offset>()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::ReadPicture(uri.to_string(), offset), ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
if parts.is_empty() {
return Ok(None);
} }
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
// TODO: is empty response possible?
// if parts.is_empty() {
// return Err(ResponseParserError::UnexpectedEOF);
// }
let size = get_and_parse_property!(parts, "size", Text); let size = get_and_parse_property!(parts, "size", Text);
let binary = get_property!(parts, "binary", Binary).into(); let binary = get_property!(parts, "binary", Binary).into();
let mimetype = get_optional_property!(parts, "mimetype", Text).map(|s| s.to_string()); let mimetype = get_optional_property!(parts, "mimetype", Text).map(|s| s.to_string());
Ok(ReadPictureResponse { Ok(Some(ReadPictureResponse {
size, size,
binary, binary,
mimetype, mimetype,
}) }))
} }
} }
impl Command for ReadPicture {
type Request = ReadPictureRequest;
type Response = ReadPictureResponse;
}

View File

@@ -1,43 +1,35 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
use crate::{ get_and_parse_property,
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::{ResponseAttributes, get_and_parse_property},
types::Uri,
}; };
pub struct Rescan; pub struct Rescan;
single_optional_item_command_request!(Rescan, "rescan", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RescanResponse { pub struct RescanResponse {
pub updating_db: usize, pub updating_db: usize,
} }
impl CommandResponse for RescanResponse { impl Command for Rescan {
fn into_response_enum(self) -> crate::Response { type Response = RescanResponse;
todo!() const COMMAND: &'static str = "rescan";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let uri = parts.next().map(|s| s.to_string());
debug_assert!(parts.next().is_none());
Ok((Request::Rescan(uri), ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let updating_db = get_and_parse_property!(parts, "updating_db", Text); let updating_db = get_and_parse_property!(parts, "updating_db", Text);
Ok(RescanResponse { updating_db }) Ok(RescanResponse { updating_db })
} }
} }
impl Command for Rescan {
type Request = RescanRequest;
type Response = RescanResponse;
}

View File

@@ -1,143 +1,51 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, commands::{
filter::Filter, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError,
response_tokenizer::ResponseAttributes, },
types::{DbSelectionPrintResponse, DbSongInfo, Sort, WindowRange}, filter::parse_filter,
}; };
pub struct Search; pub struct Search;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SearchResponse {}
pub struct SearchRequest {
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
}
impl CommandRequest for SearchRequest { impl Command for Search {
type Response = SearchResponse;
const COMMAND: &'static str = "search"; const COMMAND: &'static str = "search";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(3);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::Search(self.filter, self.sort, self.window) let filter = parse_filter(&mut parts)?;
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Search(filter, sort, window) => Some(SearchRequest {
filter,
sort,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let mut argument_index_counter = 0;
let mut sort_or_window = parts.next(); let mut sort_or_window = parts.next();
let mut sort = None; let mut sort = None;
if let Some("sort") = sort_or_window { if let Some("sort") = sort_or_window {
argument_index_counter += 1;
let s = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "sort",
argument_index: argument_index_counter,
})?;
sort = Some( sort = Some(
s.parse() parts
.map_err(|_| RequestParserError::SubtypeParserError { .next()
argument_index: argument_index_counter, .ok_or(RequestParserError::UnexpectedEOF)?
expected_type: "Sort", .to_string(),
raw_input: s.to_string(),
})?,
); );
sort_or_window = parts.next(); sort_or_window = parts.next();
} }
let mut window = None; let mut window = None;
if let Some("window") = sort_or_window { if let Some("window") = sort_or_window {
argument_index_counter += 1; let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
window = Some( window = Some(
w.parse() w.parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
); );
} }
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(SearchRequest { Ok((Request::Search(filter, sort, window), ""))
filter, }
sort,
window, fn parse_response(
}) parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
} }
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchResponse(Vec<DbSongInfo>);
impl CommandResponse for SearchResponse {
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
DbSelectionPrintResponse::Song(db_song_info) => Ok(db_song_info),
DbSelectionPrintResponse::Directory(_db_directory_info) => Err(
ResponseParserError::UnexpectedProperty("directory".to_string()),
),
DbSelectionPrintResponse::Playlist(_db_playlist_info) => Err(
ResponseParserError::UnexpectedProperty("playlist".to_string()),
),
})
.collect::<Result<Vec<DbSongInfo>, ResponseParserError>>()
.map(SearchResponse)
}
}
impl Command for Search {
type Request = SearchRequest;
type Response = SearchResponse;
}

View File

@@ -1,141 +1,60 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, commands::{
filter::Filter, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError,
types::{SongPosition, Sort, WindowRange}, },
filter::parse_filter,
}; };
pub struct SearchAdd; pub struct SearchAdd;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for SearchAdd {
pub struct SearchAddRequest { type Response = ();
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
}
impl CommandRequest for SearchAddRequest {
const COMMAND: &'static str = "searchadd"; const COMMAND: &'static str = "searchadd";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(4);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::SearchAdd(self.filter, self.sort, self.window, self.position) let filter = parse_filter(&mut parts)?;
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SearchAdd(filter, sort, window, position) => Some(SearchAddRequest {
filter,
sort,
window,
position,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
if let Some(position) = &self.position {
cmd.push_str(&format!(" position {}", position));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let mut argument_index_counter = 0;
let mut sort_or_window_or_position = parts.next(); let mut sort_or_window_or_position = parts.next();
let mut sort = None; let mut sort = None;
if let Some("sort") = sort_or_window_or_position { if let Some("sort") = sort_or_window_or_position {
argument_index_counter += 1;
let s = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "sort",
argument_index: argument_index_counter,
})?;
sort = Some( sort = Some(
s.parse() parts
.map_err(|_| RequestParserError::SubtypeParserError { .next()
argument_index: argument_index_counter, .ok_or(RequestParserError::UnexpectedEOF)?
expected_type: "Sort", .to_string(),
raw_input: s.to_string(),
})?,
); );
sort_or_window_or_position = parts.next(); sort_or_window_or_position = parts.next();
} }
let mut window = None; let mut window = None;
if let Some("window") = sort_or_window_or_position { if let Some("window") = sort_or_window_or_position {
argument_index_counter += 1; let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
window = Some( window = Some(
w.parse() w.parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
); );
sort_or_window_or_position = parts.next(); sort_or_window_or_position = parts.next();
} }
let mut position = None; let mut position = None;
if let Some("position") = sort_or_window_or_position { if let Some("position") = sort_or_window_or_position {
argument_index_counter += 1; let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let p = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "position",
argument_index: argument_index_counter,
})?;
position = Some( position = Some(
p.parse() p.parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?,
argument_index: argument_index_counter,
expected_type: "SongPosition",
raw_input: p.to_string(),
})?,
); );
} }
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(SearchAddRequest { Ok((Request::SearchAdd(filter, sort, window, position), ""))
filter, }
sort,
window, fn parse_response(
position, parts: ResponseAttributes<'_>,
}) ) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(SearchAdd);
impl Command for SearchAdd {
type Request = SearchAddRequest;
type Response = SearchAddResponse;
}

View File

@@ -1,157 +1,68 @@
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, commands::{
filter::Filter, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError,
types::{PlaylistName, SongPosition, Sort, WindowRange}, },
filter::parse_filter,
}; };
pub struct SearchAddPl; pub struct SearchAddPl;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] impl Command for SearchAddPl {
pub struct SearchAddPlRequest { type Response = ();
playlist_name: PlaylistName,
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
}
impl CommandRequest for SearchAddPlRequest {
const COMMAND: &'static str = "searchaddpl"; const COMMAND: &'static str = "searchaddpl";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = Some(5);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::SearchAddPl(
self.playlist_name,
self.filter,
self.sort,
self.window,
self.position,
)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SearchAddPl(playlist_name, filter, sort, window, position) => {
Some(SearchAddPlRequest {
playlist_name,
filter,
sort,
window,
position,
})
}
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {} {}", Self::COMMAND, self.playlist_name, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
if let Some(position) = &self.position {
cmd.push_str(&format!(" position {}", position));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let playlist_name = parts let playlist_name = parts
.next() .next()
.ok_or(Self::missing_arguments_error(0))? .ok_or(RequestParserError::UnexpectedEOF)?
.to_string(); .to_string();
let filter = match parts.next() { let filter = parse_filter(&mut parts)?;
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(1)),
};
let mut argument_index_counter = 1;
let mut sort_or_window_or_position = parts.next(); let mut sort_or_window_or_position = parts.next();
let mut sort = None; let mut sort = None;
if let Some("sort") = sort_or_window_or_position { if let Some("sort") = sort_or_window_or_position {
argument_index_counter += 1;
let s = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "sort",
argument_index: argument_index_counter,
})?;
sort = Some( sort = Some(
s.parse() parts
.map_err(|_| RequestParserError::SubtypeParserError { .next()
argument_index: argument_index_counter, .ok_or(RequestParserError::UnexpectedEOF)?
expected_type: "Sort", .to_string(),
raw_input: s.to_string(),
})?,
); );
sort_or_window_or_position = parts.next(); sort_or_window_or_position = parts.next();
} }
let mut window = None; let mut window = None;
if let Some("window") = sort_or_window_or_position { if let Some("window") = sort_or_window_or_position {
argument_index_counter += 1; let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
window = Some( window = Some(
w.parse() w.parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
); );
sort_or_window_or_position = parts.next(); sort_or_window_or_position = parts.next();
} }
let mut position = None; let mut position = None;
if let Some("position") = sort_or_window_or_position { if let Some("position") = sort_or_window_or_position {
argument_index_counter += 1; let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let p = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "position",
argument_index: argument_index_counter,
})?;
position = Some( position = Some(
p.parse() p.parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?,
argument_index: argument_index_counter,
expected_type: "SongPosition",
raw_input: p.to_string(),
})?,
); );
} }
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(SearchAddPlRequest { Ok((
playlist_name, Request::SearchAddPl(playlist_name, filter, sort, window, position),
filter, "",
sort, ))
window, }
position,
}) fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(SearchAddPl);
impl Command for SearchAddPl {
type Request = SearchAddPlRequest;
type Response = SearchAddPlResponse;
}

View File

@@ -1,101 +1,47 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError}, commands::{
filter::Filter, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError, get_and_parse_property,
response_tokenizer::{ResponseAttributes, get_and_parse_property}, },
types::{GroupType, Seconds}, filter::parse_filter,
}; };
pub struct SearchCount; pub struct SearchCount;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SearchCountResponse {
pub struct SearchCountRequest { pub songs: usize,
filter: Filter, pub playtime: u64,
group: Option<GroupType>,
} }
impl CommandRequest for SearchCountRequest { impl Command for SearchCount {
type Response = SearchCountResponse;
const COMMAND: &'static str = "searchcount"; const COMMAND: &'static str = "searchcount";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::SearchCount(self.filter, self.group) let filter = parse_filter(&mut parts)?;
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SearchCount(filter, group) => {
Some(SearchCountRequest { filter, group })
}
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(group) = &self.group {
cmd.push_str(&format!(" group {}", group));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let group = if let Some("group") = parts.next() { let group = if let Some("group") = parts.next() {
let group = parts let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "group",
argument_index: 1,
})?;
Some( Some(
group group
.parse() .parse()
.map_err(|_| RequestParserError::SubtypeParserError { .map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?,
argument_index: 1,
expected_type: "Group",
raw_input: group.to_string(),
})?,
) )
} else { } else {
None None
}; };
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(SearchCountRequest { filter, group }) Ok((Request::SearchCount(filter, group), ""))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchCountResponse {
pub songs: usize,
pub playtime: Seconds,
}
impl CommandResponse for SearchCountResponse {
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
} }
fn into_response_enum(self) -> crate::Response { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let songs = get_and_parse_property!(parts, "songs", Text); let songs = get_and_parse_property!(parts, "songs", Text);
let playtime = get_and_parse_property!(parts, "playtime", Text); let playtime = get_and_parse_property!(parts, "playtime", Text);
@@ -103,8 +49,3 @@ impl CommandResponse for SearchCountResponse {
Ok(SearchCountResponse { songs, playtime }) Ok(SearchCountResponse { songs, playtime })
} }
} }
impl Command for SearchCount {
type Request = SearchCountRequest;
type Response = SearchCountResponse;
}

View File

@@ -1,43 +1,35 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
use crate::{ get_and_parse_property,
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::{ResponseAttributes, get_and_parse_property},
types::Uri,
}; };
pub struct Update; pub struct Update;
single_optional_item_command_request!(Update, "update", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UpdateResponse { pub struct UpdateResponse {
updating_db: usize, updating_db: usize,
} }
impl CommandResponse for UpdateResponse { impl Command for Update {
fn into_response_enum(self) -> crate::Response { type Response = UpdateResponse;
todo!() const COMMAND: &'static str = "update";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let uri = parts.next().map(|s| s.to_string());
debug_assert!(parts.next().is_none());
Ok((Request::Update(uri), ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let updating_db = get_and_parse_property!(parts, "updating_db", Text); let updating_db = get_and_parse_property!(parts, "updating_db", Text);
Ok(UpdateResponse { updating_db }) Ok(UpdateResponse { updating_db })
} }
} }
impl Command for Update {
type Request = UpdateRequest;
type Response = UpdateResponse;
}

View File

@@ -1,11 +1,11 @@
mod delpartition; pub mod delpartition;
mod listpartitions; pub mod listpartitions;
mod moveoutput; pub mod moveoutput;
mod newpartition; pub mod newpartition;
mod partition; pub mod partition;
pub use delpartition::*; pub use delpartition::DelPartition;
pub use listpartitions::*; pub use listpartitions::ListPartitions;
pub use moveoutput::*; pub use moveoutput::MoveOutput;
pub use newpartition::*; pub use newpartition::NewPartition;
pub use partition::*; pub use partition::Partition;

View File

@@ -1,15 +1,29 @@
use crate::{ use crate::commands::{
commands::{Command, empty_command_response, single_item_command_request}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::PartitionName, ResponseParserError,
}; };
pub struct DelPartition; pub struct DelPartition;
single_item_command_request!(DelPartition, "delpartition", PartitionName);
empty_command_response!(DelPartition);
impl Command for DelPartition { impl Command for DelPartition {
type Request = DelPartitionRequest; type Response = ();
type Response = DelPartitionResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,16 +1,33 @@
use crate::{ use crate::commands::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response}, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
response_tokenizer::expect_property_type, expect_property_type,
types::PartitionName,
}; };
pub struct ListPartitions; pub struct ListPartitions;
empty_command_request!(ListPartitions, "listpartitions"); pub type ListPartitionsResponse = Vec<String>;
multi_item_command_response!(ListPartitions, "partition", PartitionName);
impl Command for ListPartitions { impl Command for ListPartitions {
type Request = ListPartitionsRequest;
type Response = ListPartitionsResponse; 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<Self::Response, ResponseParserError> {
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 = expect_property_type!(Some(value), "partition", Text).to_string();
partitions.push(partition);
}
Ok(partitions)
}
} }

View File

@@ -1,12 +1,29 @@
use crate::commands::{Command, empty_command_response, single_item_command_request}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct MoveOutput; pub struct MoveOutput;
single_item_command_request!(MoveOutput, "moveoutput", String);
empty_command_response!(MoveOutput);
impl Command for MoveOutput { impl Command for MoveOutput {
type Request = MoveOutputRequest; type Response = ();
type Response = MoveOutputResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,29 @@
use crate::{ use crate::commands::{
commands::{Command, empty_command_response, single_item_command_request}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::PartitionName, ResponseParserError,
}; };
pub struct NewPartition; pub struct NewPartition;
single_item_command_request!(NewPartition, "newpartition", PartitionName);
empty_command_response!(NewPartition);
impl Command for NewPartition { impl Command for NewPartition {
type Request = NewPartitionRequest; type Response = ();
type Response = NewPartitionResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,29 @@
use crate::{ use crate::commands::{
commands::{Command, empty_command_response, single_item_command_request}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
types::PartitionName, ResponseParserError,
}; };
pub struct Partition; pub struct Partition;
single_item_command_request!(Partition, "partition", PartitionName);
empty_command_response!(Partition);
impl Command for Partition { impl Command for Partition {
type Request = PartitionRequest; type Response = ();
type Response = PartitionResponse; 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<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,25 +1,25 @@
mod consume; pub mod consume;
mod crossfade; pub mod crossfade;
mod getvol; pub mod getvol;
mod mixrampdb; pub mod mixrampdb;
mod mixrampdelay; pub mod mixrampdelay;
mod random; pub mod random;
mod repeat; pub mod repeat;
mod replay_gain_mode; pub mod replay_gain_mode;
mod replay_gain_status; pub mod replay_gain_status;
mod setvol; pub mod setvol;
mod single; pub mod single;
mod volume; pub mod volume;
pub use consume::*; pub use consume::Consume;
pub use crossfade::*; pub use crossfade::Crossfade;
pub use getvol::*; pub use getvol::GetVol;
pub use mixrampdb::*; pub use mixrampdb::MixRampDb;
pub use mixrampdelay::*; pub use mixrampdelay::MixRampDelay;
pub use random::*; pub use random::Random;
pub use repeat::*; pub use repeat::Repeat;
pub use replay_gain_mode::*; pub use replay_gain_mode::ReplayGainMode;
pub use replay_gain_status::*; pub use replay_gain_status::ReplayGainStatus;
pub use setvol::*; pub use setvol::SetVol;
pub use single::*; pub use single::Single;
pub use volume::*; pub use volume::Volume;

View File

@@ -1,15 +1,32 @@
use crate::{ use std::str::FromStr;
commands::{Command, empty_command_response, single_item_command_request},
types::BoolOrOneshot, use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
}; };
pub struct Consume; pub struct Consume;
single_item_command_request!(Consume, "consume", BoolOrOneshot);
empty_command_response!(Consume);
impl Command for Consume { impl Command for Consume {
type Request = ConsumeRequest; type Response = ();
type Response = ConsumeResponse; const COMMAND: &'static str = "consume";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let state = match parts.next() {
Some(s) => crate::common::BoolOrOneshot::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Consume(state), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,34 @@
use crate::{ use crate::{
commands::{Command, empty_command_response, single_item_command_request}, commands::{
types::Seconds, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::Seconds,
}; };
pub struct Crossfade; pub struct Crossfade;
single_item_command_request!(Crossfade, "crossfade", Seconds);
empty_command_response!(Crossfade);
impl Command for Crossfade { impl Command for Crossfade {
type Request = CrossfadeRequest; type Response = ();
type Response = CrossfadeResponse; const COMMAND: &'static str = "crossfade";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let seconds = match parts.next() {
Some(s) => s
.parse::<Seconds>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Crossfade(seconds), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,30 @@
use std::collections::HashMap;
use crate::{ use crate::{
commands::{Command, empty_command_request, single_item_command_response}, commands::{
types::VolumeValue, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
get_and_parse_property,
},
common::VolumeValue,
}; };
pub struct GetVol; pub struct GetVol;
empty_command_request!(GetVol, "getvol");
single_item_command_response!(GetVol, "volume", VolumeValue);
impl Command for GetVol { impl Command for GetVol {
type Request = GetVolRequest; type Response = VolumeValue;
type Response = GetVolResponse; const COMMAND: &'static str = "getvol";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::GetVol, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
assert_eq!(parts.len(), 1);
let volume = get_and_parse_property!(parts, "volume", Text);
Ok(volume)
}
} }

View File

@@ -1,12 +1,31 @@
use crate::commands::{Command, empty_command_response, single_item_command_request}; use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct MixRampDb; pub struct MixRampDb;
single_item_command_request!(MixRampDb, "mixrampdb", f32);
empty_command_response!(MixRampDb);
impl Command for MixRampDb { impl Command for MixRampDb {
type Request = MixRampDbRequest; type Response = ();
type Response = MixRampDbResponse; const COMMAND: &'static str = "mixrampdb";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let db = match parts.next() {
Some(s) => s
.parse::<f32>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::MixRampDb(db), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,14 +1,34 @@
use crate::{ use crate::{
commands::{Command, empty_command_response, single_item_command_request}, commands::{
types::Seconds, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::Seconds,
}; };
pub struct MixRampDelay; pub struct MixRampDelay;
single_item_command_request!(MixRampDelay, "mixrampdelay", Seconds);
empty_command_response!(MixRampDelay);
impl Command for MixRampDelay { impl Command for MixRampDelay {
type Request = MixRampDelayRequest; type Response = ();
type Response = MixRampDelayResponse; const COMMAND: &'static str = "mixrampdelay";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let seconds = match parts.next() {
Some(s) => s
.parse::<Seconds>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::MixRampDelay(seconds), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,56 +1,31 @@
use crate::{ use crate::commands::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError,
}; };
pub struct Random; pub struct Random;
pub struct RandomRequest(bool); impl Command for Random {
type Response = ();
impl CommandRequest for RandomRequest {
const COMMAND: &'static str = "random"; const COMMAND: &'static str = "random";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(1);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::Random(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Random(state) => Some(RandomRequest(state)),
_ => None,
}
}
fn serialize(&self) -> String {
let state = if self.0 { "1" } else { "0" };
format!("{} {}\n", Self::COMMAND, state)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let state = match parts.next() { let state = match parts.next() {
Some("0") => false, Some("0") => false,
Some("1") => true, Some("1") => true,
Some(s) => { Some(s) => return Err(RequestParserError::SyntaxError(0, s.to_owned())),
return Err(RequestParserError::SubtypeParserError { None => return Err(RequestParserError::UnexpectedEOF),
argument_index: 0,
expected_type: "bool",
raw_input: s.to_owned(),
});
}
None => return Err(Self::missing_arguments_error(0)),
}; };
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(RandomRequest(state)) Ok((Request::Random(state), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(Random);
impl Command for Random {
type Request = RandomRequest;
type Response = RandomResponse;
}

View File

@@ -1,56 +1,31 @@
use crate::{ use crate::commands::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response}, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
request_tokenizer::RequestTokenizer, ResponseParserError,
}; };
pub struct Repeat; pub struct Repeat;
pub struct RepeatRequest(bool); impl Command for Repeat {
type Response = ();
impl CommandRequest for RepeatRequest {
const COMMAND: &'static str = "repeat"; const COMMAND: &'static str = "repeat";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(1);
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::Repeat(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Repeat(state) => Some(RepeatRequest(state)),
_ => None,
}
}
fn serialize(&self) -> String {
let state = if self.0 { "1" } else { "0" };
format!("{} {}\n", Self::COMMAND, state)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let state = match parts.next() { let state = match parts.next() {
Some("0") => false, Some("0") => false,
Some("1") => true, Some("1") => true,
Some(s) => { Some(s) => return Err(RequestParserError::SyntaxError(0, s.to_owned())),
return Err(RequestParserError::SubtypeParserError { None => return Err(RequestParserError::UnexpectedEOF),
argument_index: 0,
expected_type: "bool",
raw_input: s.to_owned(),
});
}
None => return Err(Self::missing_arguments_error(0)),
}; };
Self::throw_if_too_many_arguments(parts)?; debug_assert!(parts.next().is_none());
Ok(RepeatRequest(state)) Ok((Request::Repeat(state), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(Repeat);
impl Command for Repeat {
type Request = RepeatRequest;
type Response = RepeatResponse;
}

View File

@@ -1,15 +1,35 @@
use std::str::FromStr;
use crate::{ use crate::{
commands::{Command, empty_command_response, single_item_command_request}, commands::{
types::ReplayGainModeMode, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::ReplayGainModeMode,
}; };
pub struct ReplayGainMode; pub struct ReplayGainMode;
single_item_command_request!(ReplayGainMode, "replay_gain_mode", ReplayGainModeMode);
empty_command_response!(ReplayGainMode);
impl Command for ReplayGainMode { impl Command for ReplayGainMode {
type Request = ReplayGainModeRequest; type Response = ();
type Response = ReplayGainModeResponse; const COMMAND: &'static str = "replay_gain_mode";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let mode = match parts.next() {
Some(s) => ReplayGainModeMode::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::ReplayGainMode(mode), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -3,45 +3,39 @@ use std::{collections::HashMap, str::FromStr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, commands::{
response_tokenizer::{ResponseAttributes, get_property}, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
types::ReplayGainModeMode, get_property,
},
common::ReplayGainModeMode,
}; };
pub struct ReplayGainStatus; pub struct ReplayGainStatus;
empty_command_request!(ReplayGainStatus, "replay_gain_status");
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReplayGainStatusResponse { pub struct ReplayGainStatusResponse {
pub replay_gain_mode: ReplayGainModeMode, pub replay_gain_mode: ReplayGainModeMode,
} }
impl CommandResponse for ReplayGainStatusResponse { impl Command for ReplayGainStatus {
fn into_response_enum(self) -> crate::Response { type Response = ReplayGainStatusResponse;
todo!() const COMMAND: &'static str = "replay_gain_status";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ReplayGainStatus, ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let replay_gain_mode = get_property!(parts, "replay_gain_mode", Text); let replay_gain_mode = get_property!(parts, "replay_gain_mode", Text);
Ok(ReplayGainStatusResponse { Ok(ReplayGainStatusResponse {
replay_gain_mode: ReplayGainModeMode::from_str(replay_gain_mode).map_err(|_| { replay_gain_mode: ReplayGainModeMode::from_str(replay_gain_mode).map_err(|_| {
ResponseParserError::InvalidProperty( ResponseParserError::InvalidProperty("replay_gain_mode", replay_gain_mode)
"replay_gain_mode".to_string(),
replay_gain_mode.to_string(),
)
})?, })?,
}) })
} }
} }
impl Command for ReplayGainStatus {
type Request = ReplayGainStatusRequest;
type Response = ReplayGainStatusResponse;
}

View File

@@ -1,15 +1,35 @@
use std::str::FromStr;
use crate::{ use crate::{
commands::{Command, empty_command_response, single_item_command_request}, commands::{
types::VolumeValue, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::VolumeValue,
}; };
pub struct SetVol; pub struct SetVol;
single_item_command_request!(SetVol, "setvol", VolumeValue);
empty_command_response!(SetVol);
impl Command for SetVol { impl Command for SetVol {
type Request = SetVolRequest; type Response = ();
type Response = SetVolResponse; const COMMAND: &'static str = "setvol";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let volume = match parts.next() {
Some(s) => VolumeValue::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::SetVol(volume), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,32 @@
use crate::{ use std::str::FromStr;
commands::{Command, empty_command_response, single_item_command_request},
types::BoolOrOneshot, use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
}; };
pub struct Single; pub struct Single;
single_item_command_request!(Single, "single", BoolOrOneshot);
empty_command_response!(Single);
impl Command for Single { impl Command for Single {
type Request = SingleRequest; type Response = ();
type Response = SingleResponse; const COMMAND: &'static str = "single";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let state = match parts.next() {
Some(s) => crate::common::BoolOrOneshot::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Single(state), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,15 +1,35 @@
use std::str::FromStr;
use crate::{ use crate::{
commands::{Command, empty_command_response, single_item_command_request}, commands::{
types::VolumeValue, Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::VolumeValue,
}; };
pub struct Volume; pub struct Volume;
single_item_command_request!(Volume, "volume", VolumeValue);
empty_command_response!(Volume);
impl Command for Volume { impl Command for Volume {
type Request = VolumeRequest; type Response = ();
type Response = VolumeResponse; const COMMAND: &'static str = "volume";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let change = match parts.next() {
Some(s) => VolumeValue::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Volume(change), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,11 +1,11 @@
mod clearerror; pub mod clearerror;
mod currentsong; pub mod currentsong;
mod idle; pub mod idle;
mod stats; pub mod stats;
mod status; pub mod status;
pub use clearerror::*; pub use clearerror::ClearError;
pub use currentsong::*; pub use currentsong::CurrentSong;
pub use idle::*; pub use idle::Idle;
pub use stats::*; pub use stats::Stats;
pub use status::*; pub use status::Status;

View File

@@ -1,13 +1,24 @@
use crate::commands::{Command, empty_command_request, empty_command_response}; 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) /// Clears the current error message in status (this is also accomplished by any command that starts playback)
pub struct ClearError; pub struct ClearError;
empty_command_request!(ClearError, "clearerror");
empty_command_response!(ClearError);
impl Command for ClearError { impl Command for ClearError {
type Request = ClearErrorRequest; type Response = ();
type Response = ClearErrorResponse; const COMMAND: &'static str = "clearerror";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ClearError, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
} }

View File

@@ -1,60 +1,28 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::commands::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
response_tokenizer::{
ResponseAttributes, get_and_parse_optional_property, get_and_parse_property,
},
types::{DbSongInfo, Priority, SongId, SongPosition},
}; };
/// Displays the song info of the current song (same song that is identified in status) /// Displays the song info of the current song (same song that is identified in status)
pub struct CurrentSong; pub struct CurrentSong;
empty_command_request!(CurrentSong, "currentsong"); #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CurrentSongResponse {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CurrentSongResponse {
position: SongPosition,
id: SongId,
priority: Option<Priority>,
song_info: DbSongInfo,
}
impl CommandResponse for CurrentSongResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let mut parts: HashMap<_, _> = parts.into_map()?;
let position: SongPosition = get_and_parse_property!(parts, "Pos", Text);
let id: SongId = get_and_parse_property!(parts, "Id", Text);
let priority: Option<Priority> = get_and_parse_optional_property!(parts, "Prio", Text);
parts.remove("Pos");
parts.remove("Id");
parts.remove("Prio");
let song_info = DbSongInfo::parse_map(parts)?;
Ok(CurrentSongResponse {
position,
id,
priority,
song_info,
})
}
}
impl Command for CurrentSong { impl Command for CurrentSong {
type Request = CurrentSongRequest;
type Response = CurrentSongResponse; type Response = CurrentSongResponse;
const COMMAND: &'static str = "currentsong";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::CurrentSong, ""))
}
fn parse_response(
_parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
} }

View File

@@ -1,62 +1,37 @@
use std::str::FromStr; use std::str::{FromStr, SplitWhitespace};
use crate::{ use crate::common::SubSystem;
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer, use crate::commands::{
types::SubSystem, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
}; };
pub struct Idle; pub struct Idle;
pub struct IdleRequest(Option<Vec<SubSystem>>); impl Command for Idle {
type Response = ();
impl CommandRequest for IdleRequest {
const COMMAND: &'static str = "idle"; const COMMAND: &'static str = "idle";
const MIN_ARGS: u32 = 0;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request { fn parse_request(mut parts: SplitWhitespace<'_>) -> RequestParserResult<'_> {
crate::Request::Idle(self.0) let result = parts
} .next()
.map_or(Ok((Request::Idle(None), "")), |subsystems| {
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Idle(subsystems) => Some(IdleRequest(subsystems)),
_ => None,
}
}
fn serialize(&self) -> String {
match &self.0 {
Some(subsystems) => {
let subsystems_str = subsystems
.iter()
.map(|subsystem| subsystem.to_string())
.collect::<Vec<_>>()
.join(",");
format!("{} {}\n", Self::COMMAND, subsystems_str)
}
None => Self::COMMAND.to_string() + "\n",
}
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts;
let result = parts.next().map_or(Ok(None), |subsystems| {
let subsystems = subsystems let subsystems = subsystems
.split(',') .split(',')
.map(|subsystem| SubSystem::from_str(subsystem).unwrap()) .map(|subsystem| SubSystem::from_str(subsystem).unwrap())
.collect(); .collect();
Ok(Some(subsystems)) Ok((Request::Idle(Some(subsystems)), ""))
}); });
result.map(IdleRequest) debug_assert!(parts.next().is_none());
result
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
} }
} }
empty_command_response!(Idle);
impl Command for Idle {
type Request = IdleRequest;
type Response = IdleResponse;
}

View File

@@ -2,17 +2,13 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::commands::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request}, Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
response_tokenizer::{ get_and_parse_optional_property, get_and_parse_property,
ResponseAttributes, get_and_parse_optional_property, get_and_parse_property,
},
}; };
pub struct Stats; pub struct Stats;
empty_command_request!(Stats, "stats");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatsResponse { pub struct StatsResponse {
pub uptime: u64, pub uptime: u64,
@@ -24,17 +20,20 @@ pub struct StatsResponse {
pub db_update: Option<u64>, pub db_update: Option<u64>,
} }
impl CommandResponse for StatsResponse { impl Command for Stats {
fn into_response_enum(self) -> crate::Response { type Response = StatsResponse;
todo!() const COMMAND: &'static str = "stats";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Stats, ""))
} }
fn from_response_enum(response: crate::Response) -> Option<Self> { fn parse_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let uptime = get_and_parse_property!(parts, "uptime", Text); let uptime = get_and_parse_property!(parts, "uptime", Text);
let playtime = get_and_parse_property!(parts, "playtime", Text); let playtime = get_and_parse_property!(parts, "playtime", Text);
@@ -55,8 +54,3 @@ impl CommandResponse for StatsResponse {
}) })
} }
} }
impl Command for Stats {
type Request = StatsRequest;
type Response = StatsResponse;
}

View File

@@ -3,19 +3,14 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::common::{Audio, BoolOrOneshot, SongId, SongPosition};
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{ use crate::commands::{
ResponseAttributes, get_and_parse_optional_property, get_and_parse_property, Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
ResponseParserError, get_and_parse_optional_property, get_and_parse_property,
get_optional_property, get_property, get_optional_property, get_property,
},
types::{Audio, BoolOrOneshot, SongId, SongPosition},
}; };
pub struct Status;
empty_command_request!(Status, "status");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum StatusResponseState { pub enum StatusResponseState {
Play, Play,
@@ -65,42 +60,32 @@ pub struct StatusResponse {
pub last_loaded_playlist: Option<String>, pub last_loaded_playlist: Option<String>,
} }
impl CommandResponse for StatusResponse { #[inline]
fn into_response_enum(self) -> crate::Response { fn parse_status_response(
todo!() parts: ResponseAttributes<'_>,
} ) -> Result<StatusResponse, ResponseParserError> {
let parts: HashMap<&str, GenericResponseValue> = parts.into();
fn from_response_enum(response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
let partition = get_property!(parts, "partition", Text).to_string(); let partition = get_property!(parts, "partition", Text).to_string();
let volume = match get_property!(parts, "volume", Text) { let volume = match get_property!(parts, "volume", Text) {
"-1" => None, "-1" => None,
volume => Some(volume.parse().map_err(|_| { volume => Some(
ResponseParserError::InvalidProperty("volume".to_string(), volume.to_string()) volume
})?), .parse()
.map_err(|_| ResponseParserError::InvalidProperty("volume", volume))?,
),
}; };
let repeat = match get_property!(parts, "repeat", Text) { let repeat = match get_property!(parts, "repeat", Text) {
"0" => Ok(false), "0" => Ok(false),
"1" => Ok(true), "1" => Ok(true),
repeat => Err(ResponseParserError::InvalidProperty( repeat => Err(ResponseParserError::InvalidProperty("repeat", repeat)),
"repeat".to_string(),
repeat.to_string(),
)),
}?; }?;
let random = match get_property!(parts, "random", Text) { let random = match get_property!(parts, "random", Text) {
"0" => Ok(false), "0" => Ok(false),
"1" => Ok(true), "1" => Ok(true),
random => Err(ResponseParserError::InvalidProperty( random => Err(ResponseParserError::InvalidProperty("random", random)),
"random".to_string(),
random.to_string(),
)),
}?; }?;
let single = get_and_parse_property!(parts, "single", Text); let single = get_and_parse_property!(parts, "single", Text);
@@ -110,28 +95,22 @@ impl CommandResponse for StatusResponse {
let state: StatusResponseState = get_and_parse_property!(parts, "state", Text); let state: StatusResponseState = get_and_parse_property!(parts, "state", Text);
let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text); let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text);
let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text); let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text);
let next_song: Option<SongPosition> = let next_song: Option<SongPosition> = get_and_parse_optional_property!(parts, "nextsong", Text);
get_and_parse_optional_property!(parts, "nextsong", Text); let next_song_id: Option<SongId> = get_and_parse_optional_property!(parts, "nextsongid", Text);
let next_song_id: Option<SongId> =
get_and_parse_optional_property!(parts, "nextsongid", Text);
let time = match get_optional_property!(parts, "time", Text) { let time = match get_optional_property!(parts, "time", Text) {
Some(time) => { Some(time) => {
let mut parts = time.split(':'); let mut parts = time.split(':');
let elapsed = parts let elapsed = parts
.next() .next()
.ok_or(ResponseParserError::SyntaxError(0, time.to_string()))? .ok_or(ResponseParserError::SyntaxError(0, time))?
.parse() .parse()
.map_err(|_| { .map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
ResponseParserError::InvalidProperty("time".to_string(), time.to_string())
})?;
let duration = parts let duration = parts
.next() .next()
.ok_or(ResponseParserError::SyntaxError(0, time.to_string()))? .ok_or(ResponseParserError::SyntaxError(0, time))?
.parse() .parse()
.map_err(|_| { .map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
ResponseParserError::InvalidProperty("time".to_string(), time.to_string())
})?;
Some((elapsed, duration)) Some((elapsed, duration))
} }
None => None, None => None,
@@ -175,12 +154,25 @@ impl CommandResponse for StatusResponse {
error, error,
last_loaded_playlist, last_loaded_playlist,
}) })
}
} }
pub struct Status;
impl Command for Status { impl Command for Status {
type Request = StatusRequest;
type Response = StatusResponse; type Response = StatusResponse;
const COMMAND: &'static str = "status";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Status, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
parse_status_response(parts)
}
} }
#[cfg(test)] #[cfg(test)]
@@ -215,7 +207,7 @@ mod tests {
"# }; "# };
assert_eq!( assert_eq!(
Status::parse_raw_response(contents.as_bytes()), Status::parse_raw_response(contents),
Ok(StatusResponse { Ok(StatusResponse {
partition: "default".into(), partition: "default".into(),
volume: Some(66), volume: Some(66),

Some files were not shown because too many files have changed in this diff Show More