Compare commits
94 Commits
deduplicat
...
add-constr
| Author | SHA1 | Date | |
|---|---|---|---|
|
291aa6877c
|
|||
|
c48a8adcdc
|
|||
|
1f1e7fbb53
|
|||
|
7ec268094d
|
|||
|
afa690366f
|
|||
|
02ba7f2684
|
|||
|
44903f2ce0
|
|||
|
9f50d61ad5
|
|||
|
788a01ce83
|
|||
|
f5451b6c2f
|
|||
|
2de18cdbdb
|
|||
|
23bfd0c4a7
|
|||
|
3342293bf2
|
|||
|
2b3ab7389d
|
|||
|
f80d36e962
|
|||
|
86edd4c5b3
|
|||
|
a7a8ceedeb
|
|||
|
7ce0d68021
|
|||
|
b5cb4677ee
|
|||
|
38faf99de7
|
|||
|
69f252bad8
|
|||
|
c177c089a3
|
|||
|
d964e7857b
|
|||
|
8430592bee
|
|||
|
78cfc09d60
|
|||
|
a7b764ad0f
|
|||
|
f3e6fe13df
|
|||
|
4f8f5db620
|
|||
|
1e39640508
|
|||
|
b388bc727b
|
|||
|
9f859e1df1
|
|||
|
4bb5702eba
|
|||
|
955fdbcff1
|
|||
|
322c8c8fc3
|
|||
|
93afaf1bde
|
|||
|
c915c67f08
|
|||
|
813ffac614
|
|||
|
b0bc2752cc
|
|||
|
07e9161137
|
|||
|
f2977f1ba9
|
|||
|
b0b4134829
|
|||
|
c1dbdd4644
|
|||
|
64c94d6e89
|
|||
|
4d11df5ad1
|
|||
|
0cacbe2229
|
|||
|
7bbe1c4ced
|
|||
|
83918fd432
|
|||
|
07e1c76aa9
|
|||
|
d84e653db2
|
|||
|
7834bbd956
|
|||
|
249ffb2a36
|
|||
|
fdd4880d05
|
|||
|
9ca6544057
|
|||
|
e5f70ca87a
|
|||
|
b03f60c985
|
|||
|
b22016e970
|
|||
|
65c7798d01
|
|||
|
5188809327
|
|||
|
a4276a2caa
|
|||
|
7b27a650a1
|
|||
|
062dbcafb8
|
|||
|
57a6b0a3ee
|
|||
|
b5fbaadca2
|
|||
|
3bd7aaaad2
|
|||
|
8aba3d8859
|
|||
|
e4ece7d6b2
|
|||
|
f07f90aee5
|
|||
|
6e2aa2ba65
|
|||
|
1ef8ef669a
|
|||
|
c6a123a6e1
|
|||
|
06e24f0ce0
|
|||
|
da31ab75e2
|
|||
|
d09ca013d5
|
|||
|
10913fd48c
|
|||
|
73ddb6d498
|
|||
|
ede28623ef
|
|||
|
130fe49597
|
|||
|
4a1df97ad6
|
|||
|
7a966051d5
|
|||
|
7dc3d7f9cf
|
|||
|
2b1e99445a
|
|||
|
e932b62195
|
|||
|
49f440770e
|
|||
|
153ae9520f
|
|||
|
424c530d5d
|
|||
|
a637f24e66
|
|||
|
1d693b7b2a
|
|||
|
484b1fb68d
|
|||
|
2fa58533ba
|
|||
|
088665c9ff
|
|||
|
3e2e3fdc68
|
|||
|
59994ce740
|
|||
|
b42cad0b52
|
|||
|
3c49ece1a9
|
@@ -7,20 +7,20 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: debian-latest-slim
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- 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 --all-features --verbose --release
|
run: cargo build --verbose --release
|
||||||
|
|
||||||
check:
|
check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: debian-latest-slim
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install rust toolchain
|
- name: Install rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -31,12 +31,13 @@ jobs:
|
|||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
- name: Check clippy
|
- name: Check clippy
|
||||||
run: cargo clippy --all-features -- --deny warnings
|
# run: cargo clippy -- --deny warnings
|
||||||
|
run: cargo clippy
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: debian-latest-slim
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: cargo-bins/cargo-binstall@main
|
- uses: cargo-bins/cargo-binstall@main
|
||||||
|
|
||||||
- name: Install rust toolchain
|
- name: Install rust toolchain
|
||||||
@@ -49,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
cargo nextest run --all-features --release --no-fail-fast
|
cargo nextest run --release --no-fail-fast
|
||||||
env:
|
env:
|
||||||
RUST_LOG: "trace"
|
RUST_LOG: "trace"
|
||||||
RUSTFLAGS: "-Cinstrument-coverage"
|
RUSTFLAGS: "-Cinstrument-coverage"
|
||||||
@@ -83,13 +84,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: bekkalokk.pvv.ntnu.no
|
host: pages.pvv.ntnu.no
|
||||||
known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"
|
known-hosts: "pages.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2QjfFB+city1SYqltkVqWACfo1j37k+oQQfj13mtgg"
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: debian-latest-slim
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install latest nightly toolchain
|
- name: Install latest nightly toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -98,7 +99,7 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: cargo doc --all-features --document-private-items --release
|
run: cargo doc --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
|
||||||
@@ -107,6 +108,5 @@ 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: bekkalokk.pvv.ntnu.no
|
host: pages.pvv.ntnu.no
|
||||||
known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"
|
known-hosts: "pages.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2QjfFB+city1SYqltkVqWACfo1j37k+oQQfj13mtgg"
|
||||||
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
result
|
result
|
||||||
result-*
|
result-*
|
||||||
|
|
||||||
|
Cargo.lock
|
||||||
|
|||||||
95
Cargo.lock
generated
95
Cargo.lock
generated
@@ -1,95 +0,0 @@
|
|||||||
# 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"
|
|
||||||
20
Cargo.toml
20
Cargo.toml
@@ -11,8 +11,24 @@ edition = "2024"
|
|||||||
rust-version = "1.85.0"
|
rust-version = "1.85.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
chrono = { version = "0.4.42", features = ["serde"] }
|
||||||
|
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]
|
||||||
indoc = "2.0.5"
|
anyhow = "1.0.100"
|
||||||
|
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"
|
||||||
|
|||||||
8
build.rs
Normal file
8
build.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fn main() {
|
||||||
|
lalrpop::process_root().unwrap();
|
||||||
|
// let debug_mode = std::env::var("PROFILE").unwrap() == "debug";
|
||||||
|
// lalrpop::Configuration::new()
|
||||||
|
// .emit_comments(debug_mode)
|
||||||
|
// .process()
|
||||||
|
// .unwrap();
|
||||||
|
}
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
fn main() {
|
use empidee::MpdClient;
|
||||||
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
12
flake.lock
generated
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1746461020,
|
"lastModified": 1764950072,
|
||||||
"narHash": "sha256-7+pG1I9jvxNlmln4YgnlW4o+w0TZX24k688mibiFDUE=",
|
"narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3730d8a308f94996a9ba7c7138ede69c1b9ac4ae",
|
"rev": "f61125a668a320878494449750330ca58b78c557",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1746585402,
|
"lastModified": 1765075567,
|
||||||
"narHash": "sha256-Pf+ufu6bYNA1+KQKHnGMNEfTwpD9ZIcAeLoE2yPWIP0=",
|
"narHash": "sha256-KFDCdQcHJ0hE3Nt5Gm5enRIhmtEifAjpxgUQ3mzSJpA=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "72dd969389583664f87aa348b3458f2813693617",
|
"rev": "769156779b41e8787a46ca3d7d76443aaf68be6f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
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";
|
||||||
|
|||||||
105
src/client.rs
Normal file
105
src/client.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
//! 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
1143
src/commands.rs
1143
src/commands.rs
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
|||||||
pub mod disableoutput;
|
mod disableoutput;
|
||||||
pub mod enableoutput;
|
mod enableoutput;
|
||||||
pub mod outputs;
|
mod outputs;
|
||||||
pub mod outputset;
|
mod outputset;
|
||||||
pub mod toggleoutput;
|
mod toggleoutput;
|
||||||
|
|
||||||
pub use disableoutput::DisableOutput;
|
pub use disableoutput::*;
|
||||||
pub use enableoutput::EnableOutput;
|
pub use enableoutput::*;
|
||||||
pub use outputs::Outputs;
|
pub use outputs::*;
|
||||||
pub use outputset::OutputSet;
|
pub use outputset::*;
|
||||||
pub use toggleoutput::ToggleOutput;
|
pub use toggleoutput::*;
|
||||||
|
|||||||
@@ -1,26 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
ResponseParserError,
|
types::AudioOutputId,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = DisableOutputRequest;
|
||||||
const COMMAND: &'static str = "disableoutput";
|
type Response = DisableOutputResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
ResponseParserError,
|
types::AudioOutputId,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = EnableOutputRequest;
|
||||||
const COMMAND: &'static str = "enableoutput";
|
type Response = EnableOutputResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,35 +2,171 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
|
||||||
|
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: u64,
|
pub id: AudioOutputId,
|
||||||
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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type OutputsResponse = Vec<Output>;
|
impl CommandResponse for OutputsResponse {
|
||||||
|
fn into_response_enum(self) -> crate::Response {
|
||||||
impl Command for Outputs {
|
todo!()
|
||||||
type Response = OutputsResponse;
|
|
||||||
const COMMAND: &'static str = "outputs";
|
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
Ok((Request::Outputs, ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
_parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
}
|
||||||
unimplemented!()
|
|
||||||
|
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 {
|
||||||
|
type Request = OutputsRequest;
|
||||||
|
type Response = OutputsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_response() {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,87 @@
|
|||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
|
||||||
ResponseParserError,
|
use crate::{
|
||||||
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
|
request_tokenizer::RequestTokenizer,
|
||||||
|
types::AudioOutputId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct OutputSet;
|
pub struct OutputSet;
|
||||||
|
|
||||||
impl Command for OutputSet {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct OutputSetRequest {
|
||||||
const COMMAND: &'static str = "outputset";
|
pub output_id: AudioOutputId,
|
||||||
|
pub attribute_name: String,
|
||||||
|
pub attribute_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl OutputSetRequest {
|
||||||
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
fn new(output_id: AudioOutputId, attribute_name: String, attribute_value: String) -> Self {
|
||||||
let attribute_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
Self {
|
||||||
let attribute_value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
output_id,
|
||||||
|
attribute_name,
|
||||||
debug_assert!(parts.next().is_none());
|
attribute_value,
|
||||||
|
}
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
type Request = OutputSetRequest;
|
||||||
|
type Response = OutputSetResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
ResponseParserError,
|
types::AudioOutputId,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = ToggleOutputRequest;
|
||||||
const COMMAND: &'static str = "toggleoutput";
|
type Response = ToggleOutputResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
pub mod channels;
|
mod channels;
|
||||||
pub mod readmessages;
|
mod readmessages;
|
||||||
pub mod sendmessage;
|
mod sendmessage;
|
||||||
pub mod subscribe;
|
mod subscribe;
|
||||||
pub mod unsubscribe;
|
mod unsubscribe;
|
||||||
|
|
||||||
pub use channels::Channels;
|
pub use channels::*;
|
||||||
pub use readmessages::ReadMessages;
|
pub use readmessages::*;
|
||||||
pub use sendmessage::SendMessage;
|
pub use sendmessage::*;
|
||||||
pub use subscribe::Subscribe;
|
pub use subscribe::*;
|
||||||
pub use unsubscribe::Unsubscribe;
|
pub use unsubscribe::*;
|
||||||
|
|||||||
@@ -1,36 +1,39 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
|
||||||
expect_property_type,
|
response_tokenizer::{ResponseAttributes, 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<String>,
|
pub channels: Vec<ChannelName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command for Channels {
|
impl CommandResponse for ChannelsResponse {
|
||||||
type Response = ChannelsResponse;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "channels";
|
todo!()
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
|
|
||||||
Ok((Request::Channels, ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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);
|
||||||
channel_names.push(channel_name.to_string());
|
let channel_name = channel_name
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ResponseParserError::SyntaxError(0, channel_name.to_string()))?;
|
||||||
|
channel_names.push(channel_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ChannelsResponse {
|
Ok(ChannelsResponse {
|
||||||
@@ -39,6 +42,11 @@ impl Command for Channels {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for Channels {
|
||||||
|
type Request = ChannelsRequest;
|
||||||
|
type Response = ChannelsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -53,11 +61,15 @@ mod tests {
|
|||||||
channels: baz
|
channels: baz
|
||||||
OK
|
OK
|
||||||
"};
|
"};
|
||||||
let response = Channels::parse_raw_response(response).unwrap();
|
let response = Channels::parse_raw_response(response.as_bytes()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
response,
|
response,
|
||||||
ChannelsResponse {
|
ChannelsResponse {
|
||||||
channels: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
|
channels: vec![
|
||||||
|
"foo".parse().unwrap(),
|
||||||
|
"bar".parse().unwrap(),
|
||||||
|
"baz".parse().unwrap(),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
|
||||||
expect_property_type,
|
response_tokenizer::{ResponseAttributes, 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 {
|
pub struct ReadMessagesResponse(Vec<ReadMessagesResponseEntry>);
|
||||||
pub messages: Vec<(String, String)>,
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct ReadMessagesResponseEntry {
|
||||||
|
channel: ChannelName,
|
||||||
|
message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command for ReadMessages {
|
impl CommandResponse for ReadMessagesResponse {
|
||||||
type Response = ReadMessagesResponse;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "readmessages";
|
todo!()
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
|
|
||||||
Ok((Request::ReadMessages, ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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);
|
||||||
@@ -37,16 +41,25 @@ impl Command for ReadMessages {
|
|||||||
debug_assert!(ckey == "channel");
|
debug_assert!(ckey == "channel");
|
||||||
debug_assert!(mkey == "message");
|
debug_assert!(mkey == "message");
|
||||||
|
|
||||||
let channel = expect_property_type!(Some(cvalue), "channel", Text).to_string();
|
let channel = expect_property_type!(Some(cvalue), "channel", Text);
|
||||||
|
let channel = channel
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ResponseParserError::SyntaxError(0, channel.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((channel, message));
|
messages.push(ReadMessagesResponseEntry { 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;
|
||||||
@@ -62,15 +75,19 @@ mod tests {
|
|||||||
message: message2
|
message: message2
|
||||||
OK
|
OK
|
||||||
"};
|
"};
|
||||||
let result = ReadMessages::parse_raw_response(input);
|
let result = ReadMessages::parse_raw_response(input.as_bytes());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
Ok(ReadMessagesResponse {
|
Ok(ReadMessagesResponse(vec![
|
||||||
messages: vec![
|
ReadMessagesResponseEntry {
|
||||||
("channel1".to_string(), "message1".to_string()),
|
channel: "channel1".parse().unwrap(),
|
||||||
("channel2".to_string(), "message2".to_string()),
|
message: "message1".to_string(),
|
||||||
]
|
},
|
||||||
})
|
ReadMessagesResponseEntry {
|
||||||
|
channel: "channel2".parse().unwrap(),
|
||||||
|
message: "message2".to_string(),
|
||||||
|
},
|
||||||
|
]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,67 @@
|
|||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
|
||||||
ResponseParserError,
|
use crate::{
|
||||||
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
|
request_tokenizer::RequestTokenizer,
|
||||||
|
types::ChannelName,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SendMessage;
|
pub struct SendMessage;
|
||||||
|
|
||||||
impl Command for SendMessage {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct SendMessageRequest {
|
||||||
const COMMAND: &'static str = "sendmessage";
|
pub channel: ChannelName,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl SendMessageRequest {
|
||||||
let channel = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
fn new(channel: ChannelName, message: String) -> Self {
|
||||||
|
Self { channel, message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandRequest for SendMessageRequest {
|
||||||
|
const COMMAND: &'static str = "sendmessage";
|
||||||
|
const MIN_ARGS: u32 = 2;
|
||||||
|
const MAX_ARGS: Option<u32> = None;
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
crate::Request::SendMessage(self.channel, self.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(" ");
|
||||||
|
|
||||||
debug_assert!(!message.is_empty());
|
Ok(SendMessageRequest { channel, message })
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
ResponseParserError,
|
types::ChannelName,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = SubscribeRequest;
|
||||||
const COMMAND: &'static str = "subscribe";
|
type Response = SubscribeResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
ResponseParserError,
|
types::ChannelName,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = UnsubscribeRequest;
|
||||||
const COMMAND: &'static str = "unsubscribe";
|
type Response = UnsubscribeResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
pub mod binary_limit;
|
mod binary_limit;
|
||||||
pub mod close;
|
mod close;
|
||||||
pub mod kill;
|
mod kill;
|
||||||
pub mod password;
|
mod password;
|
||||||
pub mod ping;
|
mod ping;
|
||||||
pub mod protocol;
|
mod protocol;
|
||||||
pub mod protocol_all;
|
mod protocol_all;
|
||||||
pub mod protocol_available;
|
mod protocol_available;
|
||||||
pub mod protocol_clear;
|
mod protocol_clear;
|
||||||
pub mod protocol_disable;
|
mod protocol_disable;
|
||||||
pub mod protocol_enable;
|
mod protocol_enable;
|
||||||
pub mod tag_types;
|
mod tag_types;
|
||||||
pub mod tag_types_all;
|
mod tag_types_all;
|
||||||
pub mod tag_types_available;
|
mod tag_types_available;
|
||||||
pub mod tag_types_clear;
|
mod tag_types_clear;
|
||||||
pub mod tag_types_disable;
|
mod tag_types_disable;
|
||||||
pub mod tag_types_enable;
|
mod tag_types_enable;
|
||||||
pub mod tag_types_reset;
|
mod tag_types_reset;
|
||||||
|
|
||||||
pub use binary_limit::BinaryLimit;
|
pub use binary_limit::*;
|
||||||
pub use close::Close;
|
pub use close::*;
|
||||||
pub use kill::Kill;
|
pub use kill::*;
|
||||||
pub use password::Password;
|
pub use password::*;
|
||||||
pub use ping::Ping;
|
pub use ping::*;
|
||||||
pub use protocol::Protocol;
|
pub use protocol::*;
|
||||||
pub use protocol_all::ProtocolAll;
|
pub use protocol_all::*;
|
||||||
pub use protocol_available::ProtocolAvailable;
|
pub use protocol_available::*;
|
||||||
pub use protocol_clear::ProtocolClear;
|
pub use protocol_clear::*;
|
||||||
pub use protocol_disable::ProtocolDisable;
|
pub use protocol_disable::*;
|
||||||
pub use protocol_enable::ProtocolEnable;
|
pub use protocol_enable::*;
|
||||||
pub use tag_types::TagTypes;
|
pub use tag_types::*;
|
||||||
pub use tag_types_all::TagTypesAll;
|
pub use tag_types_all::*;
|
||||||
pub use tag_types_available::TagTypesAvailable;
|
pub use tag_types_available::*;
|
||||||
pub use tag_types_clear::TagTypesClear;
|
pub use tag_types_clear::*;
|
||||||
pub use tag_types_disable::TagTypesDisable;
|
pub use tag_types_disable::*;
|
||||||
pub use tag_types_enable::TagTypesEnable;
|
pub use tag_types_enable::*;
|
||||||
pub use tag_types_reset::TagTypesReset;
|
pub use tag_types_reset::*;
|
||||||
|
|||||||
@@ -1,27 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_response, single_item_command_request};
|
||||||
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 Response = ();
|
type Request = BinaryLimitRequest;
|
||||||
const COMMAND: &'static str = "binarylimit";
|
type Response = BinaryLimitResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = CloseRequest;
|
||||||
const COMMAND: &'static str = "close";
|
type Response = CloseResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = KillRequest;
|
||||||
const COMMAND: &'static str = "kill";
|
type Response = KillResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_response, single_item_command_request};
|
||||||
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 Response = ();
|
type Request = PasswordRequest;
|
||||||
const COMMAND: &'static str = "password";
|
type Response = PasswordResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = PingRequest;
|
||||||
const COMMAND: &'static str = "ping";
|
type Response = PingResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
|
||||||
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
|
response_tokenizer::expect_property_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = ProtocolRequest;
|
||||||
const COMMAND: &'static str = "protocol";
|
type Response = ProtocolResponse;
|
||||||
|
|
||||||
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!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
use crate::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = ProtocolAllRequest;
|
||||||
const COMMAND: &'static str = "protocol all";
|
type Response = ProtocolAllResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
|
||||||
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
|
response_tokenizer::expect_property_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = ProtocolAvailableRequest;
|
||||||
const COMMAND: &'static str = "protocol available";
|
type Response = ProtocolAvailableResponse;
|
||||||
|
|
||||||
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!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
use crate::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = ProtocolClearRequest;
|
||||||
const COMMAND: &'static str = "protocol clear";
|
type Response = ProtocolClearResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,70 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
commands::{
|
request_tokenizer::RequestTokenizer,
|
||||||
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
|
types::Feature,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ProtocolDisable;
|
pub struct ProtocolDisable;
|
||||||
|
|
||||||
impl Command for ProtocolDisable {
|
pub struct ProtocolDisableRequest(Vec<Feature>);
|
||||||
type Response = ();
|
|
||||||
const COMMAND: &'static str = "protocol disable";
|
|
||||||
|
|
||||||
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl ProtocolDisableRequest {
|
||||||
let mut parts = parts.peekable();
|
fn new(features: Vec<Feature>) -> Self {
|
||||||
if parts.peek().is_none() {
|
Self(features)
|
||||||
return Err(RequestParserError::UnexpectedEOF);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: verify that the features are split by whitespace
|
|
||||||
let mut features = Vec::new();
|
|
||||||
for feature in parts {
|
|
||||||
features.push(feature.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((Request::ProtocolDisable(features), ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_response(
|
|
||||||
parts: ResponseAttributes<'_>,
|
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
|
||||||
debug_assert!(parts.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CommandRequest for ProtocolDisableRequest {
|
||||||
|
const COMMAND: &'static str = "protocol disable";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = None;
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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();
|
||||||
|
if parts.peek().is_none() {
|
||||||
|
return Err(Self::missing_arguments_error(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let features = parts
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, f)| {
|
||||||
|
f.parse()
|
||||||
|
.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_command_response!(ProtocolDisable);
|
||||||
|
|
||||||
|
impl Command for ProtocolDisable {
|
||||||
|
type Request = ProtocolDisableRequest;
|
||||||
|
type Response = ProtocolDisableResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,70 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
commands::{
|
request_tokenizer::RequestTokenizer,
|
||||||
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
|
types::Feature,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ProtocolEnable;
|
pub struct ProtocolEnable;
|
||||||
|
|
||||||
impl Command for ProtocolEnable {
|
pub struct ProtocolEnableRequest(Vec<Feature>);
|
||||||
type Response = ();
|
|
||||||
const COMMAND: &'static str = "protocol enable";
|
|
||||||
|
|
||||||
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl ProtocolEnableRequest {
|
||||||
let mut parts = parts.peekable();
|
fn new(features: Vec<Feature>) -> Self {
|
||||||
if parts.peek().is_none() {
|
Self(features)
|
||||||
return Err(RequestParserError::UnexpectedEOF);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: verify that the features are split by whitespace
|
|
||||||
let mut features = Vec::new();
|
|
||||||
for feature in parts {
|
|
||||||
features.push(feature.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((Request::ProtocolEnable(features), ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_response(
|
|
||||||
parts: ResponseAttributes<'_>,
|
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
|
||||||
debug_assert!(parts.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CommandRequest for ProtocolEnableRequest {
|
||||||
|
const COMMAND: &'static str = "protocol enable";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = None;
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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();
|
||||||
|
if parts.peek().is_none() {
|
||||||
|
return Err(Self::missing_arguments_error(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let features = parts
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, f)| {
|
||||||
|
f.parse()
|
||||||
|
.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_command_response!(ProtocolEnable);
|
||||||
|
|
||||||
|
impl Command for ProtocolEnable {
|
||||||
|
type Request = ProtocolEnableRequest;
|
||||||
|
type Response = ProtocolEnableResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,42 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
|
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
|
||||||
ResponseParserError,
|
response_tokenizer::expect_property_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TagTypes;
|
pub struct TagTypes;
|
||||||
|
|
||||||
pub type TagTypesResponse = Vec<String>;
|
empty_command_request!(TagTypes, "tagtypes");
|
||||||
|
|
||||||
|
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 = match value {
|
|
||||||
GenericResponseValue::Text(name) => name.to_string(),
|
|
||||||
GenericResponseValue::Binary(_) => {
|
|
||||||
return Err(ResponseParserError::UnexpectedPropertyType(
|
|
||||||
"tagtype", "Binary",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tagtypes.push(tagtype);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tagtypes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
use crate::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = TagTypesAllRequest;
|
||||||
const COMMAND: &'static str = "tagtypes all";
|
type Response = TagTypesAllResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
|
||||||
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
|
response_tokenizer::expect_property_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = TagTypesAvailableRequest;
|
||||||
const COMMAND: &'static str = "tagtypes available";
|
type Response = TagTypesAvailableResponse;
|
||||||
|
|
||||||
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!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
use crate::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = TagTypesClearRequest;
|
||||||
const COMMAND: &'static str = "tagtypes clear";
|
type Response = TagTypesClearResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,74 @@
|
|||||||
|
use std::u32;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
commands::{
|
request_tokenizer::RequestTokenizer,
|
||||||
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
|
types::TagName,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TagTypesDisable;
|
pub struct TagTypesDisable;
|
||||||
|
|
||||||
impl Command for TagTypesDisable {
|
pub struct TagTypesDisableRequest(Vec<TagName>);
|
||||||
type Response = ();
|
|
||||||
const COMMAND: &'static str = "tagtypes disable";
|
|
||||||
|
|
||||||
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl TagTypesDisableRequest {
|
||||||
let mut parts = parts.peekable();
|
fn new(tagnames: Vec<TagName>) -> Self {
|
||||||
if parts.peek().is_none() {
|
Self(tagnames)
|
||||||
return Err(RequestParserError::UnexpectedEOF);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: verify that the tag types are split by whitespace
|
|
||||||
let mut tag_types = Vec::new();
|
|
||||||
for tag_type in parts {
|
|
||||||
tag_types.push(tag_type.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((Request::TagTypesDisable(tag_types), ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_response(
|
|
||||||
parts: ResponseAttributes<'_>,
|
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
|
||||||
debug_assert!(parts.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CommandRequest for TagTypesDisableRequest {
|
||||||
|
const COMMAND: &'static str = "tagtypes disable";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = None;
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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();
|
||||||
|
if parts.peek().is_none() {
|
||||||
|
return Err(Self::missing_arguments_error(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tag_types = parts
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| {
|
||||||
|
s.parse()
|
||||||
|
.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_command_response!(TagTypesDisable);
|
||||||
|
|
||||||
|
impl Command for TagTypesDisable {
|
||||||
|
type Request = TagTypesDisableRequest;
|
||||||
|
type Response = TagTypesDisableResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,72 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
commands::{
|
request_tokenizer::RequestTokenizer,
|
||||||
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
|
types::TagName,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TagTypesEnable;
|
pub struct TagTypesEnable;
|
||||||
|
|
||||||
impl Command for TagTypesEnable {
|
pub struct TagTypesEnableRequest(Vec<TagName>);
|
||||||
type Response = ();
|
|
||||||
const COMMAND: &'static str = "tagtypes enable";
|
|
||||||
|
|
||||||
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl TagTypesEnableRequest {
|
||||||
let mut parts = parts.peekable();
|
fn new(tagnames: Vec<TagName>) -> Self {
|
||||||
if parts.peek().is_none() {
|
Self(tagnames)
|
||||||
return Err(RequestParserError::UnexpectedEOF);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: verify that the tag types are split by whitespace
|
|
||||||
let mut tag_types = Vec::new();
|
|
||||||
for tag_type in parts {
|
|
||||||
tag_types.push(tag_type.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((Request::TagTypesEnable(tag_types), ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_response(
|
|
||||||
parts: ResponseAttributes<'_>,
|
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
|
||||||
debug_assert!(parts.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CommandRequest for TagTypesEnableRequest {
|
||||||
|
const COMMAND: &'static str = "tagtypes enable";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = None;
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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();
|
||||||
|
if parts.peek().is_none() {
|
||||||
|
return Err(Self::missing_arguments_error(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tag_types = parts
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| {
|
||||||
|
s.parse()
|
||||||
|
.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_command_response!(TagTypesEnable);
|
||||||
|
|
||||||
|
impl Command for TagTypesEnable {
|
||||||
|
type Request = TagTypesEnableRequest;
|
||||||
|
type Response = TagTypesEnableResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,72 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
commands::{
|
request_tokenizer::RequestTokenizer,
|
||||||
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
|
types::TagName,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TagTypesReset;
|
pub struct TagTypesReset;
|
||||||
|
|
||||||
impl Command for TagTypesReset {
|
pub struct TagTypesResetRequest(Vec<TagName>);
|
||||||
type Response = ();
|
|
||||||
const COMMAND: &'static str = "tagtypes reset";
|
|
||||||
|
|
||||||
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl TagTypesResetRequest {
|
||||||
let mut parts = parts.peekable();
|
fn new(tagnames: Vec<TagName>) -> Self {
|
||||||
if parts.peek().is_none() {
|
Self(tagnames)
|
||||||
return Err(RequestParserError::UnexpectedEOF);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: verify that the tag types are split by whitespace
|
|
||||||
let mut tag_types = Vec::new();
|
|
||||||
for tag_type in parts {
|
|
||||||
tag_types.push(tag_type.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((Request::TagTypesReset(tag_types), ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_response(
|
|
||||||
parts: ResponseAttributes<'_>,
|
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
|
||||||
debug_assert!(parts.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CommandRequest for TagTypesResetRequest {
|
||||||
|
const COMMAND: &'static str = "tagtypes reset";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = None;
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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();
|
||||||
|
if parts.peek().is_none() {
|
||||||
|
return Err(Self::missing_arguments_error(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tag_types = parts
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| {
|
||||||
|
s.parse()
|
||||||
|
.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_command_response!(TagTypesReset);
|
||||||
|
|
||||||
|
impl Command for TagTypesReset {
|
||||||
|
type Request = TagTypesResetRequest;
|
||||||
|
type Response = TagTypesResetResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
pub mod next;
|
mod next;
|
||||||
pub mod pause;
|
mod pause;
|
||||||
pub mod play;
|
mod play;
|
||||||
pub mod playid;
|
mod playid;
|
||||||
pub mod previous;
|
mod previous;
|
||||||
pub mod seek;
|
mod seek;
|
||||||
pub mod seekcur;
|
mod seekcur;
|
||||||
pub mod seekid;
|
mod seekid;
|
||||||
pub mod stop;
|
mod stop;
|
||||||
|
|
||||||
pub use next::Next;
|
pub use next::*;
|
||||||
pub use pause::Pause;
|
pub use pause::*;
|
||||||
pub use play::Play;
|
pub use play::*;
|
||||||
pub use playid::PlayId;
|
pub use playid::*;
|
||||||
pub use previous::Previous;
|
pub use previous::*;
|
||||||
pub use seek::Seek;
|
pub use seek::*;
|
||||||
pub use seekcur::SeekCur;
|
pub use seekcur::*;
|
||||||
pub use seekid::SeekId;
|
pub use seekid::*;
|
||||||
pub use stop::Stop;
|
pub use stop::*;
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = NextRequest;
|
||||||
const COMMAND: &'static str = "next";
|
type Response = NextResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,63 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
ResponseParserError,
|
request_tokenizer::RequestTokenizer,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Pause;
|
pub struct Pause;
|
||||||
|
|
||||||
impl Command for Pause {
|
pub struct PauseRequest(Option<bool>);
|
||||||
type Response = ();
|
|
||||||
const COMMAND: &'static str = "pause";
|
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl PauseRequest {
|
||||||
let result = match parts.next() {
|
fn new(toggle: Option<bool>) -> Self {
|
||||||
Some("0") => Ok((Request::Pause(Some(false)), "")),
|
Self(toggle)
|
||||||
Some("1") => Ok((Request::Pause(Some(true)), "")),
|
|
||||||
Some(s) => Err(RequestParserError::SyntaxError(0, s.to_string())),
|
|
||||||
None => Ok((Request::Pause(None), "")),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_response(
|
|
||||||
parts: ResponseAttributes<'_>,
|
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
|
||||||
debug_assert!(parts.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CommandRequest for PauseRequest {
|
||||||
|
const COMMAND: &'static str = "pause";
|
||||||
|
const MIN_ARGS: u32 = 0;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(1);
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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() {
|
||||||
|
Some("0") => Ok(Some(false)),
|
||||||
|
Some("1") => Ok(Some(true)),
|
||||||
|
Some(s) => Err(RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: 0,
|
||||||
|
expected_type: "Option<bool>",
|
||||||
|
raw_input: s.to_owned(),
|
||||||
|
}),
|
||||||
|
None => Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
|
result.map(PauseRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_command_response!(Pause);
|
||||||
|
|
||||||
|
impl Command for Pause {
|
||||||
|
type Request = PauseRequest;
|
||||||
|
type Response = PauseResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, empty_command_response, single_optional_item_command_request},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
types::SongPosition,
|
||||||
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 Response = ();
|
type Request = PlayRequest;
|
||||||
const COMMAND: &'static str = "play";
|
type Response = PlayResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, empty_command_response, single_optional_item_command_request},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
types::SongId,
|
||||||
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 Response = ();
|
type Request = PlayIdRequest;
|
||||||
const COMMAND: &'static str = "playid";
|
type Response = PlayIdResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = PreviousRequest;
|
||||||
const COMMAND: &'static str = "previous";
|
type Response = PreviousResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,73 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
request_tokenizer::RequestTokenizer,
|
||||||
ResponseParserError,
|
types::{SongPosition, TimeWithFractions},
|
||||||
},
|
|
||||||
common::{SongPosition, TimeWithFractions},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Seek;
|
pub struct Seek;
|
||||||
|
|
||||||
impl Command for Seek {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct SeekRequest {
|
||||||
const COMMAND: &'static str = "seek";
|
pub songpos: SongPosition,
|
||||||
|
pub time: TimeWithFractions,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandRequest for SeekRequest {
|
||||||
|
const COMMAND: &'static str = "seek";
|
||||||
|
const MIN_ARGS: u32 = 2;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(2);
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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) => s
|
Some(s) => {
|
||||||
.parse::<SongPosition>()
|
s.parse::<SongPosition>()
|
||||||
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
None => return Err(RequestParserError::UnexpectedEOF),
|
argument_index: 0,
|
||||||
|
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
|
Some(t) => t.parse::<TimeWithFractions>().map_err(|_| {
|
||||||
.parse::<TimeWithFractions>()
|
RequestParserError::SubtypeParserError {
|
||||||
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
|
argument_index: 1,
|
||||||
None => return Err(RequestParserError::UnexpectedEOF),
|
expected_type: "TimeWithFractions",
|
||||||
|
raw_input: t.to_owned(),
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
None => return Err(Self::missing_arguments_error(1)),
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::Seek(songpos, time), ""))
|
Ok(SeekRequest { 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,53 +1,96 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
request_tokenizer::RequestTokenizer,
|
||||||
ResponseParserError,
|
types::{SeekMode, TimeWithFractions},
|
||||||
},
|
|
||||||
common::{SeekMode, TimeWithFractions},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SeekCur;
|
pub struct SeekCur;
|
||||||
|
|
||||||
impl Command for SeekCur {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct SeekCurRequest {
|
||||||
const COMMAND: &'static str = "seekcur";
|
pub mode: SeekMode,
|
||||||
|
pub time: TimeWithFractions,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandRequest for SeekCurRequest {
|
||||||
|
const COMMAND: &'static str = "seekcur";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(1);
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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(RequestParserError::UnexpectedEOF),
|
None => return Err(Self::missing_arguments_error(0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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..]
|
t[1..].parse::<TimeWithFractions>().map_err(|_| {
|
||||||
.parse::<TimeWithFractions>()
|
RequestParserError::SubtypeParserError {
|
||||||
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
|
argument_index: 0,
|
||||||
|
expected_type: "TimeWithFractions",
|
||||||
|
raw_input: t[1..].to_owned(),
|
||||||
|
}
|
||||||
|
})?,
|
||||||
),
|
),
|
||||||
t if t.starts_with('-') => (
|
t if t.starts_with('-') => (
|
||||||
SeekMode::Relative,
|
SeekMode::RelativeReverse,
|
||||||
-t[1..]
|
t[1..].parse::<TimeWithFractions>().map_err(|_| {
|
||||||
.parse::<TimeWithFractions>()
|
RequestParserError::SubtypeParserError {
|
||||||
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
|
argument_index: 0,
|
||||||
|
expected_type: "TimeWithFractions",
|
||||||
|
raw_input: t[1..].to_owned(),
|
||||||
|
}
|
||||||
|
})?,
|
||||||
),
|
),
|
||||||
t => (
|
t => (
|
||||||
SeekMode::Absolute,
|
SeekMode::Absolute,
|
||||||
t.parse::<TimeWithFractions>()
|
t.parse::<TimeWithFractions>().map_err(|_| {
|
||||||
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
|
RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: 0,
|
||||||
|
expected_type: "TimeWithFractions",
|
||||||
|
raw_input: t.to_owned(),
|
||||||
|
}
|
||||||
|
})?,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
debug_assert!(time >= 0.0);
|
||||||
|
|
||||||
Ok((Request::SeekCur(mode, time), ""))
|
Ok(SeekCurRequest { 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,41 +1,71 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
request_tokenizer::RequestTokenizer,
|
||||||
ResponseParserError,
|
types::{SongId, TimeWithFractions},
|
||||||
},
|
|
||||||
common::{SongId, TimeWithFractions},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SeekId;
|
pub struct SeekId;
|
||||||
|
|
||||||
impl Command for SeekId {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct SeekIdRequest {
|
||||||
const COMMAND: &'static str = "seekid";
|
pub songid: SongId,
|
||||||
|
pub time: TimeWithFractions,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandRequest for SeekIdRequest {
|
||||||
|
const COMMAND: &'static str = "seekid";
|
||||||
|
const MIN_ARGS: u32 = 2;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(2);
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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::SyntaxError(0, s.to_owned()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
None => return Err(RequestParserError::UnexpectedEOF),
|
argument_index: 0,
|
||||||
|
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
|
Some(t) => t.parse::<TimeWithFractions>().map_err(|_| {
|
||||||
.parse::<TimeWithFractions>()
|
RequestParserError::SubtypeParserError {
|
||||||
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
|
argument_index: 1,
|
||||||
None => return Err(RequestParserError::UnexpectedEOF),
|
expected_type: "TimeWithFractions",
|
||||||
|
raw_input: t.to_owned(),
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
None => return Err(Self::missing_arguments_error(1)),
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::SeekId(songid, time), ""))
|
Ok(SeekIdRequest { 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = StopRequest;
|
||||||
const COMMAND: &'static str = "stop";
|
type Response = StopResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
pub mod listmounts;
|
mod listmounts;
|
||||||
pub mod listneighbors;
|
mod listneighbors;
|
||||||
pub mod mount;
|
mod mount;
|
||||||
pub mod unmount;
|
mod unmount;
|
||||||
|
|
||||||
pub use listmounts::ListMounts;
|
pub use listmounts::*;
|
||||||
pub use listneighbors::ListNeighbors;
|
pub use listneighbors::*;
|
||||||
pub use mount::Mount;
|
pub use mount::*;
|
||||||
pub use unmount::Unmount;
|
pub use unmount::*;
|
||||||
|
|||||||
@@ -1,22 +1,39 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
|
||||||
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
|
response_tokenizer::expect_property_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = Vec<(String, String)>;
|
type Request = ListMountsRequest;
|
||||||
const COMMAND: &'static str = "listmounts";
|
type Response = ListMountsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
#[cfg(test)]
|
||||||
debug_assert!(parts.next().is_none());
|
mod tests {
|
||||||
Ok((Request::ListMounts, ""))
|
use indoc::indoc;
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_response(
|
use super::*;
|
||||||
parts: ResponseAttributes<'_>,
|
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
#[test]
|
||||||
unimplemented!()
|
fn test_parse_response() {
|
||||||
|
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(),
|
||||||
|
]))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,52 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
|
||||||
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
|
response_tokenizer::{ResponseAttributes, expect_property_type},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ListNeighbors;
|
pub struct ListNeighbors;
|
||||||
|
|
||||||
impl Command for ListNeighbors {
|
empty_command_request!(ListNeighbors, "listneighbors");
|
||||||
type Response = Vec<(String, String)>;
|
|
||||||
const COMMAND: &'static str = "listneighbors";
|
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
debug_assert!(parts.next().is_none());
|
pub struct ListNeighborsResponse(HashMap<String, String>);
|
||||||
Ok((Request::ListNeighbors, ""))
|
|
||||||
|
impl CommandResponse for ListNeighborsResponse {
|
||||||
|
fn into_response_enum(self) -> crate::Response {
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
}
|
||||||
unimplemented!()
|
|
||||||
|
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 {
|
||||||
|
type Request = ListNeighborsRequest;
|
||||||
|
type Response = ListNeighborsResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,36 +1,69 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
commands::{
|
request_tokenizer::RequestTokenizer,
|
||||||
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
|
types::{MountPath, Uri},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Mount;
|
pub struct Mount;
|
||||||
|
|
||||||
impl Command for Mount {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct MountRequest {
|
||||||
const COMMAND: &'static str = "mount";
|
pub path: MountPath,
|
||||||
|
pub uri: Uri,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandRequest for MountRequest {
|
||||||
let path = parts
|
const COMMAND: &'static str = "mount";
|
||||||
.next()
|
const MIN_ARGS: u32 = 2;
|
||||||
.ok_or(RequestParserError::UnexpectedEOF)?
|
const MAX_ARGS: Option<u32> = Some(2);
|
||||||
.to_string();
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
crate::Request::Mount(self.path, self.uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_request_enum(request: crate::Request) -> Option<Self> {
|
||||||
|
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(RequestParserError::UnexpectedEOF)?
|
.ok_or(Self::missing_arguments_error(1))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::Mount(path, uri), ""))
|
Ok(MountRequest { 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,59 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Request,
|
|
||||||
commands::{
|
commands::{
|
||||||
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
|
Command, CommandRequest, RequestParserError, RequestTokenizer, empty_command_response,
|
||||||
},
|
},
|
||||||
|
types::MountPath,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Unmount;
|
pub struct Unmount;
|
||||||
|
|
||||||
impl Command for Unmount {
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
type Response = ();
|
pub struct UnmountRequest(MountPath);
|
||||||
|
|
||||||
|
impl CommandRequest for UnmountRequest {
|
||||||
const COMMAND: &'static str = "unmount";
|
const COMMAND: &'static str = "unmount";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(1);
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let path = parts
|
crate::Request::Unmount(self.0)
|
||||||
.next()
|
|
||||||
.ok_or(RequestParserError::UnexpectedEOF)?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
|
|
||||||
Ok((Request::Unmount(path), ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_request_enum(request: crate::Request) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
match request {
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
crate::Request::Unmount(item) => Some(UnmountRequest(item)),
|
||||||
debug_assert!(parts.is_empty());
|
_ => None,
|
||||||
Ok(())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
type Request = UnmountRequest;
|
||||||
|
type Response = UnmountResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
pub mod albumart;
|
mod albumart;
|
||||||
pub mod count;
|
mod count;
|
||||||
pub mod find;
|
mod find;
|
||||||
pub mod findadd;
|
mod findadd;
|
||||||
pub mod getfingerprint;
|
mod getfingerprint;
|
||||||
pub mod list;
|
mod list;
|
||||||
pub mod listall;
|
mod listall;
|
||||||
pub mod listallinfo;
|
mod listallinfo;
|
||||||
pub mod listfiles;
|
mod listfiles;
|
||||||
pub mod lsinfo;
|
mod lsinfo;
|
||||||
pub mod readcomments;
|
mod readcomments;
|
||||||
pub mod readpicture;
|
mod readpicture;
|
||||||
pub mod rescan;
|
mod rescan;
|
||||||
pub mod search;
|
mod search;
|
||||||
pub mod searchadd;
|
mod searchadd;
|
||||||
pub mod searchaddpl;
|
mod searchaddpl;
|
||||||
pub mod searchcount;
|
mod searchcount;
|
||||||
pub mod update;
|
mod update;
|
||||||
|
|
||||||
pub use albumart::AlbumArt;
|
pub use albumart::*;
|
||||||
pub use count::Count;
|
pub use count::*;
|
||||||
pub use find::Find;
|
pub use find::*;
|
||||||
pub use findadd::FindAdd;
|
pub use findadd::*;
|
||||||
pub use getfingerprint::GetFingerprint;
|
pub use getfingerprint::*;
|
||||||
pub use list::List;
|
pub use list::*;
|
||||||
pub use listall::ListAll;
|
pub use listall::*;
|
||||||
pub use listallinfo::ListAllInfo;
|
pub use listallinfo::*;
|
||||||
pub use listfiles::ListFiles;
|
pub use listfiles::*;
|
||||||
pub use lsinfo::LsInfo;
|
pub use lsinfo::*;
|
||||||
pub use readcomments::ReadComments;
|
pub use readcomments::*;
|
||||||
pub use readpicture::ReadPicture;
|
pub use readpicture::*;
|
||||||
pub use rescan::Rescan;
|
pub use rescan::*;
|
||||||
pub use search::Search;
|
pub use search::*;
|
||||||
pub use searchadd::SearchAdd;
|
pub use searchadd::*;
|
||||||
pub use searchaddpl::SearchAddPl;
|
pub use searchaddpl::*;
|
||||||
pub use searchcount::SearchCount;
|
pub use searchcount::*;
|
||||||
pub use update::Update;
|
pub use update::*;
|
||||||
|
|||||||
@@ -1,46 +1,85 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
request_tokenizer::RequestTokenizer,
|
||||||
ResponseParserError, get_and_parse_property, get_property,
|
response_tokenizer::{ResponseAttributes, get_and_parse_property, get_property},
|
||||||
},
|
types::{Offset, Uri},
|
||||||
common::Offset,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AlbumArt;
|
pub struct AlbumArt;
|
||||||
|
|
||||||
pub struct AlbumArtResponse {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub size: usize,
|
pub struct AlbumArtRequest {
|
||||||
pub binary: Vec<u8>,
|
uri: Uri,
|
||||||
|
offset: Offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command for AlbumArt {
|
impl CommandRequest for AlbumArtRequest {
|
||||||
type Response = AlbumArtResponse;
|
|
||||||
const COMMAND: &'static str = "albumart";
|
const COMMAND: &'static str = "albumart";
|
||||||
|
const MIN_ARGS: u32 = 2;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(2);
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
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() {
|
let uri = match parts.next() {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Err(RequestParserError::UnexpectedEOF),
|
None => return Err(Self::missing_arguments_error(0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let offset = match parts.next() {
|
let offset = match parts.next() {
|
||||||
Some(s) => s
|
Some(s) => s
|
||||||
.parse::<Offset>()
|
.parse::<Offset>()
|
||||||
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
None => return Err(RequestParserError::UnexpectedEOF),
|
argument_index: 1,
|
||||||
|
expected_type: "Offset",
|
||||||
|
raw_input: s.to_string(),
|
||||||
|
})?,
|
||||||
|
None => return Err(Self::missing_arguments_error(1)),
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::AlbumArt(uri.to_string(), offset), ""))
|
Ok(AlbumArtRequest {
|
||||||
|
uri: uri.to_string(),
|
||||||
|
offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct AlbumArtResponse {
|
||||||
|
pub size: usize,
|
||||||
|
pub binary: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandResponse for AlbumArtResponse {
|
||||||
|
fn into_response_enum(self) -> crate::Response {
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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);
|
||||||
|
|
||||||
@@ -49,3 +88,8 @@ impl Command for AlbumArt {
|
|||||||
Ok(AlbumArtResponse { size, binary })
|
Ok(AlbumArtResponse { size, binary })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for AlbumArt {
|
||||||
|
type Request = AlbumArtRequest;
|
||||||
|
type Response = AlbumArtResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,48 +1,100 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
filter::Filter,
|
||||||
ResponseParserError, get_and_parse_property,
|
request_tokenizer::RequestTokenizer,
|
||||||
},
|
response_tokenizer::{ResponseAttributes, get_and_parse_property},
|
||||||
filter::parse_filter,
|
types::GroupType,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Count;
|
pub struct Count;
|
||||||
|
|
||||||
pub struct CountResponse {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub songs: usize,
|
pub struct CountRequest {
|
||||||
pub playtime: u64,
|
filter: Filter,
|
||||||
|
group: Option<GroupType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command for Count {
|
impl CommandRequest for CountRequest {
|
||||||
type Response = CountResponse;
|
|
||||||
const COMMAND: &'static str = "count";
|
const COMMAND: &'static str = "count";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(2);
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let filter = parse_filter(&mut parts)?;
|
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 = if let Some("group") = parts.next() {
|
||||||
let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
let group = parts
|
||||||
|
.next()
|
||||||
|
.ok_or(RequestParserError::MissingKeywordValue {
|
||||||
|
argument_index: 1,
|
||||||
|
keyword: "group",
|
||||||
|
})?;
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
group
|
group
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: 1,
|
||||||
|
expected_type: "GroupType",
|
||||||
|
raw_input: group.to_owned(),
|
||||||
|
})?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::Count(filter, group), ""))
|
Ok(CountRequest { filter, group })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct CountResponse {
|
||||||
|
pub songs: usize,
|
||||||
|
pub playtime: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandResponse for CountResponse {
|
||||||
|
fn into_response_enum(self) -> crate::Response {
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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);
|
||||||
@@ -50,3 +102,8 @@ impl Command for Count {
|
|||||||
Ok(CountResponse { songs, playtime })
|
Ok(CountResponse { songs, playtime })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for Count {
|
||||||
|
type Request = CountRequest;
|
||||||
|
type Response = CountResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,51 +1,143 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
filter::Filter,
|
||||||
ResponseParserError,
|
request_tokenizer::RequestTokenizer,
|
||||||
},
|
response_tokenizer::ResponseAttributes,
|
||||||
filter::parse_filter,
|
types::{DbSelectionPrintResponse, DbSongInfo, Sort, WindowRange},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Find;
|
pub struct Find;
|
||||||
|
|
||||||
pub struct FindResponse {}
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct FindRequest {
|
||||||
|
filter: Filter,
|
||||||
|
sort: Option<Sort>,
|
||||||
|
window: Option<WindowRange>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Command for Find {
|
impl CommandRequest for FindRequest {
|
||||||
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 parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let filter = parse_filter(&mut parts)?;
|
crate::Request::Find(self.filter, self.sort, self.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
parts
|
s.parse()
|
||||||
.next()
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
.ok_or(RequestParserError::UnexpectedEOF)?
|
argument_index: argument_index_counter,
|
||||||
.to_string(),
|
expected_type: "Sort",
|
||||||
|
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 {
|
||||||
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
argument_index_counter += 1;
|
||||||
|
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::SyntaxError(0, w.to_string()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: argument_index_counter,
|
||||||
|
expected_type: "WindowRange",
|
||||||
|
raw_input: w.to_string(),
|
||||||
|
})?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::Find(filter, sort, window), ""))
|
Ok(FindRequest {
|
||||||
}
|
filter,
|
||||||
|
sort,
|
||||||
fn parse_response(
|
window,
|
||||||
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,60 +1,141 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
filter::Filter,
|
||||||
ResponseParserError,
|
request_tokenizer::RequestTokenizer,
|
||||||
},
|
types::{SongPosition, Sort, WindowRange},
|
||||||
filter::parse_filter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct FindAdd;
|
pub struct FindAdd;
|
||||||
|
|
||||||
impl Command for FindAdd {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct FindAddRequest {
|
||||||
|
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 parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let filter = parse_filter(&mut parts)?;
|
crate::Request::FindAdd(self.filter, self.sort, self.window, self.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
parts
|
s.parse()
|
||||||
.next()
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
.ok_or(RequestParserError::UnexpectedEOF)?
|
argument_index: argument_index_counter,
|
||||||
.to_string(),
|
expected_type: "Sort",
|
||||||
|
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 {
|
||||||
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
argument_index_counter += 1;
|
||||||
|
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::SyntaxError(0, w.to_string()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
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 {
|
||||||
let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
argument_index_counter += 1;
|
||||||
|
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::SyntaxError(0, p.to_string()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: argument_index_counter,
|
||||||
|
expected_type: "SongPosition",
|
||||||
|
raw_input: p.to_string(),
|
||||||
|
})?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::FindAdd(filter, sort, window, position), ""))
|
Ok(FindAddRequest {
|
||||||
}
|
filter,
|
||||||
|
sort,
|
||||||
fn parse_response(
|
window,
|
||||||
parts: ResponseAttributes<'_>,
|
position,
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
})
|
||||||
debug_assert!(parts.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
empty_command_response!(FindAdd);
|
||||||
|
|
||||||
|
impl Command for FindAdd {
|
||||||
|
type Request = FindAddRequest;
|
||||||
|
type Response = FindAddResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,38 +1,41 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
|
||||||
ResponseParserError, get_and_parse_property,
|
use crate::{
|
||||||
|
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 Command for GetFingerprint {
|
impl CommandResponse for GetFingerprintResponse {
|
||||||
type Response = GetFingerprintResponse;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "getfingerprint";
|
todo!()
|
||||||
|
|
||||||
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 parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,58 +1,169 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
filter::Filter,
|
||||||
ResponseParserError, expect_property_type,
|
request_tokenizer::RequestTokenizer,
|
||||||
},
|
response_tokenizer::{ResponseAttributes, expect_property_type},
|
||||||
filter::parse_filter,
|
types::{GroupType, TagName, WindowRange},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct List;
|
pub struct List;
|
||||||
|
|
||||||
pub type ListResponse = Vec<String>;
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct ListRequest {
|
||||||
|
tagname: TagName,
|
||||||
|
filter: Option<Filter>,
|
||||||
|
groups: Vec<GroupType>,
|
||||||
|
window: Option<WindowRange>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Command for List {
|
impl CommandRequest for ListRequest {
|
||||||
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 parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let tagtype = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
crate::Request::List(self.tagname, self.filter, self.groups, self.window)
|
||||||
let tagtype = tagtype
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| RequestParserError::SyntaxError(1, tagtype.to_owned()))?;
|
|
||||||
|
|
||||||
// TODO: This should be optional
|
|
||||||
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::List(tagtype, filter, group), ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_request_enum(request: crate::Request) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
match request {
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
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()
|
||||||
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: 0,
|
||||||
|
expected_type: "TagName",
|
||||||
|
raw_input: tagname.to_owned(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut filter = None;
|
||||||
|
let mut groups = Vec::new();
|
||||||
|
let mut window = None;
|
||||||
|
|
||||||
|
let mut next = parts.next();
|
||||||
|
|
||||||
|
let mut argument_index_counter = 0;
|
||||||
|
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
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: 2,
|
||||||
|
expected_type: "GroupType",
|
||||||
|
raw_input: group.to_owned(),
|
||||||
|
})?;
|
||||||
|
groups.push(parsed_group);
|
||||||
|
next = parts.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(w) = next
|
||||||
|
&& w == "window"
|
||||||
|
{
|
||||||
|
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.0.first().map(|(k, _)| k);
|
let key = parts_.first().map(|(k, _)| k);
|
||||||
parts.0.iter().all(|(k, _)| k == key.unwrap())
|
parts_.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(list)
|
Ok(ListResponse(list))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for List {
|
||||||
|
type Request = ListRequest;
|
||||||
|
type Response = ListResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,34 +1,163 @@
|
|||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
|
||||||
ResponseParserError,
|
use crate::{
|
||||||
|
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
|
||||||
pub type ListAllResponse = Vec<String>;
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct ListAllResponse(Vec<DbSelectionPrintResponse>);
|
||||||
|
|
||||||
impl Command for ListAll {
|
impl CommandResponse for ListAllResponse {
|
||||||
type Response = ListAllResponse;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "listall";
|
todo!()
|
||||||
|
|
||||||
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::ListAll(uri), ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
}
|
||||||
unimplemented!()
|
|
||||||
|
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
|
||||||
|
let result = DbSelectionPrintResponse::parse(parts)?;
|
||||||
|
|
||||||
|
Ok(ListAllResponse(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command for ListAll {
|
||||||
|
type Request = ListAllRequest;
|
||||||
|
type Response = ListAllResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::types::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo};
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
]))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,120 @@
|
|||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
|
||||||
ResponseParserError,
|
use crate::{
|
||||||
|
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
|
||||||
// in addition to the metadata of each entry
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub type ListAllInfoResponse = Vec<String>;
|
pub struct ListAllInfoResponse(Vec<DbSelectionPrintResponse>);
|
||||||
|
|
||||||
impl Command for ListAllInfo {
|
impl CommandResponse for ListAllInfoResponse {
|
||||||
type Response = ListAllInfoResponse;
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
const COMMAND: &'static str = "listallinfo";
|
todo!()
|
||||||
|
|
||||||
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::ListAllInfo(uri), ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn into_response_enum(self) -> crate::Response {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
}
|
||||||
unimplemented!()
|
|
||||||
|
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
|
||||||
|
let result = DbSelectionPrintResponse::parse(parts)?;
|
||||||
|
|
||||||
|
Ok(ListAllInfoResponse(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command for ListAllInfo {
|
||||||
|
type Request = ListAllInfoRequest;
|
||||||
|
type Response = ListAllInfoResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::types::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo, Tag};
|
||||||
|
|
||||||
|
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())
|
||||||
|
})
|
||||||
|
])),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,47 @@
|
|||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
|
||||||
ResponseParserError,
|
use crate::{
|
||||||
|
commands::{
|
||||||
|
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
|
||||||
|
},
|
||||||
|
response_tokenizer::ResponseAttributes,
|
||||||
|
types::{DbDirectoryInfo, DbSelectionPrintResponse, Uri},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ListFiles;
|
pub struct ListFiles;
|
||||||
|
|
||||||
// TODO: fix this type
|
single_optional_item_command_request!(ListFiles, "listfiles", Uri);
|
||||||
pub type ListFilesResponse = Vec<String>;
|
|
||||||
|
|
||||||
impl Command for ListFiles {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ListFilesResponse;
|
pub struct ListFilesResponse(Vec<DbDirectoryInfo>);
|
||||||
const COMMAND: &'static str = "listfiles";
|
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandResponse for ListFilesResponse {
|
||||||
let uri = parts
|
fn into_response_enum(self) -> crate::Response {
|
||||||
.next()
|
todo!()
|
||||||
.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(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
}
|
||||||
unimplemented!()
|
|
||||||
|
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 {
|
||||||
|
type Request = ListFilesRequest;
|
||||||
|
type Response = ListFilesResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,34 +1,95 @@
|
|||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
|
||||||
ResponseParserError,
|
use crate::{
|
||||||
|
commands::{
|
||||||
|
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
|
||||||
|
},
|
||||||
|
response_tokenizer::ResponseAttributes,
|
||||||
|
types::{DbSelectionPrintResponse, Uri},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct LsInfo;
|
pub struct LsInfo;
|
||||||
|
|
||||||
// TODO: fix this type
|
single_optional_item_command_request!(LsInfo, "lsinfo", Uri);
|
||||||
pub type LsInfoResponse = Vec<String>;
|
|
||||||
|
|
||||||
impl Command for LsInfo {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = LsInfoResponse;
|
pub struct LsInfoResponse(Vec<DbSelectionPrintResponse>);
|
||||||
const COMMAND: &'static str = "lsinfo";
|
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandResponse for LsInfoResponse {
|
||||||
let uri = parts
|
fn into_response_enum(self) -> crate::Response {
|
||||||
.next()
|
todo!()
|
||||||
.map(|s| {
|
|
||||||
s.parse()
|
|
||||||
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
|
|
||||||
Ok((Request::LsInfo(uri), ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
}
|
||||||
unimplemented!()
|
|
||||||
|
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
|
||||||
|
let result = DbSelectionPrintResponse::parse(parts)?;
|
||||||
|
|
||||||
|
Ok(LsInfoResponse(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command for LsInfo {
|
||||||
|
type Request = LsInfoRequest;
|
||||||
|
type Response = LsInfoResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::types::{DbDirectoryInfo, DbPlaylistInfo};
|
||||||
|
|
||||||
|
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())
|
||||||
|
})
|
||||||
|
])),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,47 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, GenericResponseValue, Request, RequestParserError, RequestParserResult,
|
|
||||||
ResponseAttributes, ResponseParserError,
|
use crate::{
|
||||||
|
commands::{Command, CommandResponse, ResponseParserError, single_item_command_request},
|
||||||
|
response_tokenizer::{GenericResponseValue, ResponseAttributes},
|
||||||
|
types::Uri,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ReadComments;
|
pub struct ReadComments;
|
||||||
|
|
||||||
pub type ReadCommentsResponse = HashMap<String, String>;
|
single_item_command_request!(ReadComments, "readcomments", Uri);
|
||||||
|
|
||||||
impl Command for ReadComments {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ReadCommentsResponse;
|
pub struct ReadCommentsResponse(HashMap<String, String>);
|
||||||
const COMMAND: &'static str = "readcomments";
|
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandResponse for ReadCommentsResponse {
|
||||||
let uri = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
let uri = uri
|
todo!()
|
||||||
.parse()
|
|
||||||
.map_err(|_| RequestParserError::SyntaxError(1, uri.to_owned()))?;
|
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
|
|
||||||
Ok((Request::ReadComments(uri), ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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()
|
.iter()
|
||||||
.map(|(k, v)| match v {
|
.map(|(k, v)| match v {
|
||||||
GenericResponseValue::Text(s) => Ok((k.to_string(), s.to_string())),
|
GenericResponseValue::Text(s) => Ok((k.to_string(), s.to_string())),
|
||||||
GenericResponseValue::Binary(_) => Err(ResponseParserError::SyntaxError(1, k)),
|
GenericResponseValue::Binary(_) => {
|
||||||
|
Err(ResponseParserError::SyntaxError(1, k.to_string()))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<HashMap<_, _>, ResponseParserError>>()?;
|
.collect::<Result<HashMap<_, _>, ResponseParserError>>()?;
|
||||||
|
|
||||||
Ok(comments)
|
Ok(ReadCommentsResponse(comments))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for ReadComments {
|
||||||
|
type Request = ReadCommentsRequest;
|
||||||
|
type Response = ReadCommentsResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,51 +1,93 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
request_tokenizer::RequestTokenizer,
|
||||||
ResponseParserError, get_and_parse_property, get_optional_property, get_property,
|
response_tokenizer::{
|
||||||
|
ResponseAttributes, get_and_parse_property, get_optional_property, get_property,
|
||||||
},
|
},
|
||||||
common::Offset,
|
types::{Offset, Uri},
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Command for ReadPicture {
|
impl CommandResponse for ReadPictureResponse {
|
||||||
type Response = Option<ReadPictureResponse>;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "readpicture";
|
todo!()
|
||||||
|
|
||||||
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 parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
}
|
||||||
let parts: HashMap<_, _> = parts.into();
|
|
||||||
|
|
||||||
if parts.is_empty() {
|
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
|
||||||
return Ok(None);
|
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);
|
||||||
|
|
||||||
@@ -53,10 +95,15 @@ impl Command for ReadPicture {
|
|||||||
|
|
||||||
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(Some(ReadPictureResponse {
|
Ok(ReadPictureResponse {
|
||||||
size,
|
size,
|
||||||
binary,
|
binary,
|
||||||
mimetype,
|
mimetype,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for ReadPicture {
|
||||||
|
type Request = ReadPictureRequest;
|
||||||
|
type Response = ReadPictureResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,43 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
|
||||||
get_and_parse_property,
|
use crate::{
|
||||||
|
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 Command for Rescan {
|
impl CommandResponse for RescanResponse {
|
||||||
type Response = RescanResponse;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "rescan";
|
todo!()
|
||||||
|
|
||||||
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 parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,51 +1,143 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
filter::Filter,
|
||||||
ResponseParserError,
|
request_tokenizer::RequestTokenizer,
|
||||||
},
|
response_tokenizer::ResponseAttributes,
|
||||||
filter::parse_filter,
|
types::{DbSelectionPrintResponse, DbSongInfo, Sort, WindowRange},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Search;
|
pub struct Search;
|
||||||
|
|
||||||
pub struct SearchResponse {}
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct SearchRequest {
|
||||||
|
filter: Filter,
|
||||||
|
sort: Option<Sort>,
|
||||||
|
window: Option<WindowRange>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Command for Search {
|
impl CommandRequest for SearchRequest {
|
||||||
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 parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let filter = parse_filter(&mut parts)?;
|
crate::Request::Search(self.filter, self.sort, self.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
parts
|
s.parse()
|
||||||
.next()
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
.ok_or(RequestParserError::UnexpectedEOF)?
|
argument_index: argument_index_counter,
|
||||||
.to_string(),
|
expected_type: "Sort",
|
||||||
|
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 {
|
||||||
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
argument_index_counter += 1;
|
||||||
|
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::SyntaxError(0, w.to_string()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: argument_index_counter,
|
||||||
|
expected_type: "WindowRange",
|
||||||
|
raw_input: w.to_string(),
|
||||||
|
})?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::Search(filter, sort, window), ""))
|
Ok(SearchRequest {
|
||||||
}
|
filter,
|
||||||
|
sort,
|
||||||
fn parse_response(
|
window,
|
||||||
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,60 +1,141 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
filter::Filter,
|
||||||
ResponseParserError,
|
request_tokenizer::RequestTokenizer,
|
||||||
},
|
types::{SongPosition, Sort, WindowRange},
|
||||||
filter::parse_filter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SearchAdd;
|
pub struct SearchAdd;
|
||||||
|
|
||||||
impl Command for SearchAdd {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct SearchAddRequest {
|
||||||
|
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 parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let filter = parse_filter(&mut parts)?;
|
crate::Request::SearchAdd(self.filter, self.sort, self.window, self.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
parts
|
s.parse()
|
||||||
.next()
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
.ok_or(RequestParserError::UnexpectedEOF)?
|
argument_index: argument_index_counter,
|
||||||
.to_string(),
|
expected_type: "Sort",
|
||||||
|
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 {
|
||||||
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
argument_index_counter += 1;
|
||||||
|
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::SyntaxError(0, w.to_string()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
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 {
|
||||||
let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
argument_index_counter += 1;
|
||||||
|
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::SyntaxError(0, p.to_string()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: argument_index_counter,
|
||||||
|
expected_type: "SongPosition",
|
||||||
|
raw_input: p.to_string(),
|
||||||
|
})?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::SearchAdd(filter, sort, window, position), ""))
|
Ok(SearchAddRequest {
|
||||||
}
|
filter,
|
||||||
|
sort,
|
||||||
fn parse_response(
|
window,
|
||||||
parts: ResponseAttributes<'_>,
|
position,
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
})
|
||||||
debug_assert!(parts.is_empty());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
empty_command_response!(SearchAdd);
|
||||||
|
|
||||||
|
impl Command for SearchAdd {
|
||||||
|
type Request = SearchAddRequest;
|
||||||
|
type Response = SearchAddResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,68 +1,157 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
filter::Filter,
|
||||||
ResponseParserError,
|
request_tokenizer::RequestTokenizer,
|
||||||
},
|
types::{PlaylistName, SongPosition, Sort, WindowRange},
|
||||||
filter::parse_filter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SearchAddPl;
|
pub struct SearchAddPl;
|
||||||
|
|
||||||
impl Command for SearchAddPl {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = ();
|
pub struct SearchAddPlRequest {
|
||||||
const COMMAND: &'static str = "searchaddpl";
|
playlist_name: PlaylistName,
|
||||||
|
filter: Filter,
|
||||||
|
sort: Option<Sort>,
|
||||||
|
window: Option<WindowRange>,
|
||||||
|
position: Option<SongPosition>,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandRequest for SearchAddPlRequest {
|
||||||
|
const COMMAND: &'static str = "searchaddpl";
|
||||||
|
const MIN_ARGS: u32 = 2;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(5);
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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(RequestParserError::UnexpectedEOF)?
|
.ok_or(Self::missing_arguments_error(0))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let filter = parse_filter(&mut parts)?;
|
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(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(
|
||||||
parts
|
s.parse()
|
||||||
.next()
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
.ok_or(RequestParserError::UnexpectedEOF)?
|
argument_index: argument_index_counter,
|
||||||
.to_string(),
|
expected_type: "Sort",
|
||||||
|
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 {
|
||||||
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
argument_index_counter += 1;
|
||||||
|
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::SyntaxError(0, w.to_string()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
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 {
|
||||||
let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
argument_index_counter += 1;
|
||||||
|
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::SyntaxError(0, p.to_string()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: argument_index_counter,
|
||||||
|
expected_type: "SongPosition",
|
||||||
|
raw_input: p.to_string(),
|
||||||
|
})?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((
|
Ok(SearchAddPlRequest {
|
||||||
Request::SearchAddPl(playlist_name, filter, sort, window, position),
|
playlist_name,
|
||||||
"",
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,47 +1,101 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
filter::Filter,
|
||||||
ResponseParserError, get_and_parse_property,
|
request_tokenizer::RequestTokenizer,
|
||||||
},
|
response_tokenizer::{ResponseAttributes, get_and_parse_property},
|
||||||
filter::parse_filter,
|
types::{GroupType, Seconds},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SearchCount;
|
pub struct SearchCount;
|
||||||
|
|
||||||
pub struct SearchCountResponse {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub songs: usize,
|
pub struct SearchCountRequest {
|
||||||
pub playtime: u64,
|
filter: Filter,
|
||||||
|
group: Option<GroupType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command for SearchCount {
|
impl CommandRequest for SearchCountRequest {
|
||||||
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 parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let filter = parse_filter(&mut parts)?;
|
crate::Request::SearchCount(self.filter, self.group)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.next().ok_or(RequestParserError::UnexpectedEOF)?;
|
let group = parts
|
||||||
|
.next()
|
||||||
|
.ok_or(RequestParserError::MissingKeywordValue {
|
||||||
|
keyword: "group",
|
||||||
|
argument_index: 1,
|
||||||
|
})?;
|
||||||
Some(
|
Some(
|
||||||
group
|
group
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?,
|
.map_err(|_| RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: 1,
|
||||||
|
expected_type: "Group",
|
||||||
|
raw_input: group.to_string(),
|
||||||
|
})?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::SearchCount(filter, group), ""))
|
Ok(SearchCountRequest { 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 parse_response(
|
fn into_response_enum(self) -> crate::Response {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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);
|
||||||
@@ -49,3 +103,8 @@ impl Command for SearchCount {
|
|||||||
Ok(SearchCountResponse { songs, playtime })
|
Ok(SearchCountResponse { songs, playtime })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for SearchCount {
|
||||||
|
type Request = SearchCountRequest;
|
||||||
|
type Response = SearchCountResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,43 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::commands::{
|
use serde::{Deserialize, Serialize};
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
|
||||||
get_and_parse_property,
|
use crate::{
|
||||||
|
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 Command for Update {
|
impl CommandResponse for UpdateResponse {
|
||||||
type Response = UpdateResponse;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "update";
|
todo!()
|
||||||
|
|
||||||
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 parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
pub mod delpartition;
|
mod delpartition;
|
||||||
pub mod listpartitions;
|
mod listpartitions;
|
||||||
pub mod moveoutput;
|
mod moveoutput;
|
||||||
pub mod newpartition;
|
mod newpartition;
|
||||||
pub mod partition;
|
mod partition;
|
||||||
|
|
||||||
pub use delpartition::DelPartition;
|
pub use delpartition::*;
|
||||||
pub use listpartitions::ListPartitions;
|
pub use listpartitions::*;
|
||||||
pub use moveoutput::MoveOutput;
|
pub use moveoutput::*;
|
||||||
pub use newpartition::NewPartition;
|
pub use newpartition::*;
|
||||||
pub use partition::Partition;
|
pub use partition::*;
|
||||||
|
|||||||
@@ -1,29 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
ResponseParserError,
|
types::PartitionName,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = DelPartitionRequest;
|
||||||
const COMMAND: &'static str = "delpartition";
|
type Response = DelPartitionResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,16 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
|
||||||
expect_property_type,
|
response_tokenizer::expect_property_type,
|
||||||
|
types::PartitionName,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ListPartitions;
|
pub struct ListPartitions;
|
||||||
|
|
||||||
pub type ListPartitionsResponse = Vec<String>;
|
empty_command_request!(ListPartitions, "listpartitions");
|
||||||
|
|
||||||
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_response, single_item_command_request};
|
||||||
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 Response = ();
|
type Request = MoveOutputRequest;
|
||||||
const COMMAND: &'static str = "moveoutput";
|
type Response = MoveOutputResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
ResponseParserError,
|
types::PartitionName,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = NewPartitionRequest;
|
||||||
const COMMAND: &'static str = "newpartition";
|
type Response = NewPartitionResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,15 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
ResponseParserError,
|
types::PartitionName,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Response = ();
|
type Request = PartitionRequest;
|
||||||
const COMMAND: &'static str = "partition";
|
type Response = PartitionResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
pub mod consume;
|
mod consume;
|
||||||
pub mod crossfade;
|
mod crossfade;
|
||||||
pub mod getvol;
|
mod getvol;
|
||||||
pub mod mixrampdb;
|
mod mixrampdb;
|
||||||
pub mod mixrampdelay;
|
mod mixrampdelay;
|
||||||
pub mod random;
|
mod random;
|
||||||
pub mod repeat;
|
mod repeat;
|
||||||
pub mod replay_gain_mode;
|
mod replay_gain_mode;
|
||||||
pub mod replay_gain_status;
|
mod replay_gain_status;
|
||||||
pub mod setvol;
|
mod setvol;
|
||||||
pub mod single;
|
mod single;
|
||||||
pub mod volume;
|
mod volume;
|
||||||
|
|
||||||
pub use consume::Consume;
|
pub use consume::*;
|
||||||
pub use crossfade::Crossfade;
|
pub use crossfade::*;
|
||||||
pub use getvol::GetVol;
|
pub use getvol::*;
|
||||||
pub use mixrampdb::MixRampDb;
|
pub use mixrampdb::*;
|
||||||
pub use mixrampdelay::MixRampDelay;
|
pub use mixrampdelay::*;
|
||||||
pub use random::Random;
|
pub use random::*;
|
||||||
pub use repeat::Repeat;
|
pub use repeat::*;
|
||||||
pub use replay_gain_mode::ReplayGainMode;
|
pub use replay_gain_mode::*;
|
||||||
pub use replay_gain_status::ReplayGainStatus;
|
pub use replay_gain_status::*;
|
||||||
pub use setvol::SetVol;
|
pub use setvol::*;
|
||||||
pub use single::Single;
|
pub use single::*;
|
||||||
pub use volume::Volume;
|
pub use volume::*;
|
||||||
|
|||||||
@@ -1,32 +1,15 @@
|
|||||||
use std::str::FromStr;
|
use crate::{
|
||||||
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
use crate::commands::{
|
types::BoolOrOneshot,
|
||||||
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 Response = ();
|
type Request = ConsumeRequest;
|
||||||
const COMMAND: &'static str = "consume";
|
type Response = ConsumeResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
types::Seconds,
|
||||||
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 Response = ();
|
type Request = CrossfadeRequest;
|
||||||
const COMMAND: &'static str = "crossfade";
|
type Response = CrossfadeResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,15 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, empty_command_request, single_item_command_response},
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
types::VolumeValue,
|
||||||
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 Response = VolumeValue;
|
type Request = GetVolRequest;
|
||||||
const COMMAND: &'static str = "getvol";
|
type Response = GetVolResponse;
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,12 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_response, single_item_command_request};
|
||||||
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 Response = ();
|
type Request = MixRampDbRequest;
|
||||||
const COMMAND: &'static str = "mixrampdb";
|
type Response = MixRampDbResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,14 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
types::Seconds,
|
||||||
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 Response = ();
|
type Request = MixRampDelayRequest;
|
||||||
const COMMAND: &'static str = "mixrampdelay";
|
type Response = MixRampDelayResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,56 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
ResponseParserError,
|
request_tokenizer::RequestTokenizer,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Random;
|
pub struct Random;
|
||||||
|
|
||||||
impl Command for Random {
|
pub struct RandomRequest(bool);
|
||||||
type Response = ();
|
|
||||||
const COMMAND: &'static str = "random";
|
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandRequest for RandomRequest {
|
||||||
|
const COMMAND: &'static str = "random";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(1);
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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) => return Err(RequestParserError::SyntaxError(0, s.to_owned())),
|
Some(s) => {
|
||||||
None => return Err(RequestParserError::UnexpectedEOF),
|
return Err(RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: 0,
|
||||||
|
expected_type: "bool",
|
||||||
|
raw_input: s.to_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => return Err(Self::missing_arguments_error(0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::Random(state), ""))
|
Ok(RandomRequest(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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,56 @@
|
|||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
ResponseParserError,
|
request_tokenizer::RequestTokenizer,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Repeat;
|
pub struct Repeat;
|
||||||
|
|
||||||
impl Command for Repeat {
|
pub struct RepeatRequest(bool);
|
||||||
type Response = ();
|
|
||||||
const COMMAND: &'static str = "repeat";
|
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandRequest for RepeatRequest {
|
||||||
|
const COMMAND: &'static str = "repeat";
|
||||||
|
const MIN_ARGS: u32 = 1;
|
||||||
|
const MAX_ARGS: Option<u32> = Some(1);
|
||||||
|
|
||||||
|
fn into_request_enum(self) -> crate::Request {
|
||||||
|
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) => return Err(RequestParserError::SyntaxError(0, s.to_owned())),
|
Some(s) => {
|
||||||
None => return Err(RequestParserError::UnexpectedEOF),
|
return Err(RequestParserError::SubtypeParserError {
|
||||||
|
argument_index: 0,
|
||||||
|
expected_type: "bool",
|
||||||
|
raw_input: s.to_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => return Err(Self::missing_arguments_error(0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
Self::throw_if_too_many_arguments(parts)?;
|
||||||
|
|
||||||
Ok((Request::Repeat(state), ""))
|
Ok(RepeatRequest(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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,15 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
types::ReplayGainModeMode,
|
||||||
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 Response = ();
|
type Request = ReplayGainModeRequest;
|
||||||
const COMMAND: &'static str = "replay_gain_mode";
|
type Response = ReplayGainModeResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,39 +3,45 @@ use std::{collections::HashMap, str::FromStr};
|
|||||||
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_property},
|
||||||
get_property,
|
types::ReplayGainModeMode,
|
||||||
},
|
|
||||||
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 Command for ReplayGainStatus {
|
impl CommandResponse for ReplayGainStatusResponse {
|
||||||
type Response = ReplayGainStatusResponse;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "replay_gain_status";
|
todo!()
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
Ok((Request::ReplayGainStatus, ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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("replay_gain_mode", replay_gain_mode)
|
ResponseParserError::InvalidProperty(
|
||||||
|
"replay_gain_mode".to_string(),
|
||||||
|
replay_gain_mode.to_string(),
|
||||||
|
)
|
||||||
})?,
|
})?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for ReplayGainStatus {
|
||||||
|
type Request = ReplayGainStatusRequest;
|
||||||
|
type Response = ReplayGainStatusResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,15 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
types::VolumeValue,
|
||||||
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 Response = ();
|
type Request = SetVolRequest;
|
||||||
const COMMAND: &'static str = "setvol";
|
type Response = SetVolResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,15 @@
|
|||||||
use std::str::FromStr;
|
use crate::{
|
||||||
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
use crate::commands::{
|
types::BoolOrOneshot,
|
||||||
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 Response = ();
|
type Request = SingleRequest;
|
||||||
const COMMAND: &'static str = "single";
|
type Response = SingleResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,15 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{Command, empty_command_response, single_item_command_request},
|
||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
types::VolumeValue,
|
||||||
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 Response = ();
|
type Request = VolumeRequest;
|
||||||
const COMMAND: &'static str = "volume";
|
type Response = VolumeResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
pub mod clearerror;
|
mod clearerror;
|
||||||
pub mod currentsong;
|
mod currentsong;
|
||||||
pub mod idle;
|
mod idle;
|
||||||
pub mod stats;
|
mod stats;
|
||||||
pub mod status;
|
mod status;
|
||||||
|
|
||||||
pub use clearerror::ClearError;
|
pub use clearerror::*;
|
||||||
pub use currentsong::CurrentSong;
|
pub use currentsong::*;
|
||||||
pub use idle::Idle;
|
pub use idle::*;
|
||||||
pub use stats::Stats;
|
pub use stats::*;
|
||||||
pub use status::Status;
|
pub use status::*;
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
use crate::commands::{
|
use crate::commands::{Command, empty_command_request, empty_command_response};
|
||||||
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 Response = ();
|
type Request = ClearErrorRequest;
|
||||||
const COMMAND: &'static str = "clearerror";
|
type Response = ClearErrorResponse;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,60 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
|
||||||
|
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;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
empty_command_request!(CurrentSong, "currentsong");
|
||||||
pub struct CurrentSongResponse {}
|
|
||||||
|
|
||||||
impl Command for CurrentSong {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
type Response = CurrentSongResponse;
|
pub struct CurrentSongResponse {
|
||||||
const COMMAND: &'static str = "currentsong";
|
position: SongPosition,
|
||||||
|
id: SongId,
|
||||||
|
priority: Option<Priority>,
|
||||||
|
song_info: DbSongInfo,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
impl CommandResponse for CurrentSongResponse {
|
||||||
debug_assert!(parts.next().is_none());
|
fn into_response_enum(self) -> crate::Response {
|
||||||
|
todo!()
|
||||||
Ok((Request::CurrentSong, ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
_parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
}
|
||||||
unimplemented!()
|
|
||||||
|
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 {
|
||||||
|
type Request = CurrentSongRequest;
|
||||||
|
type Response = CurrentSongResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,37 +1,62 @@
|
|||||||
use std::str::{FromStr, SplitWhitespace};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::common::SubSystem;
|
use crate::{
|
||||||
|
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
|
||||||
use crate::commands::{
|
request_tokenizer::RequestTokenizer,
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
types::SubSystem,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Idle;
|
pub struct Idle;
|
||||||
|
|
||||||
impl Command for Idle {
|
pub struct IdleRequest(Option<Vec<SubSystem>>);
|
||||||
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 parse_request(mut parts: SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
fn into_request_enum(self) -> crate::Request {
|
||||||
let result = parts
|
crate::Request::Idle(self.0)
|
||||||
.next()
|
|
||||||
.map_or(Ok((Request::Idle(None), "")), |subsystems| {
|
|
||||||
let subsystems = subsystems
|
|
||||||
.split(',')
|
|
||||||
.map(|subsystem| SubSystem::from_str(subsystem).unwrap())
|
|
||||||
.collect();
|
|
||||||
Ok((Request::Idle(Some(subsystems)), ""))
|
|
||||||
});
|
|
||||||
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_request_enum(request: crate::Request) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
match request {
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
crate::Request::Idle(subsystems) => Some(IdleRequest(subsystems)),
|
||||||
debug_assert!(parts.is_empty());
|
_ => None,
|
||||||
Ok(())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
.split(',')
|
||||||
|
.map(|subsystem| SubSystem::from_str(subsystem).unwrap())
|
||||||
|
.collect();
|
||||||
|
Ok(Some(subsystems))
|
||||||
|
});
|
||||||
|
|
||||||
|
result.map(IdleRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
empty_command_response!(Idle);
|
||||||
|
|
||||||
|
impl Command for Idle {
|
||||||
|
type Request = IdleRequest;
|
||||||
|
type Response = IdleResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::commands::{
|
use crate::{
|
||||||
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
|
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
|
||||||
get_and_parse_optional_property, get_and_parse_property,
|
response_tokenizer::{
|
||||||
|
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,
|
||||||
@@ -20,20 +24,17 @@ pub struct StatsResponse {
|
|||||||
pub db_update: Option<u64>,
|
pub db_update: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command for Stats {
|
impl CommandResponse for StatsResponse {
|
||||||
type Response = StatsResponse;
|
fn into_response_enum(self) -> crate::Response {
|
||||||
const COMMAND: &'static str = "stats";
|
todo!()
|
||||||
|
|
||||||
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
|
|
||||||
debug_assert!(parts.next().is_none());
|
|
||||||
|
|
||||||
Ok((Request::Stats, ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_response(
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> 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);
|
||||||
@@ -54,3 +55,8 @@ impl Command for Stats {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command for Stats {
|
||||||
|
type Request = StatsRequest;
|
||||||
|
type Response = StatsResponse;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,14 +3,19 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::common::{Audio, BoolOrOneshot, SongId, SongPosition};
|
use crate::{
|
||||||
|
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
|
||||||
use crate::commands::{
|
response_tokenizer::{
|
||||||
Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
|
ResponseAttributes, get_and_parse_optional_property, get_and_parse_property,
|
||||||
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,
|
||||||
@@ -60,119 +65,122 @@ pub struct StatusResponse {
|
|||||||
pub last_loaded_playlist: Option<String>,
|
pub last_loaded_playlist: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
impl CommandResponse for StatusResponse {
|
||||||
fn parse_status_response(
|
fn into_response_enum(self) -> crate::Response {
|
||||||
parts: ResponseAttributes<'_>,
|
todo!()
|
||||||
) -> Result<StatusResponse, ResponseParserError> {
|
}
|
||||||
let parts: HashMap<&str, GenericResponseValue> = parts.into();
|
|
||||||
let partition = get_property!(parts, "partition", Text).to_string();
|
|
||||||
|
|
||||||
let volume = match get_property!(parts, "volume", Text) {
|
fn from_response_enum(response: crate::Response) -> Option<Self> {
|
||||||
"-1" => None,
|
todo!()
|
||||||
volume => Some(
|
}
|
||||||
volume
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| ResponseParserError::InvalidProperty("volume", volume))?,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let repeat = match get_property!(parts, "repeat", Text) {
|
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
|
||||||
"0" => Ok(false),
|
let parts: HashMap<_, _> = parts.into_map()?;
|
||||||
"1" => Ok(true),
|
let partition = get_property!(parts, "partition", Text).to_string();
|
||||||
repeat => Err(ResponseParserError::InvalidProperty("repeat", repeat)),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let random = match get_property!(parts, "random", Text) {
|
let volume = match get_property!(parts, "volume", Text) {
|
||||||
"0" => Ok(false),
|
"-1" => None,
|
||||||
"1" => Ok(true),
|
volume => Some(volume.parse().map_err(|_| {
|
||||||
random => Err(ResponseParserError::InvalidProperty("random", random)),
|
ResponseParserError::InvalidProperty("volume".to_string(), volume.to_string())
|
||||||
}?;
|
})?),
|
||||||
|
};
|
||||||
|
|
||||||
let single = get_and_parse_property!(parts, "single", Text);
|
let repeat = match get_property!(parts, "repeat", Text) {
|
||||||
let consume = get_and_parse_property!(parts, "consume", Text);
|
"0" => Ok(false),
|
||||||
let playlist: u32 = get_and_parse_property!(parts, "playlist", Text);
|
"1" => Ok(true),
|
||||||
let playlist_length: u64 = get_and_parse_property!(parts, "playlistlength", Text);
|
repeat => Err(ResponseParserError::InvalidProperty(
|
||||||
let state: StatusResponseState = get_and_parse_property!(parts, "state", Text);
|
"repeat".to_string(),
|
||||||
let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text);
|
repeat.to_string(),
|
||||||
let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text);
|
)),
|
||||||
let next_song: Option<SongPosition> = get_and_parse_optional_property!(parts, "nextsong", 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 random = match get_property!(parts, "random", Text) {
|
||||||
Some(time) => {
|
"0" => Ok(false),
|
||||||
let mut parts = time.split(':');
|
"1" => Ok(true),
|
||||||
let elapsed = parts
|
random => Err(ResponseParserError::InvalidProperty(
|
||||||
.next()
|
"random".to_string(),
|
||||||
.ok_or(ResponseParserError::SyntaxError(0, time))?
|
random.to_string(),
|
||||||
.parse()
|
)),
|
||||||
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
|
}?;
|
||||||
let duration = parts
|
|
||||||
.next()
|
|
||||||
.ok_or(ResponseParserError::SyntaxError(0, time))?
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
|
|
||||||
Some((elapsed, duration))
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let elapsed = get_and_parse_optional_property!(parts, "elapsed", Text);
|
let single = get_and_parse_property!(parts, "single", Text);
|
||||||
let duration = get_and_parse_optional_property!(parts, "duration", Text);
|
let consume = get_and_parse_property!(parts, "consume", Text);
|
||||||
let bitrate = get_and_parse_optional_property!(parts, "bitrate", Text);
|
let playlist: u32 = get_and_parse_property!(parts, "playlist", Text);
|
||||||
let xfade = get_and_parse_optional_property!(parts, "xfade", Text);
|
let playlist_length: u64 = get_and_parse_property!(parts, "playlistlength", Text);
|
||||||
let mixrampdb = get_and_parse_optional_property!(parts, "mixrampdb", Text);
|
let state: StatusResponseState = get_and_parse_property!(parts, "state", Text);
|
||||||
let mixrampdelay = get_and_parse_optional_property!(parts, "mixrampdelay", Text);
|
let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text);
|
||||||
let audio = get_and_parse_optional_property!(parts, "audio", Text);
|
let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text);
|
||||||
let updating_db = get_and_parse_optional_property!(parts, "updating_db", Text);
|
let next_song: Option<SongPosition> =
|
||||||
let error = get_and_parse_optional_property!(parts, "error", Text);
|
get_and_parse_optional_property!(parts, "nextsong", Text);
|
||||||
let last_loaded_playlist =
|
let next_song_id: Option<SongId> =
|
||||||
get_and_parse_optional_property!(parts, "last_loaded_playlist", Text);
|
get_and_parse_optional_property!(parts, "nextsongid", Text);
|
||||||
|
|
||||||
Ok(StatusResponse {
|
let time = match get_optional_property!(parts, "time", Text) {
|
||||||
partition,
|
Some(time) => {
|
||||||
volume,
|
let mut parts = time.split(':');
|
||||||
repeat,
|
let elapsed = parts
|
||||||
random,
|
.next()
|
||||||
single,
|
.ok_or(ResponseParserError::SyntaxError(0, time.to_string()))?
|
||||||
consume,
|
.parse()
|
||||||
playlist,
|
.map_err(|_| {
|
||||||
playlist_length,
|
ResponseParserError::InvalidProperty("time".to_string(), time.to_string())
|
||||||
state,
|
})?;
|
||||||
song,
|
let duration = parts
|
||||||
song_id,
|
.next()
|
||||||
next_song,
|
.ok_or(ResponseParserError::SyntaxError(0, time.to_string()))?
|
||||||
next_song_id,
|
.parse()
|
||||||
time,
|
.map_err(|_| {
|
||||||
elapsed,
|
ResponseParserError::InvalidProperty("time".to_string(), time.to_string())
|
||||||
duration,
|
})?;
|
||||||
bitrate,
|
Some((elapsed, duration))
|
||||||
xfade,
|
}
|
||||||
mixrampdb,
|
None => None,
|
||||||
mixrampdelay,
|
};
|
||||||
audio,
|
|
||||||
updating_db,
|
let elapsed = get_and_parse_optional_property!(parts, "elapsed", Text);
|
||||||
error,
|
let duration = get_and_parse_optional_property!(parts, "duration", Text);
|
||||||
last_loaded_playlist,
|
let bitrate = get_and_parse_optional_property!(parts, "bitrate", Text);
|
||||||
})
|
let xfade = get_and_parse_optional_property!(parts, "xfade", Text);
|
||||||
|
let mixrampdb = get_and_parse_optional_property!(parts, "mixrampdb", Text);
|
||||||
|
let mixrampdelay = get_and_parse_optional_property!(parts, "mixrampdelay", Text);
|
||||||
|
let audio = get_and_parse_optional_property!(parts, "audio", Text);
|
||||||
|
let updating_db = get_and_parse_optional_property!(parts, "updating_db", Text);
|
||||||
|
let error = get_and_parse_optional_property!(parts, "error", Text);
|
||||||
|
let last_loaded_playlist =
|
||||||
|
get_and_parse_optional_property!(parts, "last_loaded_playlist", Text);
|
||||||
|
|
||||||
|
Ok(StatusResponse {
|
||||||
|
partition,
|
||||||
|
volume,
|
||||||
|
repeat,
|
||||||
|
random,
|
||||||
|
single,
|
||||||
|
consume,
|
||||||
|
playlist,
|
||||||
|
playlist_length,
|
||||||
|
state,
|
||||||
|
song,
|
||||||
|
song_id,
|
||||||
|
next_song,
|
||||||
|
next_song_id,
|
||||||
|
time,
|
||||||
|
elapsed,
|
||||||
|
duration,
|
||||||
|
bitrate,
|
||||||
|
xfade,
|
||||||
|
mixrampdb,
|
||||||
|
mixrampdelay,
|
||||||
|
audio,
|
||||||
|
updating_db,
|
||||||
|
error,
|
||||||
|
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)]
|
||||||
@@ -207,7 +215,7 @@ mod tests {
|
|||||||
"# };
|
"# };
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Status::parse_raw_response(contents),
|
Status::parse_raw_response(contents.as_bytes()),
|
||||||
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
Reference in New Issue
Block a user