39 Commits

Author SHA1 Message Date
oysteikt e469a8eb3c WIP 2025-05-07 10:54:28 +02:00
oysteikt 5a5870d7de flake.lock: bump
Build and test / check (push) Has been cancelled
Build and test / docs (push) Has been cancelled
Build and test / build (push) Has been cancelled
Build and test / test (push) Has been cancelled
2025-05-07 10:43:24 +02:00
oysteikt e5303954c5 Cargo.lock: bump
Build and test / build (push) Has been cancelled
Build and test / check (push) Has been cancelled
Build and test / test (push) Has been cancelled
Build and test / docs (push) Has been cancelled
2025-05-05 10:15:00 +02:00
oysteikt c95d39a9ec flake.lock: bump
Build and test / build (push) Has been cancelled
Build and test / check (push) Has been cancelled
Build and test / test (push) Has been cancelled
Build and test / docs (push) Has been cancelled
2025-05-05 10:13:44 +02:00
oysteikt e910d29aa4 Rust edition 2024
Build and test / build (push) Successful in 59s
Build and test / check (push) Failing after 1m2s
Build and test / test (push) Successful in 1m42s
Build and test / docs (push) Successful in 1m19s
2025-02-26 16:39:34 +01:00
oysteikt 58a06bd930 common/types: move stuff from requests to types
Build and test / build (push) Successful in 56s
Build and test / check (push) Failing after 59s
Build and test / docs (push) Successful in 1m25s
Build and test / test (push) Successful in 1m38s
2025-02-25 12:15:39 +01:00
oysteikt 380a4aed2c commands: deduplicate logic in macros, add more macros
Build and test / build (push) Successful in 59s
Build and test / check (push) Failing after 1m2s
Build and test / test (push) Successful in 1m40s
Build and test / docs (push) Successful in 1m27s
2025-02-23 19:26:46 +01:00
oysteikt ed7f9a6917 commands: implement some more response parsers
Build and test / build (push) Successful in 1m1s
Build and test / test (push) Successful in 1m37s
Build and test / docs (push) Successful in 1m16s
Build and test / check (push) Failing after 1m0s
2025-02-23 16:41:15 +01:00
oysteikt 432f16ae2b Cargo fmt
Build and test / build (push) Successful in 57s
Build and test / check (push) Failing after 1m2s
Build and test / docs (push) Has been cancelled
Build and test / test (push) Has been cancelled
2025-02-23 16:40:06 +01:00
oysteikt 44ba3eb4dc commands: move ResponseAttributes parser out of Command
Build and test / build (push) Successful in 1m0s
Build and test / check (push) Failing after 52s
Build and test / test (push) Successful in 1m45s
Build and test / docs (push) Successful in 1m23s
2025-02-23 16:33:34 +01:00
oysteikt d80ebfc4cf flake.lock: bump
Build and test / check (push) Failing after 51s
Build and test / build (push) Successful in 1m1s
Build and test / docs (push) Has been cancelled
Build and test / test (push) Has been cancelled
2025-02-23 16:31:49 +01:00
oysteikt 9f07114401 common: move types into separate files 2025-02-23 16:31:49 +01:00
oysteikt 5e0fe71feb Move project from Projects to Grzegorz
Build and test / build (push) Successful in 58s
Build and test / check (push) Failing after 59s
Build and test / test (push) Successful in 1m33s
Build and test / docs (push) Successful in 1m4s
2025-01-06 16:32:13 +01:00
oysteikt 366f56da9e commands: fix some syntax errors reporting literals
Build and test / build (push) Successful in 59s
Build and test / check (push) Failing after 58s
Build and test / docs (push) Successful in 1m21s
Build and test / test (push) Successful in 1m37s
2024-12-14 00:19:25 +01:00
oysteikt 9cef59eb88 Implement some more commands 2024-12-14 00:14:02 +01:00
oysteikt abacb54c8d commands: add missing debug asserts
Build and test / build (push) Failing after 16s
Build and test / test (push) Failing after 15s
Build and test / check (push) Failing after 59s
Build and test / docs (push) Successful in 1m17s
2024-12-13 19:31:50 +01:00
oysteikt 86183e56ad commands: add some TODOs for assumptions made about syntax 2024-12-13 19:31:48 +01:00
oysteikt 6a9cd00275 response: remove leftover response types, add error codes
Build and test / check (push) Failing after 1m2s
Build and test / build (push) Successful in 1m3s
Build and test / test (push) Successful in 1m37s
Build and test / docs (push) Successful in 1m16s
2024-12-13 18:30:16 +01:00
oysteikt 9cb92741a4 Implement some more commands
Build and test / check (push) Failing after 13s
Build and test / test (push) Failing after 14s
Build and test / build (push) Successful in 57s
Build and test / docs (push) Successful in 1m12s
2024-12-13 18:20:03 +01:00
oysteikt cea3d3da04 commands: add a few TODOs
Build and test / build (push) Successful in 57s
Build and test / check (push) Failing after 57s
Build and test / docs (push) Successful in 1m8s
Build and test / test (push) Successful in 1m27s
2024-12-13 17:16:16 +01:00
oysteikt 87fa93fb1c format 2024-12-13 17:16:16 +01:00
oysteikt cf0db472e8 commands: verify key uniqueness for ResponseAttributes -> HashMap 2024-12-13 17:16:16 +01:00
oysteikt 4f6392e376 commands: use real Filter type 2024-12-13 17:16:16 +01:00
oysteikt 2fb97a9964 Move repo to Projects 2024-12-13 17:16:15 +01:00
oysteikt 48beaa7732 examples/mpd-client: format 2024-12-13 16:59:40 +01:00
oysteikt 2bd53462d7 .gitea/build-and-test: init
Build and test / check (push) Failing after 5m36s
Build and test / build (push) Successful in 10m23s
Build and test / test (push) Failing after 6m16s
Build and test / docs (push) Successful in 10m25s
2024-12-10 21:48:20 +01:00
oysteikt edeb0e3b78 .envrc: init 2024-12-05 18:10:11 +01:00
oysteikt 2ee6bbc582 Add more commands 2024-12-02 21:00:22 +01:00
oysteikt 3e512092bd Add existing command parsers to the main request parser 2024-12-01 19:16:44 +01:00
oysteikt 08104b3537 Implement some more commands 2024-12-01 18:31:34 +01:00
oysteikt 1561ae2e80 prefer unimplemented! for unimplemented functions 2024-11-30 03:36:00 +01:00
oysteikt d45502a43e commands: add derives for a few response types 2024-11-30 03:31:52 +01:00
oysteikt 4fada75fe9 commands: handle some more response parsing 2024-11-30 03:28:22 +01:00
oysteikt 184f9fc215 commands/stats: parse_response 2024-11-30 02:28:29 +01:00
oysteikt 74074bf9c7 commands: make macros usable without use statements 2024-11-30 02:28:10 +01:00
oysteikt 84f061a79f commands: use different datastructure for response attrs 2024-11-30 02:18:06 +01:00
oysteikt 472be6c083 flake.lock: bump 2024-11-30 01:57:57 +01:00
oysteikt 49e070a41d Continued development 2024-11-30 01:57:45 +01:00
oysteikt 8293c6e6e5 Initial commit 2024-11-05 22:47:35 +01:00
188 changed files with 3977 additions and 11687 deletions
-2
View File
@@ -1,2 +0,0 @@
[registries]
pvv-git = { index = "sparse+https://git.pvv.ntnu.no/api/packages/Grzegorz/cargo/" }
+18 -18
View File
@@ -7,20 +7,20 @@ on:
jobs:
build:
runs-on: debian-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build
run: cargo build --verbose --release
run: cargo build --all-features --verbose --release
check:
runs-on: debian-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -31,13 +31,12 @@ jobs:
run: cargo fmt -- --check
- name: Check clippy
# run: cargo clippy -- --deny warnings
run: cargo clippy
run: cargo clippy --all-features -- --deny warnings
test:
runs-on: debian-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: cargo-bins/cargo-binstall@main
- name: Install rust toolchain
@@ -50,7 +49,7 @@ jobs:
- name: Run tests
run: |
cargo nextest run --release --no-fail-fast
cargo nextest run --all-features --release --no-fail-fast
env:
RUST_LOG: "trace"
RUSTFLAGS: "-Cinstrument-coverage"
@@ -84,13 +83,13 @@ jobs:
target: ${{ gitea.ref_name }}/coverage/
username: gitea-web
ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }}
host: pages.pvv.ntnu.no
known-hosts: "pages.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2QjfFB+city1SYqltkVqWACfo1j37k+oQQfj13mtgg"
host: bekkalokk.pvv.ntnu.no
known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"
docs:
runs-on: debian-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
- name: Install latest nightly toolchain
uses: actions-rs/toolchain@v1
@@ -99,14 +98,15 @@ jobs:
override: true
- name: Build docs
run: cargo doc --document-private-items --release
run: cargo doc --all-features --document-private-items --release
- name: Transfer files
uses: https://git.pvv.ntnu.no/Projects/rsync-action@v2
uses: https://git.pvv.ntnu.no/Projects/rsync-action@v1
with:
source: target/doc/
target: ${{ gitea.ref_name }}/docs/
username: gitea-web
ssh-key: ${{ secrets.WEB_SYNC_SSH_KEY }}
host: pages.pvv.ntnu.no
known-hosts: "pages.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2QjfFB+city1SYqltkVqWACfo1j37k+oQQfj13mtgg"
host: bekkalokk.pvv.ntnu.no
known-hosts: "bekkalokk.pvv.ntnu.no ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEI6VSaDrMG8+flg4/AeHlAFIen8RUzWh6URQKqFegSx"
+1 -1
View File
@@ -1,3 +1,3 @@
/target
result
result-*
result-*
Generated
+14 -819
View File
@@ -2,495 +2,26 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "ascii-canvas"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891"
dependencies = [
"term",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
version = "1.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "empidee"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"futures-util",
"indoc",
"lalrpop",
"lalrpop-util",
"paste",
"pretty_assertions",
"serde",
"thiserror",
"tokio",
]
[[package]]
name = "ena"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1"
dependencies = [
"log",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-macro"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-io",
"futures-macro",
"futures-task",
"memchr",
"pin-project-lite",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "indoc"
version = "2.0.7"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
dependencies = [
"rustversion",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "js-sys"
version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "keccak"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
dependencies = [
"cpufeatures",
]
[[package]]
name = "lalrpop"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501"
dependencies = [
"ascii-canvas",
"bit-set",
"ena",
"itertools",
"lalrpop-util",
"petgraph",
"pico-args",
"regex",
"regex-syntax",
"sha3",
"string_cache",
"term",
"unicode-xid",
"walkdir",
]
[[package]]
name = "lalrpop-util"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733"
dependencies = [
"regex-automata",
"rustversion",
]
[[package]]
name = "libc"
version = "0.2.184"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "mio"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi",
"windows-sys",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "petgraph"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]]
name = "pretty_assertions"
@@ -504,394 +35,58 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.106"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.228"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha3"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
"digest",
"keccak",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "siphasher"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "string_cache"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
dependencies = [
"new_debug_unreachable",
"parking_lot",
"phf_shared",
"precomputed-hash",
]
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "term"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1"
dependencies = [
"windows-sys",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
dependencies = [
"bytes",
"libc",
"mio",
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.24"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "yansi"
+2 -19
View File
@@ -4,7 +4,6 @@ version = "0.1.0"
authors = [
"Øystein Tveit <oysteikt@pvv.ntnu.no>"
]
license = "MIT"
description = "A rust implementation of the mpd protocol, both client and serverside"
repository = "https://git.pvv.ntnu.no/Grzegorz/empidee"
documentation = "https://pages.pvv.ntnu.no/Grzegorz/empidee/main/docs/empidee/"
@@ -12,24 +11,8 @@ edition = "2024"
rust-version = "1.85.0"
[dependencies]
chrono = { version = "0.4.44", features = ["serde"] }
futures-util = { version = "0.3.32", 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.18"
tokio = { version = "1.50.0", optional = true, features = ["io-util"] }
[features]
default = ["tokio"]
futures = ["dep:futures-util"]
tokio = ["dep:tokio"]
serde = { version = "1.0.210", features = ["derive"] }
[dev-dependencies]
anyhow = "1.0.102"
indoc = "2.0.7"
indoc = "2.0.5"
pretty_assertions = "1.4.1"
tokio = { version = "1.50.0", features = ["macros", "net", "rt"] }
[build-dependencies]
lalrpop = "0.22.2"
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 Programvareverkstedet <projects@pvv.ntnu.no>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-8
View File
@@ -1,8 +0,0 @@
fn main() {
lalrpop::process_root().unwrap();
// let debug_mode = std::env::var("PROFILE").unwrap() == "debug";
// lalrpop::Configuration::new()
// .emit_comments(debug_mode)
// .process()
// .unwrap();
}
+2 -15
View File
@@ -1,16 +1,3 @@
use empidee::MpdClient;
#[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).await?;
println!(
"Connected to MPD server: {}",
client.get_mpd_version().unwrap_or("unknown")
);
client.play(None).await?;
Ok(())
fn main() {
todo!()
}
Generated
+6 -6
View File
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1775036866,
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"lastModified": 1746461020,
"narHash": "sha256-7+pG1I9jvxNlmln4YgnlW4o+w0TZX24k688mibiFDUE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"rev": "3730d8a308f94996a9ba7c7138ede69c1b9ac4ae",
"type": "github"
},
"original": {
@@ -29,11 +29,11 @@
]
},
"locked": {
"lastModified": 1775099554,
"narHash": "sha256-3xBsGnGDLOFtnPZ1D3j2LU19wpAlYefRKTlkv648rU0=",
"lastModified": 1746585402,
"narHash": "sha256-Pf+ufu6bYNA1+KQKHnGMNEfTwpD9ZIcAeLoE2yPWIP0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "8d6387ed6d8e6e6672fd3ed4b61b59d44b124d99",
"rev": "72dd969389583664f87aa348b3458f2813693617",
"type": "github"
},
"original": {
-42
View File
@@ -36,52 +36,10 @@
default = pkgs.mkShell {
nativeBuildInputs = [
toolchain
pkgs.cargo-edit
];
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
};
});
packages = forAllSystems (system: pkgs: toolchain: let
rustPlatform = pkgs.makeRustPlatform {
cargo = toolchain;
rustc = toolchain;
};
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./.cargo
./Cargo.toml
./Cargo.lock
./LICENSE
./README.md
./build.rs
./examples
./rustfmt.toml
./src
];
};
in {
default = self.packages.${system}.example-binaries;
example-binaries = rustPlatform.buildRustPackage {
pname = "empidee-example-bins";
inherit (cargoToml.package) version;
inherit src;
cargoLock.lockFile = ./Cargo.lock;
cargoBuildFlags = [ "--examples" ];
# TODO: avoid the binary variant with the hash at the end
postInstall = ''
find "$releaseDir"/examples -type f -executable -exec install -Dt "$out/bin" {} \;
'';
doCheck = true;
useNextest = true;
};
});
};
}
-130
View File
@@ -1,130 +0,0 @@
//! A high-level client for interacting with an Mpd server.
//!
//! The client provides methods for common operations such as playing, pausing, and
//! managing the playlist, and returns the expected response types directly
//! from its methods.
use crate::{Request, commands::*, types::SongPosition};
#[cfg(feature = "futures")]
use futures_util::{
AsyncBufReadExt,
io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter},
};
#[cfg(feature = "tokio")]
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufStream};
use thiserror::Error;
pub struct MpdClient<'a, T>
where
T: AsyncWrite + AsyncRead + Unpin,
{
stream: BufStream<&'a mut T>,
mpd_version: Option<String>,
}
#[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 async fn new(connection: &'a mut T) -> Result<Self, MpdClientError> {
let mut client = MpdClient {
stream: BufStream::new(connection),
mpd_version: None,
};
client.read_initial_mpd_version().await?;
Ok(client)
}
pub async fn wrap_existing(connection: &'a mut T, mpd_version: Option<String>) -> Self {
MpdClient {
stream: BufStream::new(connection),
mpd_version,
}
}
pub fn into_connection(self) -> &'a mut T {
self.stream.into_inner()
}
pub fn get_mpd_version(&self) -> Option<&str> {
self.mpd_version.as_deref()
}
async fn read_initial_mpd_version(&mut self) -> Result<(), MpdClientError> {
let mut version_line = String::new();
self.stream
.read_line(&mut version_line)
.await
.map_err(MpdClientError::ConnectionError)?;
self.mpd_version = Some(version_line.trim().to_string());
Ok(())
}
async fn read_response(&mut self) -> Result<Vec<u8>, MpdClientError> {
let mut response = Vec::new();
loop {
let mut line = Vec::new();
let bytes_read = self
.stream
.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.stream
.write_all(payload.as_bytes())
.await
.map_err(MpdClientError::ConnectionError)?;
self.stream
.flush()
.await
.map_err(MpdClientError::ConnectionError)?;
let response_bytes = self.read_response().await?;
let response = PlayResponse::parse_raw(&response_bytes)?;
Ok(response)
}
}
+339 -762
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -1,11 +1,11 @@
mod disableoutput;
mod enableoutput;
mod outputs;
mod outputset;
mod toggleoutput;
pub mod disableoutput;
pub mod enableoutput;
pub mod outputs;
pub mod outputset;
pub mod toggleoutput;
pub use disableoutput::*;
pub use enableoutput::*;
pub use outputs::*;
pub use outputset::*;
pub use toggleoutput::*;
pub use disableoutput::DisableOutput;
pub use enableoutput::EnableOutput;
pub use outputs::Outputs;
pub use outputset::OutputSet;
pub use toggleoutput::ToggleOutput;
@@ -1,15 +1,26 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::AudioOutputId,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct DisableOutput;
single_item_command_request!(DisableOutput, "disableoutput", AudioOutputId);
empty_command_response!(DisableOutput);
impl Command for DisableOutput {
type Request = DisableOutputRequest;
type Response = DisableOutputResponse;
type Response = ();
const COMMAND: &'static str = "disableoutput";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::DisableOutput(output_id.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -1,15 +1,26 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::AudioOutputId,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct EnableOutput;
single_item_command_request!(EnableOutput, "enableoutput", AudioOutputId);
empty_command_response!(EnableOutput);
impl Command for EnableOutput {
type Request = EnableOutputRequest;
type Response = EnableOutputResponse;
type Response = ();
const COMMAND: &'static str = "enableoutput";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::EnableOutput(output_id.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+13 -173
View File
@@ -2,195 +2,35 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{ResponseAttributes, expect_property_type},
types::AudioOutputId,
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Outputs;
empty_command_request!(Outputs, "outputs");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OutputsResponse(Vec<Output>);
impl OutputsResponse {
pub fn new(outputs: Vec<Output>) -> Self {
OutputsResponse(outputs)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Output {
pub id: AudioOutputId,
pub id: u64,
pub name: String,
pub plugin: String,
pub enabled: bool,
pub attribute: HashMap<String, String>,
}
impl Output {
pub fn new(
id: AudioOutputId,
name: String,
plugin: String,
enabled: bool,
attribute: HashMap<String, String>,
) -> Self {
Output {
id,
name,
plugin,
enabled,
attribute,
}
}
}
impl CommandResponse for OutputsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts = parts.into_vec()?;
let result_len = parts.iter().filter(|(k, _)| *k == "outputid").count();
let mut outputs = Vec::with_capacity(result_len);
let mut id: Option<AudioOutputId> = None;
let mut name: Option<String> = None;
let mut plugin: Option<String> = None;
let mut enabled: Option<bool> = None;
let mut attributes: HashMap<String, String> = HashMap::new();
for (k, v) in parts.into_iter() {
match k {
"outputid" => {
// Reset and store the previous output if all fields are present
if let (Some(id), Some(name), Some(plugin), Some(enabled)) =
(id.take(), name.take(), plugin.take(), enabled.take())
{
outputs.push(Output {
id,
name,
plugin,
enabled,
attribute: attributes,
});
}
attributes = HashMap::new();
let id_s = expect_property_type!(Some(v), k, Text);
id = Some(
id_s.parse()
.map_err(|_| ResponseParserError::SyntaxError(0, id_s.to_string()))?,
);
}
"outputname" => {
name = Some(expect_property_type!(Some(v), k, Text).to_string());
}
"plugin" => {
plugin = Some(expect_property_type!(Some(v), k, Text).to_string());
}
"outputenabled" => {
let val_s = expect_property_type!(Some(v), k, Text);
let val: u64 = val_s
.parse::<u64>()
.map_err(|_| ResponseParserError::SyntaxError(0, val_s.to_string()))?;
enabled = Some(val != 0);
}
"attribute" => {
let value = expect_property_type!(Some(v), k, Text);
let mut parts = value.splitn(2, '=');
let attr_key = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, value.to_string()))?
.to_string();
let attr_value = parts
.next()
.ok_or(ResponseParserError::SyntaxError(0, value.to_string()))?
.to_string();
attributes.insert(attr_key, attr_value);
}
_ => {
return Err(ResponseParserError::UnexpectedProperty(k.to_string()));
}
}
}
// Store the last output if all fields are present
if let (Some(id), Some(name), Some(plugin), Some(enabled)) =
(id.take(), name.take(), plugin.take(), enabled.take())
{
outputs.push(Output {
id,
name,
plugin,
enabled,
attribute: attributes,
});
}
Ok(OutputsResponse(outputs))
}
}
pub type OutputsResponse = Vec<Output>;
impl Command for Outputs {
type Request = OutputsRequest;
type Response = OutputsResponse;
}
const COMMAND: &'static str = "outputs";
#[cfg(test)]
mod tests {
use indoc::indoc;
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Outputs, ""))
}
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
},
},
])),
);
fn parse_response(
_parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
+29 -81
View File
@@ -1,87 +1,35 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::AudioOutputId,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct OutputSet;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OutputSetRequest {
pub output_id: AudioOutputId,
pub attribute_name: String,
pub attribute_value: String,
}
impl OutputSetRequest {
pub fn new(output_id: AudioOutputId, attribute_name: String, attribute_value: String) -> Self {
Self {
output_id,
attribute_name,
attribute_value,
}
}
}
impl CommandRequest for OutputSetRequest {
const COMMAND: &'static str = "outputset";
const MIN_ARGS: u32 = 3;
const MAX_ARGS: Option<u32> = Some(3);
fn into_request_enum(self) -> crate::Request {
crate::Request::OutputSet(self.output_id, self.attribute_name, self.attribute_value)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::OutputSet(output_id, attribute_name, attribute_value) => {
Some(OutputSetRequest {
output_id,
attribute_name,
attribute_value,
})
}
_ => None,
}
}
fn serialize(&self) -> String {
format!(
"{} {} {} {}",
Self::COMMAND,
self.output_id,
self.attribute_name,
self.attribute_value
)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let output_id = parts.next().ok_or(Self::missing_arguments_error(0))?;
let output_id = output_id
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "AudioOutputId",
raw_input: output_id.to_string(),
})?;
let attribute_name = parts.next().ok_or(Self::missing_arguments_error(1))?;
let attribute_value = parts.next().ok_or(Self::missing_arguments_error(2))?;
Self::throw_if_too_many_arguments(parts)?;
Ok(OutputSetRequest {
output_id,
attribute_name: attribute_name.to_string(),
attribute_value: attribute_value.to_string(),
})
}
}
empty_command_response!(OutputSet);
impl Command for OutputSet {
type Request = OutputSetRequest;
type Response = OutputSetResponse;
type Response = ();
const COMMAND: &'static str = "outputset";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let attribute_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let attribute_value = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((
Request::OutputSet(
output_id.to_string(),
attribute_name.to_string(),
attribute_value.to_string(),
),
"",
))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -1,15 +1,26 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::AudioOutputId,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct ToggleOutput;
single_item_command_request!(ToggleOutput, "toggleoutput", AudioOutputId);
empty_command_response!(ToggleOutput);
impl Command for ToggleOutput {
type Request = ToggleOutputRequest;
type Response = ToggleOutputResponse;
type Response = ();
const COMMAND: &'static str = "toggleoutput";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_id = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::ToggleOutput(output_id.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+10 -10
View File
@@ -1,11 +1,11 @@
mod channels;
mod readmessages;
mod sendmessage;
mod subscribe;
mod unsubscribe;
pub mod channels;
pub mod readmessages;
pub mod sendmessage;
pub mod subscribe;
pub mod unsubscribe;
pub use channels::*;
pub use readmessages::*;
pub use sendmessage::*;
pub use subscribe::*;
pub use unsubscribe::*;
pub use channels::Channels;
pub use readmessages::ReadMessages;
pub use sendmessage::SendMessage;
pub use subscribe::Subscribe;
pub use unsubscribe::Unsubscribe;
+18 -36
View File
@@ -1,45 +1,36 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{ResponseAttributes, expect_property_type},
types::ChannelName,
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
expect_property_type,
};
pub struct Channels;
empty_command_request!(Channels, "channels");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChannelsResponse {
pub channels: Vec<ChannelName>,
pub channels: Vec<String>,
}
impl ChannelsResponse {
pub fn new(channels: Vec<ChannelName>) -> Self {
ChannelsResponse { channels }
}
}
impl Command for Channels {
type Response = ChannelsResponse;
const COMMAND: &'static str = "channels";
impl CommandResponse for ChannelsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Channels, ""))
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
let mut channel_names = Vec::with_capacity(parts.len());
for (key, value) in parts {
debug_assert!(key == "channels");
let channel_name = expect_property_type!(Some(value), "channels", Text);
let channel_name = channel_name
.parse()
.map_err(|_| ResponseParserError::SyntaxError(0, channel_name.to_string()))?;
channel_names.push(channel_name);
channel_names.push(channel_name.to_string());
}
Ok(ChannelsResponse {
@@ -48,11 +39,6 @@ impl CommandResponse for ChannelsResponse {
}
}
impl Command for Channels {
type Request = ChannelsRequest;
type Response = ChannelsResponse;
}
#[cfg(test)]
mod tests {
use super::*;
@@ -67,15 +53,11 @@ mod tests {
channels: baz
OK
"};
let response = Channels::parse_raw_response(response.as_bytes()).unwrap();
let response = Channels::parse_raw_response(response).unwrap();
assert_eq!(
response,
ChannelsResponse {
channels: vec![
"foo".parse().unwrap(),
"bar".parse().unwrap(),
"baz".parse().unwrap(),
]
channels: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
}
);
}
+31 -56
View File
@@ -1,47 +1,31 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{ResponseAttributes, expect_property_type},
types::ChannelName,
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
expect_property_type,
};
pub struct ReadMessages;
empty_command_request!(ReadMessages, "readmessages");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadMessagesResponse(Vec<ReadMessagesResponseEntry>);
impl ReadMessagesResponse {
pub fn new(entries: Vec<ReadMessagesResponseEntry>) -> Self {
ReadMessagesResponse(entries)
}
pub struct ReadMessagesResponse {
pub messages: Vec<(String, String)>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadMessagesResponseEntry {
channel: ChannelName,
message: String,
}
impl Command for ReadMessages {
type Response = ReadMessagesResponse;
const COMMAND: &'static str = "readmessages";
impl ReadMessagesResponseEntry {
pub fn new(channel: ChannelName, message: String) -> Self {
ReadMessagesResponseEntry { channel, message }
}
}
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
impl CommandResponse for ReadMessagesResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
Ok((Request::ReadMessages, ""))
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
debug_assert!(parts.len() % 2 == 0);
let mut messages = Vec::with_capacity(parts.len() / 2);
@@ -50,28 +34,23 @@ impl CommandResponse for ReadMessagesResponse {
let (ckey, cvalue) = channel_message_pair[0];
let (mkey, mvalue) = channel_message_pair[1];
debug_assert!(ckey == "channel");
debug_assert!(mkey == "message");
let channel = expect_property_type!(Some(cvalue), "channel", Text);
let channel = channel
.parse()
.map_err(|_| ResponseParserError::SyntaxError(0, channel.to_string()))?;
if ckey != "channel" {
return Err(ResponseParserError::UnexpectedProperty(ckey));
}
if mkey != "message" {
return Err(ResponseParserError::UnexpectedProperty(mkey));
}
let channel = expect_property_type!(Some(cvalue), "channel", Text).to_string();
let message = expect_property_type!(Some(mvalue), "message", Text).to_string();
messages.push(ReadMessagesResponseEntry { channel, message });
messages.push((channel, message));
}
Ok(ReadMessagesResponse(messages))
Ok(ReadMessagesResponse { messages })
}
}
impl Command for ReadMessages {
type Request = ReadMessagesRequest;
type Response = ReadMessagesResponse;
}
#[cfg(test)]
mod tests {
use indoc::indoc;
@@ -87,19 +66,15 @@ mod tests {
message: message2
OK
"};
let result = ReadMessages::parse_raw_response(input.as_bytes());
let result = ReadMessages::parse_raw_response(input);
assert_eq!(
result,
Ok(ReadMessagesResponse(vec![
ReadMessagesResponseEntry {
channel: "channel1".parse().unwrap(),
message: "message1".to_string(),
},
ReadMessagesResponseEntry {
channel: "channel2".parse().unwrap(),
message: "message2".to_string(),
},
]))
Ok(ReadMessagesResponse {
messages: vec![
("channel1".to_string(), "message1".to_string()),
("channel2".to_string(), "message2".to_string()),
]
})
);
}
}
+17 -55
View File
@@ -1,67 +1,29 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::ChannelName,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct SendMessage;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SendMessageRequest {
pub channel: ChannelName,
pub message: String,
}
impl SendMessageRequest {
pub fn new(channel: ChannelName, message: String) -> Self {
Self { channel, message }
}
}
impl CommandRequest for SendMessageRequest {
impl Command for SendMessage {
type Response = ();
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(),
})?;
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let channel = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
// TODO: SplitWhitespace::remainder() is unstable, use when stable
let message = parts.collect::<Vec<_>>().join(" ");
Ok(SendMessageRequest { channel, message })
debug_assert!(!message.is_empty());
Ok((Request::SendMessage(channel.to_string(), message), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SendMessage);
impl Command for SendMessage {
type Request = SendMessageRequest;
type Response = SendMessageResponse;
}
+20 -9
View File
@@ -1,15 +1,26 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::ChannelName,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Subscribe;
single_item_command_request!(Subscribe, "subscribe", ChannelName);
empty_command_response!(Subscribe);
impl Command for Subscribe {
type Request = SubscribeRequest;
type Response = SubscribeResponse;
type Response = ();
const COMMAND: &'static str = "subscribe";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let channel_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::Subscribe(channel_name.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+20 -9
View File
@@ -1,15 +1,26 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::ChannelName,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Unsubscribe;
single_item_command_request!(Unsubscribe, "unsubscribe", ChannelName);
empty_command_response!(Unsubscribe);
impl Command for Unsubscribe {
type Request = UnsubscribeRequest;
type Response = UnsubscribeResponse;
type Response = ();
const COMMAND: &'static str = "unsubscribe";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let channel_name = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
debug_assert!(parts.next().is_none());
Ok((Request::Unsubscribe(channel_name.to_string()), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+36 -36
View File
@@ -1,37 +1,37 @@
mod binary_limit;
mod close;
mod kill;
mod password;
mod ping;
mod protocol;
mod protocol_all;
mod protocol_available;
mod protocol_clear;
mod protocol_disable;
mod protocol_enable;
mod tag_types;
mod tag_types_all;
mod tag_types_available;
mod tag_types_clear;
mod tag_types_disable;
mod tag_types_enable;
mod tag_types_reset;
pub mod binary_limit;
pub mod close;
pub mod kill;
pub mod password;
pub mod ping;
pub mod protocol;
pub mod protocol_all;
pub mod protocol_available;
pub mod protocol_clear;
pub mod protocol_disable;
pub mod protocol_enable;
pub mod tag_types;
pub mod tag_types_all;
pub mod tag_types_available;
pub mod tag_types_clear;
pub mod tag_types_disable;
pub mod tag_types_enable;
pub mod tag_types_reset;
pub use binary_limit::*;
pub use close::*;
pub use kill::*;
pub use password::*;
pub use ping::*;
pub use protocol::*;
pub use protocol_all::*;
pub use protocol_available::*;
pub use protocol_clear::*;
pub use protocol_disable::*;
pub use protocol_enable::*;
pub use tag_types::*;
pub use tag_types_all::*;
pub use tag_types_available::*;
pub use tag_types_clear::*;
pub use tag_types_disable::*;
pub use tag_types_enable::*;
pub use tag_types_reset::*;
pub use binary_limit::BinaryLimit;
pub use close::Close;
pub use kill::Kill;
pub use password::Password;
pub use ping::Ping;
pub use protocol::Protocol;
pub use protocol_all::ProtocolAll;
pub use protocol_available::ProtocolAvailable;
pub use protocol_clear::ProtocolClear;
pub use protocol_disable::ProtocolDisable;
pub use protocol_enable::ProtocolEnable;
pub use tag_types::TagTypes;
pub use tag_types_all::TagTypesAll;
pub use tag_types_available::TagTypesAvailable;
pub use tag_types_clear::TagTypesClear;
pub use tag_types_disable::TagTypesDisable;
pub use tag_types_enable::TagTypesEnable;
pub use tag_types_reset::TagTypesReset;
@@ -1,12 +1,27 @@
use crate::commands::{Command, empty_command_response, single_item_command_request};
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct BinaryLimit;
single_item_command_request!(BinaryLimit, "binarylimit", u64);
empty_command_response!(BinaryLimit);
impl Command for BinaryLimit {
type Request = BinaryLimitRequest;
type Response = BinaryLimitResponse;
type Response = ();
const COMMAND: &'static str = "binarylimit";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let limit = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let limit = limit
.parse()
.map_err(|_| RequestParserError::SyntaxError(0, limit.to_string()))?;
debug_assert!(parts.next().is_none());
Ok((Request::BinaryLimit(limit), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+17 -7
View File
@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Close;
empty_command_request!(Close, "close");
empty_command_response!(Close);
impl Command for Close {
type Request = CloseRequest;
type Response = CloseResponse;
type Response = ();
const COMMAND: &'static str = "close";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Close, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+17 -7
View File
@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Kill;
empty_command_request!(Kill, "kill");
empty_command_response!(Kill);
impl Command for Kill {
type Request = KillRequest;
type Response = KillResponse;
type Response = ();
const COMMAND: &'static str = "kill";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Kill, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+22 -7
View File
@@ -1,12 +1,27 @@
use crate::commands::{Command, empty_command_response, single_item_command_request};
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Password;
single_item_command_request!(Password, "password", String);
empty_command_response!(Password);
impl Command for Password {
type Request = PasswordRequest;
type Response = PasswordResponse;
type Response = ();
const COMMAND: &'static str = "password";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let password = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok((Request::Password(password), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+17 -7
View File
@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Ping;
empty_command_request!(Ping, "ping");
empty_command_response!(Ping);
impl Command for Ping {
type Request = PingRequest;
type Response = PingResponse;
type Response = ();
const COMMAND: &'static str = "ping";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Ping, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+15 -8
View File
@@ -1,15 +1,22 @@
use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct Protocol;
empty_command_request!(Protocol, "protocol");
multi_item_command_response!(Protocol, "feature", String);
impl Command for Protocol {
type Request = ProtocolRequest;
type Response = ProtocolResponse;
type Response = ();
const COMMAND: &'static str = "protocol";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Protocol, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
@@ -1,12 +1,23 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::{
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct ProtocolAll;
empty_command_request!(ProtocolAll, "protocol all");
empty_command_response!(ProtocolAll);
impl Command for ProtocolAll {
type Request = ProtocolAllRequest;
type Response = ProtocolAllResponse;
type Response = ();
const COMMAND: &'static str = "protocol all";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ProtocolAll, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -1,15 +1,22 @@
use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct ProtocolAvailable;
empty_command_request!(ProtocolAvailable, "protocol available");
multi_item_command_response!(ProtocolAvailable, "feature", String);
impl Command for ProtocolAvailable {
type Request = ProtocolAvailableRequest;
type Response = ProtocolAvailableResponse;
type Response = ();
const COMMAND: &'static str = "protocol available";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ProtocolAvailable, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
@@ -1,12 +1,23 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::{
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct ProtocolClear;
empty_command_request!(ProtocolClear, "protocol clear");
empty_command_response!(ProtocolClear);
impl Command for ProtocolClear {
type Request = ProtocolClearRequest;
type Response = ProtocolClearResponse;
type Response = ();
const COMMAND: &'static str = "protocol clear";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ProtocolClear, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -1,70 +1,35 @@
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::Feature,
Request,
commands::{
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
};
pub struct ProtocolDisable;
pub struct ProtocolDisableRequest(Vec<Feature>);
impl ProtocolDisableRequest {
pub fn new(features: Vec<Feature>) -> Self {
ProtocolDisableRequest(features)
}
}
impl CommandRequest for ProtocolDisableRequest {
impl Command for ProtocolDisable {
type Response = ();
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> {
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0));
return Err(RequestParserError::UnexpectedEOF);
}
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>>()?;
// TODO: verify that the features are split by whitespace
let mut features = Vec::new();
for feature in parts {
features.push(feature.to_string());
}
Ok(ProtocolDisableRequest(features))
Ok((Request::ProtocolDisable(features), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(ProtocolDisable);
impl Command for ProtocolDisable {
type Request = ProtocolDisableRequest;
type Response = ProtocolDisableResponse;
}
@@ -1,70 +1,35 @@
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::Feature,
Request,
commands::{
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
};
pub struct ProtocolEnable;
pub struct ProtocolEnableRequest(Vec<Feature>);
impl ProtocolEnableRequest {
pub fn new(features: Vec<Feature>) -> Self {
ProtocolEnableRequest(features)
}
}
impl CommandRequest for ProtocolEnableRequest {
impl Command for ProtocolEnable {
type Response = ();
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> {
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0));
return Err(RequestParserError::UnexpectedEOF);
}
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>>()?;
// TODO: verify that the features are split by whitespace
let mut features = Vec::new();
for feature in parts {
features.push(feature.to_string());
}
Ok(ProtocolEnableRequest(features))
Ok((Request::ProtocolEnable(features), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(ProtocolEnable);
impl Command for ProtocolEnable {
type Request = ProtocolEnableRequest;
type Response = ProtocolEnableResponse;
}
+25 -7
View File
@@ -1,15 +1,33 @@
use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
expect_property_type,
};
pub struct TagTypes;
empty_command_request!(TagTypes, "tagtypes");
multi_item_command_response!(TagTypes, "tagtype", String);
pub type TagTypesResponse = Vec<String>;
impl Command for TagTypes {
type Request = TagTypesRequest;
type Response = TagTypesResponse;
const COMMAND: &'static str = "tagtypes";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::TagTypes, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: Vec<_> = parts.into();
let mut tagtypes = Vec::with_capacity(parts.len());
for (key, value) in parts.into_iter() {
debug_assert_eq!(key, "tagtype");
let tagtype = expect_property_type!(Some(value), "tagtype", Text).to_string();
tagtypes.push(tagtype);
}
Ok(tagtypes)
}
}
@@ -1,12 +1,23 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::{
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct TagTypesAll;
empty_command_request!(TagTypesAll, "tagtypes all");
empty_command_response!(TagTypesAll);
impl Command for TagTypesAll {
type Request = TagTypesAllRequest;
type Response = TagTypesAllResponse;
type Response = ();
const COMMAND: &'static str = "tagtypes all";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::TagTypesAll, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -1,15 +1,22 @@
use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct TagTypesAvailable;
empty_command_request!(TagTypesAvailable, "tagtypes available");
multi_item_command_response!(TagTypesAvailable, "tagtype", String);
impl Command for TagTypesAvailable {
type Request = TagTypesAvailableRequest;
type Response = TagTypesAvailableResponse;
type Response = ();
const COMMAND: &'static str = "tagtypes available";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::TagTypesAvailable, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
@@ -1,12 +1,23 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::{
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct TagTypesClear;
empty_command_request!(TagTypesClear, "tagtypes clear");
empty_command_response!(TagTypesClear);
impl Command for TagTypesClear {
type Request = TagTypesClearRequest;
type Response = TagTypesClearResponse;
type Response = ();
const COMMAND: &'static str = "tagtypes clear";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::TagTypesClear, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -1,72 +1,35 @@
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::TagName,
Request,
commands::{
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
};
pub struct TagTypesDisable;
pub struct TagTypesDisableRequest(Vec<TagName>);
impl TagTypesDisableRequest {
pub fn new(tag_types: Vec<TagName>) -> Self {
TagTypesDisableRequest(tag_types)
}
}
impl CommandRequest for TagTypesDisableRequest {
impl Command for TagTypesDisable {
type Response = ();
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> {
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0));
return Err(RequestParserError::UnexpectedEOF);
}
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>>()?;
// 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(TagTypesDisableRequest(tag_types))
Ok((Request::TagTypesDisable(tag_types), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(TagTypesDisable);
impl Command for TagTypesDisable {
type Request = TagTypesDisableRequest;
type Response = TagTypesDisableResponse;
}
@@ -1,72 +1,35 @@
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::TagName,
Request,
commands::{
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
};
pub struct TagTypesEnable;
pub struct TagTypesEnableRequest(Vec<TagName>);
impl TagTypesEnableRequest {
pub fn new(tag_types: Vec<TagName>) -> Self {
TagTypesEnableRequest(tag_types)
}
}
impl CommandRequest for TagTypesEnableRequest {
impl Command for TagTypesEnable {
type Response = ();
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> {
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0));
return Err(RequestParserError::UnexpectedEOF);
}
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>>()?;
// 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(TagTypesEnableRequest(tag_types))
Ok((Request::TagTypesEnable(tag_types), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(TagTypesEnable);
impl Command for TagTypesEnable {
type Request = TagTypesEnableRequest;
type Response = TagTypesEnableResponse;
}
@@ -1,72 +1,35 @@
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::TagName,
Request,
commands::{
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
};
pub struct TagTypesReset;
pub struct TagTypesResetRequest(Vec<TagName>);
impl TagTypesResetRequest {
pub fn new(tag_types: Vec<TagName>) -> Self {
TagTypesResetRequest(tag_types)
}
}
impl CommandRequest for TagTypesResetRequest {
impl Command for TagTypesReset {
type Response = ();
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> {
fn parse_request(parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let mut parts = parts.peekable();
if parts.peek().is_none() {
return Err(Self::missing_arguments_error(0));
return Err(RequestParserError::UnexpectedEOF);
}
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>>()?;
// 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(TagTypesResetRequest(tag_types))
Ok((Request::TagTypesReset(tag_types), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(TagTypesReset);
impl Command for TagTypesReset {
type Request = TagTypesResetRequest;
type Response = TagTypesResetResponse;
}
+18 -18
View File
@@ -1,19 +1,19 @@
mod next;
mod pause;
mod play;
mod playid;
mod previous;
mod seek;
mod seekcur;
mod seekid;
mod stop;
pub mod next;
pub mod pause;
pub mod play;
pub mod playid;
pub mod previous;
pub mod seek;
pub mod seekcur;
pub mod seekid;
pub mod stop;
pub use next::*;
pub use pause::*;
pub use play::*;
pub use playid::*;
pub use previous::*;
pub use seek::*;
pub use seekcur::*;
pub use seekid::*;
pub use stop::*;
pub use next::Next;
pub use pause::Pause;
pub use play::Play;
pub use playid::PlayId;
pub use previous::Previous;
pub use seek::Seek;
pub use seekcur::SeekCur;
pub use seekid::SeekId;
pub use stop::Stop;
+17 -7
View File
@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Next;
empty_command_request!(Next, "next");
empty_command_response!(Next);
impl Command for Next {
type Request = NextRequest;
type Response = NextResponse;
type Response = ();
const COMMAND: &'static str = "next";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Next, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+19 -51
View File
@@ -1,63 +1,31 @@
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Pause;
pub struct PauseRequest(Option<bool>);
impl PauseRequest {
pub fn new(state: Option<bool>) -> Self {
PauseRequest(state)
}
}
impl CommandRequest for PauseRequest {
impl Command for Pause {
type Response = ();
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> {
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
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),
Some("0") => Ok((Request::Pause(Some(false)), "")),
Some("1") => Ok((Request::Pause(Some(true)), "")),
Some(s) => Err(RequestParserError::SyntaxError(0, s.to_string())),
None => Ok((Request::Pause(None), "")),
};
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
result.map(PauseRequest)
result
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Pause);
impl Command for Pause {
type Request = PauseRequest;
type Response = PauseResponse;
}
+27 -8
View File
@@ -1,15 +1,34 @@
use crate::{
commands::{Command, empty_command_response, single_optional_item_command_request},
types::SongPosition,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::SongPosition,
};
pub struct Play;
single_optional_item_command_request!(Play, "play", SongPosition);
empty_command_response!(Play);
impl Command for Play {
type Request = PlayRequest;
type Response = PlayResponse;
type Response = ();
const COMMAND: &'static str = "play";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let songpos = match parts.next() {
Some(s) => s
.parse::<SongPosition>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Play(songpos), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+27 -8
View File
@@ -1,15 +1,34 @@
use crate::{
commands::{Command, empty_command_response, single_optional_item_command_request},
types::SongId,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::SongId,
};
pub struct PlayId;
single_optional_item_command_request!(PlayId, "playid", SongId);
empty_command_response!(PlayId);
impl Command for PlayId {
type Request = PlayIdRequest;
type Response = PlayIdResponse;
type Response = ();
const COMMAND: &'static str = "playid";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let songid = match parts.next() {
Some(s) => s
.parse::<SongId>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::PlayId(songid), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+17 -7
View File
@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Previous;
empty_command_request!(Previous, "previous");
empty_command_response!(Previous);
impl Command for Previous {
type Request = PreviousRequest;
type Response = PreviousResponse;
type Response = ();
const COMMAND: &'static str = "previous";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Previous, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+25 -63
View File
@@ -1,79 +1,41 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::{SongPosition, TimeWithFractions},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::{SongPosition, TimeWithFractions},
};
pub struct Seek;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SeekRequest {
pub songpos: SongPosition,
pub time: TimeWithFractions,
}
impl SeekRequest {
pub fn new(songpos: SongPosition, time: TimeWithFractions) -> Self {
Self { songpos, time }
}
}
impl CommandRequest for SeekRequest {
impl Command for Seek {
type Response = ();
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> {
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let songpos = match parts.next() {
Some(s) => {
s.parse::<SongPosition>()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "SongPosition",
raw_input: s.to_owned(),
})?
}
None => return Err(Self::missing_arguments_error(0)),
Some(s) => s
.parse::<SongPosition>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
let time = match parts.next() {
Some(t) => t.parse::<TimeWithFractions>().map_err(|_| {
RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "TimeWithFractions",
raw_input: t.to_owned(),
}
})?,
None => return Err(Self::missing_arguments_error(1)),
Some(t) => t
.parse::<TimeWithFractions>()
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(SeekRequest { songpos, time })
Ok((Request::Seek(songpos, time), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Seek);
impl Command for Seek {
type Request = SeekRequest;
type Response = SeekResponse;
}
+27 -76
View File
@@ -1,102 +1,53 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::{SeekMode, TimeWithFractions},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::{SeekMode, TimeWithFractions},
};
pub struct SeekCur;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SeekCurRequest {
pub mode: SeekMode,
pub time: TimeWithFractions,
}
impl SeekCurRequest {
pub fn new(mode: SeekMode, time: TimeWithFractions) -> Self {
Self { mode, time }
}
}
impl CommandRequest for SeekCurRequest {
impl Command for SeekCur {
type Response = ();
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> {
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let time_raw = match parts.next() {
Some(t) => t,
None => return Err(Self::missing_arguments_error(0)),
None => return Err(RequestParserError::UnexpectedEOF),
};
Self::throw_if_too_many_arguments(parts)?;
// TODO: DRY
let (mode, time) = match time_raw {
t if t.starts_with('+') => (
SeekMode::Relative,
t[1..].parse::<TimeWithFractions>().map_err(|_| {
RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "TimeWithFractions",
raw_input: t[1..].to_owned(),
}
})?,
t[1..]
.parse::<TimeWithFractions>()
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
),
t if t.starts_with('-') => (
SeekMode::RelativeReverse,
t[1..].parse::<TimeWithFractions>().map_err(|_| {
RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "TimeWithFractions",
raw_input: t[1..].to_owned(),
}
})?,
SeekMode::Relative,
-t[1..]
.parse::<TimeWithFractions>()
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
),
t => (
SeekMode::Absolute,
t.parse::<TimeWithFractions>().map_err(|_| {
RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "TimeWithFractions",
raw_input: t.to_owned(),
}
})?,
t.parse::<TimeWithFractions>()
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
),
};
debug_assert!(time >= 0.0);
debug_assert!(parts.next().is_none());
Ok(SeekCurRequest { mode, time })
Ok((Request::SeekCur(mode, time), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SeekCur);
impl Command for SeekCur {
type Request = SeekCurRequest;
type Response = SeekCurResponse;
}
+23 -59
View File
@@ -1,77 +1,41 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::{SongId, TimeWithFractions},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::{SongId, TimeWithFractions},
};
pub struct SeekId;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SeekIdRequest {
pub songid: SongId,
pub time: TimeWithFractions,
}
impl SeekIdRequest {
pub fn new(songid: SongId, time: TimeWithFractions) -> Self {
Self { songid, time }
}
}
impl CommandRequest for SeekIdRequest {
impl Command for SeekId {
type Response = ();
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> {
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let songid = match parts.next() {
Some(s) => s
.parse::<SongId>()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "SongId",
raw_input: s.to_owned(),
})?,
None => return Err(Self::missing_arguments_error(0)),
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
let time = match parts.next() {
Some(t) => t.parse::<TimeWithFractions>().map_err(|_| {
RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "TimeWithFractions",
raw_input: t.to_owned(),
}
})?,
None => return Err(Self::missing_arguments_error(1)),
Some(t) => t
.parse::<TimeWithFractions>()
.map_err(|_| RequestParserError::SyntaxError(0, t.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(SeekIdRequest { songid, time })
Ok((Request::SeekId(songid, time), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SeekId);
impl Command for SeekId {
type Request = SeekIdRequest;
type Response = SeekIdResponse;
}
+17 -7
View File
@@ -1,12 +1,22 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Stop;
empty_command_request!(Stop, "stop");
empty_command_response!(Stop);
impl Command for Stop {
type Request = StopRequest;
type Response = StopResponse;
type Response = ();
const COMMAND: &'static str = "stop";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Stop, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+8 -8
View File
@@ -1,9 +1,9 @@
mod listmounts;
mod listneighbors;
mod mount;
mod unmount;
pub mod listmounts;
pub mod listneighbors;
pub mod mount;
pub mod unmount;
pub use listmounts::*;
pub use listneighbors::*;
pub use mount::*;
pub use unmount::*;
pub use listmounts::ListMounts;
pub use listneighbors::ListNeighbors;
pub use mount::Mount;
pub use unmount::Unmount;
+12 -29
View File
@@ -1,39 +1,22 @@
use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct ListMounts;
empty_command_request!(ListMounts, "listmounts");
multi_item_command_response!(ListMounts, "mount", String);
impl Command for ListMounts {
type Request = ListMountsRequest;
type Response = ListMountsResponse;
}
type Response = Vec<(String, String)>;
const COMMAND: &'static str = "listmounts";
#[cfg(test)]
mod tests {
use indoc::indoc;
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ListMounts, ""))
}
use super::*;
#[test]
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(),
]))
);
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
@@ -1,58 +1,22 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{ResponseAttributes, expect_property_type},
Request,
commands::{Command, RequestParserResult, ResponseAttributes, ResponseParserError},
};
pub struct ListNeighbors;
empty_command_request!(ListNeighbors, "listneighbors");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListNeighborsResponse(HashMap<String, String>);
impl ListNeighborsResponse {
pub fn new(map: HashMap<String, String>) -> Self {
ListNeighborsResponse(map)
}
}
impl CommandResponse for ListNeighborsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: Vec<_> = parts.into_vec()?;
debug_assert!(parts.len() % 2 == 0);
let mut result = HashMap::with_capacity(parts.len() / 2);
for channel_message_pair in parts.chunks_exact(2) {
let (neigh_key, neigh_value) = channel_message_pair[0];
let (name_key, name_value) = channel_message_pair[1];
debug_assert!(neigh_key == "neighbor");
debug_assert!(name_key == "name");
let neighbor = expect_property_type!(Some(neigh_value), "neighbor", Text).to_string();
let name = expect_property_type!(Some(name_value), "name", Text).to_string();
result.insert(neighbor, name);
}
Ok(ListNeighborsResponse(result))
}
}
impl Command for ListNeighbors {
type Request = ListNeighborsRequest;
type Response = ListNeighborsResponse;
type Response = Vec<(String, String)>;
const COMMAND: &'static str = "listneighbors";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ListNeighbors, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
+21 -60
View File
@@ -1,75 +1,36 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::{MountPath, Uri},
Request,
commands::{
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
};
pub struct Mount;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MountRequest {
pub path: MountPath,
pub uri: Uri,
}
impl MountRequest {
pub fn new(path: MountPath, uri: Uri) -> Self {
MountRequest { path, uri }
}
}
impl CommandRequest for MountRequest {
impl Command for Mount {
type Response = ();
const COMMAND: &'static str = "mount";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = Some(2);
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(),
})?;
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let path = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
let uri = parts
.next()
.ok_or(Self::missing_arguments_error(1))?
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(MountRequest { path, uri })
Ok((Request::Mount(path, uri), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Mount);
impl Command for Mount {
type Request = MountRequest;
type Response = MountResponse;
}
+22 -56
View File
@@ -1,65 +1,31 @@
use crate::{
Request,
commands::{
Command, CommandRequest, RequestParserError, RequestTokenizer, empty_command_response,
Command, RequestParserError, RequestParserResult, ResponseAttributes, ResponseParserError,
},
types::MountPath,
};
pub struct Unmount;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct UnmountRequest(MountPath);
impl UnmountRequest {
pub fn new(path: MountPath) -> Self {
UnmountRequest(path)
}
}
impl CommandRequest for UnmountRequest {
const COMMAND: &'static str = "unmount";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(1);
fn into_request_enum(self) -> crate::Request {
crate::Request::Unmount(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Unmount(item) => Some(UnmountRequest(item)),
_ => None,
}
}
fn serialize(&self) -> String {
debug_assert!(self.0.to_str().is_some());
format!(
"{} {}\n",
Self::COMMAND,
self.0.to_str().unwrap_or("<invalid path>")
)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let path = parts.next().ok_or(Self::missing_arguments_error(0))?;
let path =
path.parse::<MountPath>()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "MountPath",
raw_input: path.to_string(),
})?;
Self::throw_if_too_many_arguments(parts)?;
Ok(UnmountRequest(path))
}
}
empty_command_response!(Unmount);
impl Command for Unmount {
type Request = UnmountRequest;
type Response = UnmountResponse;
type Response = ();
const COMMAND: &'static str = "unmount";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let path = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok((Request::Unmount(path), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+36 -36
View File
@@ -1,37 +1,37 @@
mod albumart;
mod count;
mod find;
mod findadd;
mod getfingerprint;
mod list;
mod listall;
mod listallinfo;
mod listfiles;
mod lsinfo;
mod readcomments;
mod readpicture;
mod rescan;
mod search;
mod searchadd;
mod searchaddpl;
mod searchcount;
mod update;
pub mod albumart;
pub mod count;
pub mod find;
pub mod findadd;
pub mod getfingerprint;
pub mod list;
pub mod listall;
pub mod listallinfo;
pub mod listfiles;
pub mod lsinfo;
pub mod readcomments;
pub mod readpicture;
pub mod rescan;
pub mod search;
pub mod searchadd;
pub mod searchaddpl;
pub mod searchcount;
pub mod update;
pub use albumart::*;
pub use count::*;
pub use find::*;
pub use findadd::*;
pub use getfingerprint::*;
pub use list::*;
pub use listall::*;
pub use listallinfo::*;
pub use listfiles::*;
pub use lsinfo::*;
pub use readcomments::*;
pub use readpicture::*;
pub use rescan::*;
pub use search::*;
pub use searchadd::*;
pub use searchaddpl::*;
pub use searchcount::*;
pub use update::*;
pub use albumart::AlbumArt;
pub use count::Count;
pub use find::Find;
pub use findadd::FindAdd;
pub use getfingerprint::GetFingerprint;
pub use list::List;
pub use listall::ListAll;
pub use listallinfo::ListAllInfo;
pub use listfiles::ListFiles;
pub use lsinfo::LsInfo;
pub use readcomments::ReadComments;
pub use readpicture::ReadPicture;
pub use rescan::Rescan;
pub use search::Search;
pub use searchadd::SearchAdd;
pub use searchaddpl::SearchAddPl;
pub use searchcount::SearchCount;
pub use update::Update;
+28 -84
View File
@@ -1,97 +1,46 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_and_parse_property, get_property},
types::{Offset, Uri},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError, get_and_parse_property, get_property,
},
common::Offset,
};
pub struct AlbumArt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlbumArtRequest {
uri: Uri,
offset: Offset,
}
impl AlbumArtRequest {
pub fn new(uri: Uri, offset: Offset) -> Self {
AlbumArtRequest { uri, offset }
}
}
impl CommandRequest for AlbumArtRequest {
const COMMAND: &'static str = "albumart";
const MIN_ARGS: u32 = 2;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request {
crate::Request::AlbumArt(self.uri, self.offset)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::AlbumArt(uri, offset) => Some(AlbumArtRequest { uri, offset }),
_ => None,
}
}
fn serialize(&self) -> String {
format!("{} {} {}\n", Self::COMMAND, self.uri, self.offset)
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(Self::missing_arguments_error(0)),
};
let offset = match parts.next() {
Some(s) => s
.parse::<Offset>()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "Offset",
raw_input: s.to_string(),
})?,
None => return Err(Self::missing_arguments_error(1)),
};
Self::throw_if_too_many_arguments(parts)?;
Ok(AlbumArtRequest {
uri: uri.to_string(),
offset,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlbumArtResponse {
pub size: usize,
pub binary: Vec<u8>,
}
impl AlbumArtResponse {
pub fn new(size: usize, binary: Vec<u8>) -> Self {
AlbumArtResponse { size, binary }
}
}
impl Command for AlbumArt {
type Response = AlbumArtResponse;
const COMMAND: &'static str = "albumart";
impl CommandResponse for AlbumArtResponse {
fn into_response_enum(self) -> crate::Response {
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::AlbumArt(uri.to_string(), offset), ""))
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
let size = get_and_parse_property!(parts, "size", Text);
@@ -100,8 +49,3 @@ impl CommandResponse for AlbumArtResponse {
Ok(AlbumArtResponse { size, binary })
}
}
impl Command for AlbumArt {
type Request = AlbumArtRequest;
type Response = AlbumArtResponse;
}
+30 -99
View File
@@ -1,112 +1,48 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
filter::Filter,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_and_parse_property},
types::GroupType,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError, get_and_parse_property,
},
filter::parse_filter,
};
pub struct Count;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CountRequest {
filter: Filter,
group: Option<GroupType>,
}
impl CountRequest {
pub fn new(filter: Filter, group: Option<GroupType>) -> Self {
CountRequest { filter, group }
}
}
impl CommandRequest for CountRequest {
const COMMAND: &'static str = "count";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request {
crate::Request::Count(self.filter, self.group)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Count(filter, group) => Some(CountRequest { filter, group }),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(group) = self.group.as_ref() {
cmd.push_str(&format!(" group {}", group));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let group = if let Some("group") = parts.next() {
let group = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
argument_index: 1,
keyword: "group",
})?;
Some(
group
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "GroupType",
raw_input: group.to_owned(),
})?,
)
} else {
None
};
Self::throw_if_too_many_arguments(parts)?;
Ok(CountRequest { filter, group })
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CountResponse {
pub songs: usize,
pub playtime: u64,
}
impl CountResponse {
pub fn new(songs: usize, playtime: u64) -> Self {
CountResponse { songs, playtime }
}
}
impl Command for Count {
type Response = CountResponse;
const COMMAND: &'static str = "count";
impl CommandResponse for CountResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let filter = parse_filter(&mut parts)?;
let group = if let Some("group") = parts.next() {
let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
Some(
group
.parse()
.map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?,
)
} else {
None
};
debug_assert!(parts.next().is_none());
Ok((Request::Count(filter, group), ""))
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
let songs = get_and_parse_property!(parts, "songs", Text);
let playtime = get_and_parse_property!(parts, "playtime", Text);
@@ -114,8 +50,3 @@ impl CommandResponse for CountResponse {
Ok(CountResponse { songs, playtime })
}
}
impl Command for Count {
type Request = CountRequest;
type Response = CountResponse;
}
+24 -132
View File
@@ -1,159 +1,51 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
filter::Filter,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
types::{DbSelectionPrintResponse, DbSongInfo, Sort, WindowRange},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
filter::parse_filter,
};
pub struct Find;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FindRequest {
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
}
pub struct FindResponse {}
impl FindRequest {
pub fn new(filter: Filter, sort: Option<Sort>, window: Option<WindowRange>) -> Self {
Self {
filter,
sort,
window,
}
}
}
impl CommandRequest for FindRequest {
impl Command for Find {
type Response = FindResponse;
const COMMAND: &'static str = "find";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(3);
fn into_request_enum(self) -> crate::Request {
crate::Request::Find(self.filter, self.sort, self.window)
}
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let filter = parse_filter(&mut parts)?;
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Find(filter, sort, window) => Some(FindRequest {
filter,
sort,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let mut argument_index_counter = 0;
let mut sort_or_window = parts.next();
let mut sort = None;
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(
s.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "Sort",
raw_input: s.to_string(),
})?,
parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string(),
);
sort_or_window = parts.next();
}
let mut window = None;
if let Some("window") = sort_or_window {
argument_index_counter += 1;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
window = Some(
w.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
);
}
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(FindRequest {
filter,
sort,
window,
})
Ok((Request::Find(filter, sort, window), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FindResponse(Vec<DbSongInfo>);
impl FindResponse {
pub fn new(items: Vec<DbSongInfo>) -> Self {
FindResponse(items)
}
}
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;
}
+26 -123
View File
@@ -1,157 +1,60 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
filter::Filter,
request_tokenizer::RequestTokenizer,
types::{SongPosition, Sort, WindowRange},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
filter::parse_filter,
};
pub struct FindAdd;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FindAddRequest {
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
}
impl FindAddRequest {
pub fn new(
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
) -> Self {
Self {
filter,
sort,
window,
position,
}
}
}
impl CommandRequest for FindAddRequest {
impl Command for FindAdd {
type Response = ();
const COMMAND: &'static str = "findadd";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(4);
fn into_request_enum(self) -> crate::Request {
crate::Request::FindAdd(self.filter, self.sort, self.window, self.position)
}
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let filter = parse_filter(&mut parts)?;
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::FindAdd(filter, sort, window, position) => Some(FindAddRequest {
filter,
sort,
window,
position,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
if let Some(position) = &self.position {
cmd.push_str(&format!(" position {}", position));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let mut argument_index_counter = 0;
let mut sort_or_window_or_position = parts.next();
let mut sort = None;
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(
s.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "Sort",
raw_input: s.to_string(),
})?,
parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string(),
);
sort_or_window_or_position = parts.next();
}
let mut window = None;
if let Some("window") = sort_or_window_or_position {
argument_index_counter += 1;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
window = Some(
w.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
);
sort_or_window_or_position = parts.next();
}
let mut position = None;
if let Some("position") = sort_or_window_or_position {
argument_index_counter += 1;
let p = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "position",
argument_index: argument_index_counter,
})?;
let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
position = Some(
p.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "SongPosition",
raw_input: p.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?,
);
}
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(FindAddRequest {
filter,
sort,
window,
position,
})
Ok((Request::FindAdd(filter, sort, window, position), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(FindAdd);
impl Command for FindAdd {
type Request = FindAddRequest;
type Response = FindAddResponse;
}
+19 -28
View File
@@ -1,47 +1,38 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandResponse, ResponseParserError, single_item_command_request},
response_tokenizer::{ResponseAttributes, get_and_parse_property},
types::Uri,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError, get_and_parse_property,
};
pub struct GetFingerprint;
single_item_command_request!(GetFingerprint, "getfingerprint", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GetFingerprintResponse {
pub chromaprint: String,
}
impl GetFingerprintResponse {
pub fn new(chromaprint: String) -> Self {
Self { chromaprint }
}
}
impl Command for GetFingerprint {
type Response = GetFingerprintResponse;
const COMMAND: &'static str = "getfingerprint";
impl CommandResponse for GetFingerprintResponse {
fn into_response_enum(self) -> crate::Response {
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 from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
let chromaprint = get_and_parse_property!(parts, "chromaprint", Text);
Ok(GetFingerprintResponse { chromaprint })
}
}
impl Command for GetFingerprint {
type Request = GetFingerprintRequest;
type Response = GetFingerprintResponse;
}
+32 -165
View File
@@ -1,191 +1,58 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
filter::Filter,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
types::{GroupType, TagName, WindowRange},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError, expect_property_type,
},
filter::parse_filter,
};
pub struct List;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListRequest {
tagname: TagName,
filter: Option<Filter>,
groups: Vec<GroupType>,
window: Option<WindowRange>,
}
pub type ListResponse = Vec<String>;
impl ListRequest {
pub fn new(
tagname: TagName,
filter: Option<Filter>,
groups: Vec<GroupType>,
window: Option<WindowRange>,
) -> Self {
Self {
tagname,
filter,
groups,
window,
}
}
}
impl CommandRequest for ListRequest {
impl Command for List {
type Response = ListResponse;
const COMMAND: &'static str = "list";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request {
crate::Request::List(self.tagname, self.filter, self.groups, self.window)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::List(tagname, filter, groups, window) => Some(ListRequest {
tagname,
filter,
groups,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = match &self.filter {
Some(f) => format!("{} {} {}", Self::COMMAND, self.tagname, f),
None => format!("{} {}", Self::COMMAND, self.tagname),
};
for group in &self.groups {
cmd.push_str(&format!(" group {}", group));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let tagname = parts.next().ok_or(Self::missing_arguments_error(0))?;
let tagname = tagname
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let tagtype = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
let tagtype = tagtype
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "TagName",
raw_input: tagname.to_owned(),
})?;
.map_err(|_| RequestParserError::SyntaxError(1, tagtype.to_owned()))?;
let mut filter = None;
let mut groups = Vec::new();
let mut window = None;
// TODO: This should be optional
let filter = parse_filter(&mut parts)?;
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 =
let group = if let Some("group") = parts.next() {
let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
Some(
group
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 2,
expected_type: "GroupType",
raw_input: group.to_owned(),
})?;
groups.push(parsed_group);
next = parts.next();
}
.map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?,
)
} else {
None
};
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);
}
debug_assert!(parts.next().is_none());
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 ListResponse {
pub fn new(items: Vec<String>) -> Self {
ListResponse(items)
}
}
impl CommandResponse for ListResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
Ok((Request::List(tagtype, filter, group), ""))
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts_: Vec<_> = parts.into_vec()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!({
let key = parts_.first().map(|(k, _)| k);
parts_.iter().all(|(k, _)| k == key.unwrap())
let key = parts.0.first().map(|(k, _)| k);
parts.0.iter().all(|(k, _)| k == key.unwrap())
});
let list = parts_
let list = parts
.0
.into_iter()
.map(|(k, v)| Ok(expect_property_type!(Some(v), k, Text).to_string()))
.collect::<Result<Vec<_>, ResponseParserError>>()?;
Ok(ListResponse(list))
Ok(list)
}
}
impl Command for List {
type Request = ListRequest;
type Response = ListResponse;
}
+20 -155
View File
@@ -1,169 +1,34 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::ResponseAttributes,
types::{DbSelectionPrintResponse, Uri},
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
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
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListAllResponse(Vec<DbSelectionPrintResponse>);
impl ListAllResponse {
pub fn new(items: Vec<DbSelectionPrintResponse>) -> Self {
ListAllResponse(items)
}
}
impl CommandResponse for ListAllResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(ListAllResponse(result))
}
}
pub type ListAllResponse = Vec<String>;
impl Command for ListAll {
type Request = ListAllRequest;
type Response = ListAllResponse;
}
const COMMAND: &'static str = "listall";
#[cfg(test)]
mod tests {
use std::path::PathBuf;
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()?;
use crate::types::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo};
debug_assert!(parts.next().is_none());
use super::*;
Ok((Request::ListAll(uri), ""))
}
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
})
]))
)
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
+21 -112
View File
@@ -1,126 +1,35 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::ResponseAttributes,
types::{DbSelectionPrintResponse, Uri},
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
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
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListAllInfoResponse(Vec<DbSelectionPrintResponse>);
impl ListAllInfoResponse {
pub fn new(items: Vec<DbSelectionPrintResponse>) -> Self {
ListAllInfoResponse(items)
}
}
impl CommandResponse for ListAllInfoResponse {
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(ListAllInfoResponse(result))
}
}
// in addition to the metadata of each entry
pub type ListAllInfoResponse = Vec<String>;
impl Command for ListAllInfo {
type Request = ListAllInfoRequest;
type Response = ListAllInfoResponse;
}
const COMMAND: &'static str = "listallinfo";
#[cfg(test)]
mod tests {
use std::path::PathBuf;
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()?;
use crate::types::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo, Tag};
debug_assert!(parts.next().is_none());
use super::*;
Ok((Request::ListAllInfo(uri), ""))
}
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())
})
])),
)
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
+26 -45
View File
@@ -1,53 +1,34 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::ResponseAttributes,
types::{DbDirectoryInfo, DbSelectionPrintResponse, Uri},
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct ListFiles;
single_optional_item_command_request!(ListFiles, "listfiles", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListFilesResponse(Vec<DbDirectoryInfo>);
impl ListFilesResponse {
pub fn new(items: Vec<DbDirectoryInfo>) -> Self {
ListFilesResponse(items)
}
}
impl CommandResponse for ListFilesResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
DbSelectionPrintResponse::parse(parts)?
.into_iter()
.map(|i| match i {
DbSelectionPrintResponse::Directory(db_directory_info) => Ok(db_directory_info),
DbSelectionPrintResponse::Song(_db_song_info) => {
Err(ResponseParserError::UnexpectedProperty("file".to_string()))
}
DbSelectionPrintResponse::Playlist(_db_playlist_info) => Err(
ResponseParserError::UnexpectedProperty("playlist".to_string()),
),
})
.collect::<Result<Vec<_>, ResponseParserError>>()
.map(ListFilesResponse)
}
}
// TODO: fix this type
pub type ListFilesResponse = Vec<String>;
impl Command for ListFiles {
type Request = ListFilesRequest;
type Response = ListFilesResponse;
const COMMAND: &'static str = "listfiles";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let uri = parts
.next()
.map(|s| {
s.parse()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))
})
.transpose()?;
debug_assert!(parts.next().is_none());
Ok((Request::ListFiles(uri), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
+21 -88
View File
@@ -1,101 +1,34 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::ResponseAttributes,
types::{DbSelectionPrintResponse, Uri},
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct LsInfo;
single_optional_item_command_request!(LsInfo, "lsinfo", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LsInfoResponse(Vec<DbSelectionPrintResponse>);
impl LsInfoResponse {
pub fn new(items: Vec<DbSelectionPrintResponse>) -> Self {
LsInfoResponse(items)
}
}
impl CommandResponse for LsInfoResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let result = DbSelectionPrintResponse::parse(parts)?;
Ok(LsInfoResponse(result))
}
}
// TODO: fix this type
pub type LsInfoResponse = Vec<String>;
impl Command for LsInfo {
type Request = LsInfoRequest;
type Response = LsInfoResponse;
}
const COMMAND: &'static str = "lsinfo";
#[cfg(test)]
mod tests {
use std::path::PathBuf;
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()?;
use crate::types::{DbDirectoryInfo, DbPlaylistInfo};
debug_assert!(parts.next().is_none());
use super::*;
Ok((Request::LsInfo(uri), ""))
}
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())
})
])),
);
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
+26 -35
View File
@@ -1,53 +1,44 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandResponse, ResponseParserError, single_item_command_request},
response_tokenizer::{GenericResponseValue, ResponseAttributes},
types::Uri,
use crate::commands::{
Command, GenericResponseValue, Request, RequestParserError, RequestParserResult,
ResponseAttributes, ResponseParserError, expect_property_type,
};
pub struct ReadComments;
single_item_command_request!(ReadComments, "readcomments", Uri);
pub type ReadCommentsResponse = HashMap<String, String>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadCommentsResponse(HashMap<String, String>);
impl Command for ReadComments {
type Response = ReadCommentsResponse;
const COMMAND: &'static str = "readcomments";
impl ReadCommentsResponse {
pub fn new(comments: HashMap<String, String>) -> Self {
ReadCommentsResponse(comments)
}
}
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()))?;
impl CommandResponse for ReadCommentsResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
debug_assert!(parts.next().is_none());
Ok((Request::ReadComments(uri), ""))
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
let comments = parts
.iter()
.map(|(k, v)| match v {
GenericResponseValue::Text(s) => Ok((k.to_string(), s.to_string())),
GenericResponseValue::Binary(_) => {
Err(ResponseParserError::SyntaxError(1, k.to_string()))
}
.into_iter()
.map(|(k, v)| {
Ok((
k.to_string(),
expect_property_type!(Some(v), k, Text).to_string(),
))
})
.collect::<Result<HashMap<_, _>, ResponseParserError>>()?;
Ok(ReadCommentsResponse(comments))
Ok(comments)
}
}
impl Command for ReadComments {
type Request = ReadCommentsRequest;
type Response = ReadCommentsResponse;
}
+35 -98
View File
@@ -1,109 +1,51 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::{
ResponseAttributes, get_and_parse_property, get_optional_property, get_property,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError, get_and_parse_property, get_optional_property, get_property,
},
types::{Offset, Uri},
common::Offset,
};
pub struct ReadPicture;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadPictureRequest {
pub uri: Uri,
pub offset: Offset,
}
impl ReadPictureRequest {
pub fn new(uri: Uri, offset: Offset) -> Self {
Self { uri, 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 size: usize,
pub binary: Vec<u8>,
pub mimetype: Option<String>,
}
impl ReadPictureResponse {
pub fn new(size: usize, binary: Vec<u8>, mimetype: Option<String>) -> Self {
Self {
size,
binary,
mimetype,
impl Command for ReadPicture {
type Response = Option<ReadPictureResponse>;
const COMMAND: &'static str = "readpicture";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let uri = match parts.next() {
Some(s) => s,
None => return Err(RequestParserError::UnexpectedEOF),
};
let offset = match parts.next() {
Some(s) => s
.parse::<Offset>()
.map_err(|_| RequestParserError::SyntaxError(1, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::ReadPicture(uri.to_string(), offset), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
if parts.is_empty() {
return Ok(None);
}
}
}
impl CommandResponse for ReadPictureResponse {
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: 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);
@@ -111,15 +53,10 @@ impl CommandResponse for ReadPictureResponse {
let mimetype = get_optional_property!(parts, "mimetype", Text).map(|s| s.to_string());
Ok(ReadPictureResponse {
Ok(Some(ReadPictureResponse {
size,
binary,
mimetype,
})
}))
}
}
impl Command for ReadPicture {
type Request = ReadPictureRequest;
type Response = ReadPictureResponse;
}
+16 -30
View File
@@ -1,49 +1,35 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::{ResponseAttributes, get_and_parse_property},
types::Uri,
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
get_and_parse_property,
};
pub struct Rescan;
single_optional_item_command_request!(Rescan, "rescan", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RescanResponse {
pub updating_db: usize,
}
impl RescanResponse {
pub fn new(updating_db: usize) -> Self {
RescanResponse { updating_db }
}
}
impl Command for Rescan {
type Response = RescanResponse;
const COMMAND: &'static str = "rescan";
impl CommandResponse for RescanResponse {
fn into_response_enum(self) -> crate::Response {
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 from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
let updating_db = get_and_parse_property!(parts, "updating_db", Text);
Ok(RescanResponse { updating_db })
}
}
impl Command for Rescan {
type Request = RescanRequest;
type Response = RescanResponse;
}
+24 -132
View File
@@ -1,159 +1,51 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
filter::Filter,
request_tokenizer::RequestTokenizer,
response_tokenizer::ResponseAttributes,
types::{DbSelectionPrintResponse, DbSongInfo, Sort, WindowRange},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
filter::parse_filter,
};
pub struct Search;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchRequest {
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
}
pub struct SearchResponse {}
impl SearchRequest {
pub fn new(filter: Filter, sort: Option<Sort>, window: Option<WindowRange>) -> Self {
Self {
filter,
sort,
window,
}
}
}
impl CommandRequest for SearchRequest {
impl Command for Search {
type Response = SearchResponse;
const COMMAND: &'static str = "search";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(3);
fn into_request_enum(self) -> crate::Request {
crate::Request::Search(self.filter, self.sort, self.window)
}
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let filter = parse_filter(&mut parts)?;
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Search(filter, sort, window) => Some(SearchRequest {
filter,
sort,
window,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let mut argument_index_counter = 0;
let mut sort_or_window = parts.next();
let mut sort = None;
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(
s.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "Sort",
raw_input: s.to_string(),
})?,
parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string(),
);
sort_or_window = parts.next();
}
let mut window = None;
if let Some("window") = sort_or_window {
argument_index_counter += 1;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
window = Some(
w.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
);
}
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(SearchRequest {
filter,
sort,
window,
})
Ok((Request::Search(filter, sort, window), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchResponse(Vec<DbSongInfo>);
impl SearchResponse {
pub fn new(items: Vec<DbSongInfo>) -> Self {
SearchResponse(items)
}
}
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;
}
+26 -123
View File
@@ -1,157 +1,60 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
filter::Filter,
request_tokenizer::RequestTokenizer,
types::{SongPosition, Sort, WindowRange},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
filter::parse_filter,
};
pub struct SearchAdd;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchAddRequest {
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
}
impl SearchAddRequest {
pub fn new(
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
) -> Self {
Self {
filter,
sort,
window,
position,
}
}
}
impl CommandRequest for SearchAddRequest {
impl Command for SearchAdd {
type Response = ();
const COMMAND: &'static str = "searchadd";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(4);
fn into_request_enum(self) -> crate::Request {
crate::Request::SearchAdd(self.filter, self.sort, self.window, self.position)
}
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let filter = parse_filter(&mut parts)?;
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::SearchAdd(filter, sort, window, position) => Some(SearchAddRequest {
filter,
sort,
window,
position,
}),
_ => None,
}
}
fn serialize(&self) -> String {
let mut cmd = format!("{} {}", Self::COMMAND, self.filter);
if let Some(sort) = &self.sort {
cmd.push_str(&format!(" sort {}", sort));
}
if let Some(window) = &self.window {
cmd.push_str(&format!(" window {}", window));
}
if let Some(position) = &self.position {
cmd.push_str(&format!(" position {}", position));
}
cmd.push('\n');
cmd
}
fn parse(mut parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let filter = match parts.next() {
Some(f) => {
Filter::parse(f).map_err(|_| RequestParserError::SyntaxError(1, f.to_owned()))?
}
None => return Err(Self::missing_arguments_error(0)),
};
let mut argument_index_counter = 0;
let mut sort_or_window_or_position = parts.next();
let mut sort = None;
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(
s.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "Sort",
raw_input: s.to_string(),
})?,
parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string(),
);
sort_or_window_or_position = parts.next();
}
let mut window = None;
if let Some("window") = sort_or_window_or_position {
argument_index_counter += 1;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
window = Some(
w.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
);
sort_or_window_or_position = parts.next();
}
let mut position = None;
if let Some("position") = sort_or_window_or_position {
argument_index_counter += 1;
let p = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "position",
argument_index: argument_index_counter,
})?;
let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
position = Some(
p.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "SongPosition",
raw_input: p.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?,
);
}
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(SearchAddRequest {
filter,
sort,
window,
position,
})
Ok((Request::SearchAdd(filter, sort, window, position), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(SearchAdd);
impl Command for SearchAdd {
type Request = SearchAddRequest;
type Response = SearchAddResponse;
}
+30 -137
View File
@@ -1,175 +1,68 @@
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
filter::Filter,
request_tokenizer::RequestTokenizer,
types::{PlaylistName, SongPosition, Sort, WindowRange},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
filter::parse_filter,
};
pub struct SearchAddPl;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchAddPlRequest {
playlist_name: PlaylistName,
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
}
impl SearchAddPlRequest {
pub fn new(
playlist_name: PlaylistName,
filter: Filter,
sort: Option<Sort>,
window: Option<WindowRange>,
position: Option<SongPosition>,
) -> Self {
Self {
playlist_name,
filter,
sort,
window,
position,
}
}
}
impl CommandRequest for SearchAddPlRequest {
impl Command for SearchAddPl {
type Response = ();
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> {
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let playlist_name = parts
.next()
.ok_or(Self::missing_arguments_error(0))?
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
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 filter = parse_filter(&mut parts)?;
let mut argument_index_counter = 1;
let mut sort_or_window_or_position = parts.next();
let mut sort = None;
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(
s.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "Sort",
raw_input: s.to_string(),
})?,
parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string(),
);
sort_or_window_or_position = parts.next();
}
let mut window = None;
if let Some("window") = sort_or_window_or_position {
argument_index_counter += 1;
let w = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "window",
argument_index: argument_index_counter,
})?;
let w = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
window = Some(
w.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "WindowRange",
raw_input: w.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(0, w.to_string()))?,
);
sort_or_window_or_position = parts.next();
}
let mut position = None;
if let Some("position") = sort_or_window_or_position {
argument_index_counter += 1;
let p = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "position",
argument_index: argument_index_counter,
})?;
let p = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
position = Some(
p.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: argument_index_counter,
expected_type: "SongPosition",
raw_input: p.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(0, p.to_string()))?,
);
}
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(SearchAddPlRequest {
playlist_name,
filter,
sort,
window,
position,
})
Ok((
Request::SearchAddPl(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;
}
+20 -91
View File
@@ -1,113 +1,47 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandRequest, CommandResponse, RequestParserError, ResponseParserError},
filter::Filter,
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, get_and_parse_property},
types::{GroupType, Seconds},
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError, get_and_parse_property,
},
filter::parse_filter,
};
pub struct SearchCount;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchCountRequest {
filter: Filter,
group: Option<GroupType>,
pub struct SearchCountResponse {
pub songs: usize,
pub playtime: u64,
}
impl SearchCountRequest {
pub fn new(filter: Filter, group: Option<GroupType>) -> Self {
Self { filter, group }
}
}
impl CommandRequest for SearchCountRequest {
impl Command for SearchCount {
type Response = SearchCountResponse;
const COMMAND: &'static str = "searchcount";
const MIN_ARGS: u32 = 1;
const MAX_ARGS: Option<u32> = Some(2);
fn into_request_enum(self) -> crate::Request {
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)),
};
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let filter = parse_filter(&mut parts)?;
let group = if let Some("group") = parts.next() {
let group = parts
.next()
.ok_or(RequestParserError::MissingKeywordValue {
keyword: "group",
argument_index: 1,
})?;
let group = parts.next().ok_or(RequestParserError::UnexpectedEOF)?;
Some(
group
.parse()
.map_err(|_| RequestParserError::SubtypeParserError {
argument_index: 1,
expected_type: "Group",
raw_input: group.to_string(),
})?,
.map_err(|_| RequestParserError::SyntaxError(1, group.to_owned()))?,
)
} else {
None
};
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(SearchCountRequest { filter, group })
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SearchCountResponse {
pub songs: usize,
pub playtime: Seconds,
}
impl SearchCountResponse {
pub fn new(songs: usize, playtime: Seconds) -> Self {
Self { songs, playtime }
}
}
impl CommandResponse for SearchCountResponse {
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
Ok((Request::SearchCount(filter, group), ""))
}
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
let songs = get_and_parse_property!(parts, "songs", Text);
let playtime = get_and_parse_property!(parts, "playtime", Text);
@@ -115,8 +49,3 @@ impl CommandResponse for SearchCountResponse {
Ok(SearchCountResponse { songs, playtime })
}
}
impl Command for SearchCount {
type Request = SearchCountRequest;
type Response = SearchCountResponse;
}
+16 -30
View File
@@ -1,49 +1,35 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{
Command, CommandResponse, ResponseParserError, single_optional_item_command_request,
},
response_tokenizer::{ResponseAttributes, get_and_parse_property},
types::Uri,
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
get_and_parse_property,
};
pub struct Update;
single_optional_item_command_request!(Update, "update", Uri);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UpdateResponse {
updating_db: usize,
}
impl UpdateResponse {
pub fn new(updating_db: usize) -> Self {
UpdateResponse { updating_db }
}
}
impl Command for Update {
type Response = UpdateResponse;
const COMMAND: &'static str = "update";
impl CommandResponse for UpdateResponse {
fn into_response_enum(self) -> crate::Response {
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 from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
let updating_db = get_and_parse_property!(parts, "updating_db", Text);
Ok(UpdateResponse { updating_db })
}
}
impl Command for Update {
type Request = UpdateRequest;
type Response = UpdateResponse;
}
+10 -10
View File
@@ -1,11 +1,11 @@
mod delpartition;
mod listpartitions;
mod moveoutput;
mod newpartition;
mod partition;
pub mod delpartition;
pub mod listpartitions;
pub mod moveoutput;
pub mod newpartition;
pub mod partition;
pub use delpartition::*;
pub use listpartitions::*;
pub use moveoutput::*;
pub use newpartition::*;
pub use partition::*;
pub use delpartition::DelPartition;
pub use listpartitions::ListPartitions;
pub use moveoutput::MoveOutput;
pub use newpartition::NewPartition;
pub use partition::Partition;
@@ -1,15 +1,29 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::PartitionName,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct DelPartition;
single_item_command_request!(DelPartition, "delpartition", PartitionName);
empty_command_response!(DelPartition);
impl Command for DelPartition {
type Request = DelPartitionRequest;
type Response = DelPartitionResponse;
type Response = ();
const COMMAND: &'static str = "delpartition";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let partition = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok((Request::DelPartition(partition), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -1,16 +1,33 @@
use crate::{
commands::{Command, ResponseParserError, empty_command_request, multi_item_command_response},
response_tokenizer::expect_property_type,
types::PartitionName,
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
expect_property_type,
};
pub struct ListPartitions;
empty_command_request!(ListPartitions, "listpartitions");
multi_item_command_response!(ListPartitions, "partition", PartitionName);
pub type ListPartitionsResponse = Vec<String>;
impl Command for ListPartitions {
type Request = ListPartitionsRequest;
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)
}
}
+24 -7
View File
@@ -1,12 +1,29 @@
use crate::commands::{Command, empty_command_response, single_item_command_request};
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct MoveOutput;
single_item_command_request!(MoveOutput, "moveoutput", String);
empty_command_response!(MoveOutput);
impl Command for MoveOutput {
type Request = MoveOutputRequest;
type Response = MoveOutputResponse;
type Response = ();
const COMMAND: &'static str = "moveoutput";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let output_name = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok((Request::MoveOutput(output_name), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -1,15 +1,29 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::PartitionName,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct NewPartition;
single_item_command_request!(NewPartition, "newpartition", PartitionName);
empty_command_response!(NewPartition);
impl Command for NewPartition {
type Request = NewPartitionRequest;
type Response = NewPartitionResponse;
type Response = ();
const COMMAND: &'static str = "newpartition";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let partition = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok((Request::NewPartition(partition), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+23 -9
View File
@@ -1,15 +1,29 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::PartitionName,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Partition;
single_item_command_request!(Partition, "partition", PartitionName);
empty_command_response!(Partition);
impl Command for Partition {
type Request = PartitionRequest;
type Response = PartitionResponse;
type Response = ();
const COMMAND: &'static str = "partition";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let partition = parts
.next()
.ok_or(RequestParserError::UnexpectedEOF)?
.to_string();
debug_assert!(parts.next().is_none());
Ok((Request::Partition(partition), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+24 -24
View File
@@ -1,25 +1,25 @@
mod consume;
mod crossfade;
mod getvol;
mod mixrampdb;
mod mixrampdelay;
mod random;
mod repeat;
mod replay_gain_mode;
mod replay_gain_status;
mod setvol;
mod single;
mod volume;
pub mod consume;
pub mod crossfade;
pub mod getvol;
pub mod mixrampdb;
pub mod mixrampdelay;
pub mod random;
pub mod repeat;
pub mod replay_gain_mode;
pub mod replay_gain_status;
pub mod setvol;
pub mod single;
pub mod volume;
pub use consume::*;
pub use crossfade::*;
pub use getvol::*;
pub use mixrampdb::*;
pub use mixrampdelay::*;
pub use random::*;
pub use repeat::*;
pub use replay_gain_mode::*;
pub use replay_gain_status::*;
pub use setvol::*;
pub use single::*;
pub use volume::*;
pub use consume::Consume;
pub use crossfade::Crossfade;
pub use getvol::GetVol;
pub use mixrampdb::MixRampDb;
pub use mixrampdelay::MixRampDelay;
pub use random::Random;
pub use repeat::Repeat;
pub use replay_gain_mode::ReplayGainMode;
pub use replay_gain_status::ReplayGainStatus;
pub use setvol::SetVol;
pub use single::Single;
pub use volume::Volume;
+26 -9
View File
@@ -1,15 +1,32 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::BoolOrOneshot,
use std::str::FromStr;
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Consume;
single_item_command_request!(Consume, "consume", BoolOrOneshot);
empty_command_response!(Consume);
impl Command for Consume {
type Request = ConsumeRequest;
type Response = ConsumeResponse;
type Response = ();
const COMMAND: &'static str = "consume";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let state = match parts.next() {
Some(s) => crate::common::BoolOrOneshot::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Consume(state), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+27 -8
View File
@@ -1,15 +1,34 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::Seconds,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::Seconds,
};
pub struct Crossfade;
single_item_command_request!(Crossfade, "crossfade", Seconds);
empty_command_response!(Crossfade);
impl Command for Crossfade {
type Request = CrossfadeRequest;
type Response = CrossfadeResponse;
type Response = ();
const COMMAND: &'static str = "crossfade";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let seconds = match parts.next() {
Some(s) => s
.parse::<Seconds>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Crossfade(seconds), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+23 -8
View File
@@ -1,15 +1,30 @@
use std::collections::HashMap;
use crate::{
commands::{Command, empty_command_request, single_item_command_response},
types::VolumeValue,
commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
get_and_parse_property,
},
common::VolumeValue,
};
pub struct GetVol;
empty_command_request!(GetVol, "getvol");
single_item_command_response!(GetVol, "volume", VolumeValue);
impl Command for GetVol {
type Request = GetVolRequest;
type Response = GetVolResponse;
type Response = VolumeValue;
const COMMAND: &'static str = "getvol";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::GetVol, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
assert_eq!(parts.len(), 1);
let volume = get_and_parse_property!(parts, "volume", Text);
Ok(volume)
}
}
+26 -7
View File
@@ -1,12 +1,31 @@
use crate::commands::{Command, empty_command_response, single_item_command_request};
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct MixRampDb;
single_item_command_request!(MixRampDb, "mixrampdb", f32);
empty_command_response!(MixRampDb);
impl Command for MixRampDb {
type Request = MixRampDbRequest;
type Response = MixRampDbResponse;
type Response = ();
const COMMAND: &'static str = "mixrampdb";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let db = match parts.next() {
Some(s) => s
.parse::<f32>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::MixRampDb(db), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+27 -8
View File
@@ -1,15 +1,34 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::Seconds,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::Seconds,
};
pub struct MixRampDelay;
single_item_command_request!(MixRampDelay, "mixrampdelay", Seconds);
empty_command_response!(MixRampDelay);
impl Command for MixRampDelay {
type Request = MixRampDelayRequest;
type Response = MixRampDelayResponse;
type Response = ();
const COMMAND: &'static str = "mixrampdelay";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let seconds = match parts.next() {
Some(s) => s
.parse::<Seconds>()
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::MixRampDelay(seconds), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+17 -48
View File
@@ -1,62 +1,31 @@
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Random;
pub struct RandomRequest(bool);
impl RandomRequest {
pub fn new(state: bool) -> Self {
Self(state)
}
}
impl CommandRequest for RandomRequest {
impl Command for Random {
type Response = ();
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> {
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let state = match parts.next() {
Some("0") => false,
Some("1") => true,
Some(s) => {
return Err(RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "bool",
raw_input: s.to_owned(),
});
}
None => return Err(Self::missing_arguments_error(0)),
Some(s) => return Err(RequestParserError::SyntaxError(0, s.to_owned())),
None => return Err(RequestParserError::UnexpectedEOF),
};
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(RandomRequest(state))
Ok((Request::Random(state), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Random);
impl Command for Random {
type Request = RandomRequest;
type Response = RandomResponse;
}
+17 -48
View File
@@ -1,62 +1,31 @@
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Repeat;
pub struct RepeatRequest(bool);
impl RepeatRequest {
pub fn new(state: bool) -> Self {
RepeatRequest(state)
}
}
impl CommandRequest for RepeatRequest {
impl Command for Repeat {
type Response = ();
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> {
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let state = match parts.next() {
Some("0") => false,
Some("1") => true,
Some(s) => {
return Err(RequestParserError::SubtypeParserError {
argument_index: 0,
expected_type: "bool",
raw_input: s.to_owned(),
});
}
None => return Err(Self::missing_arguments_error(0)),
Some(s) => return Err(RequestParserError::SyntaxError(0, s.to_owned())),
None => return Err(RequestParserError::UnexpectedEOF),
};
Self::throw_if_too_many_arguments(parts)?;
debug_assert!(parts.next().is_none());
Ok(RepeatRequest(state))
Ok((Request::Repeat(state), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
empty_command_response!(Repeat);
impl Command for Repeat {
type Request = RepeatRequest;
type Response = RepeatResponse;
}
@@ -1,15 +1,35 @@
use std::str::FromStr;
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::ReplayGainModeMode,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::ReplayGainModeMode,
};
pub struct ReplayGainMode;
single_item_command_request!(ReplayGainMode, "replay_gain_mode", ReplayGainModeMode);
empty_command_response!(ReplayGainMode);
impl Command for ReplayGainMode {
type Request = ReplayGainModeRequest;
type Response = ReplayGainModeResponse;
type Response = ();
const COMMAND: &'static str = "replay_gain_mode";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let mode = match parts.next() {
Some(s) => ReplayGainModeMode::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::ReplayGainMode(mode), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
@@ -3,51 +3,39 @@ use std::{collections::HashMap, str::FromStr};
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{ResponseAttributes, get_property},
types::ReplayGainModeMode,
commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
get_property,
},
common::ReplayGainModeMode,
};
pub struct ReplayGainStatus;
empty_command_request!(ReplayGainStatus, "replay_gain_status");
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReplayGainStatusResponse {
pub replay_gain_mode: ReplayGainModeMode,
}
impl ReplayGainStatusResponse {
pub fn new(replay_gain_mode: ReplayGainModeMode) -> Self {
Self { replay_gain_mode }
}
}
impl Command for ReplayGainStatus {
type Response = ReplayGainStatusResponse;
const COMMAND: &'static str = "replay_gain_status";
impl CommandResponse for ReplayGainStatusResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ReplayGainStatus, ""))
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let parts: HashMap<_, _> = parts.into_map()?;
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
let parts: HashMap<_, _> = parts.into();
let replay_gain_mode = get_property!(parts, "replay_gain_mode", Text);
Ok(ReplayGainStatusResponse {
replay_gain_mode: ReplayGainModeMode::from_str(replay_gain_mode).map_err(|_| {
ResponseParserError::InvalidProperty(
"replay_gain_mode".to_string(),
replay_gain_mode.to_string(),
)
ResponseParserError::InvalidProperty("replay_gain_mode", replay_gain_mode)
})?,
})
}
}
impl Command for ReplayGainStatus {
type Request = ReplayGainStatusRequest;
type Response = ReplayGainStatusResponse;
}
+28 -8
View File
@@ -1,15 +1,35 @@
use std::str::FromStr;
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::VolumeValue,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::VolumeValue,
};
pub struct SetVol;
single_item_command_request!(SetVol, "setvol", VolumeValue);
empty_command_response!(SetVol);
impl Command for SetVol {
type Request = SetVolRequest;
type Response = SetVolResponse;
type Response = ();
const COMMAND: &'static str = "setvol";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let volume = match parts.next() {
Some(s) => VolumeValue::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::SetVol(volume), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+26 -9
View File
@@ -1,15 +1,32 @@
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::BoolOrOneshot,
use std::str::FromStr;
use crate::commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
};
pub struct Single;
single_item_command_request!(Single, "single", BoolOrOneshot);
empty_command_response!(Single);
impl Command for Single {
type Request = SingleRequest;
type Response = SingleResponse;
type Response = ();
const COMMAND: &'static str = "single";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let state = match parts.next() {
Some(s) => crate::common::BoolOrOneshot::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Single(state), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+28 -8
View File
@@ -1,15 +1,35 @@
use std::str::FromStr;
use crate::{
commands::{Command, empty_command_response, single_item_command_request},
types::VolumeValue,
commands::{
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
ResponseParserError,
},
common::VolumeValue,
};
pub struct Volume;
single_item_command_request!(Volume, "volume", VolumeValue);
empty_command_response!(Volume);
impl Command for Volume {
type Request = VolumeRequest;
type Response = VolumeResponse;
type Response = ();
const COMMAND: &'static str = "volume";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
let change = match parts.next() {
Some(s) => VolumeValue::from_str(s)
.map_err(|_| RequestParserError::SyntaxError(0, s.to_owned()))?,
None => return Err(RequestParserError::UnexpectedEOF),
};
debug_assert!(parts.next().is_none());
Ok((Request::Volume(change), ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+10 -10
View File
@@ -1,11 +1,11 @@
mod clearerror;
mod currentsong;
mod idle;
mod stats;
mod status;
pub mod clearerror;
pub mod currentsong;
pub mod idle;
pub mod stats;
pub mod status;
pub use clearerror::*;
pub use currentsong::*;
pub use idle::*;
pub use stats::*;
pub use status::*;
pub use clearerror::ClearError;
pub use currentsong::CurrentSong;
pub use idle::Idle;
pub use stats::Stats;
pub use status::Status;
+18 -7
View File
@@ -1,13 +1,24 @@
use crate::commands::{Command, empty_command_request, empty_command_response};
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
/// Clears the current error message in status (this is also accomplished by any command that starts playback)
pub struct ClearError;
empty_command_request!(ClearError, "clearerror");
empty_command_response!(ClearError);
impl Command for ClearError {
type Request = ClearErrorRequest;
type Response = ClearErrorResponse;
type Response = ();
const COMMAND: &'static str = "clearerror";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::ClearError, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}
+17 -65
View File
@@ -1,76 +1,28 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, CommandResponse, ResponseParserError, empty_command_request},
response_tokenizer::{
ResponseAttributes, get_and_parse_optional_property, get_and_parse_property,
},
types::{DbSongInfo, Priority, SongId, SongPosition},
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
/// Displays the song info of the current song (same song that is identified in status)
pub struct CurrentSong;
empty_command_request!(CurrentSong, "currentsong");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CurrentSongResponse {
position: SongPosition,
id: SongId,
priority: Option<Priority>,
song_info: DbSongInfo,
}
impl CurrentSongResponse {
pub fn new(
position: SongPosition,
id: SongId,
priority: Option<Priority>,
song_info: DbSongInfo,
) -> Self {
Self {
position,
id,
priority,
song_info,
}
}
}
impl CommandResponse for CurrentSongResponse {
fn into_response_enum(self) -> crate::Response {
todo!()
}
fn from_response_enum(_response: crate::Response) -> Option<Self> {
todo!()
}
fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
let mut parts: HashMap<_, _> = parts.into_map()?;
let position: SongPosition = get_and_parse_property!(parts, "Pos", Text);
let id: SongId = get_and_parse_property!(parts, "Id", Text);
let priority: Option<Priority> = get_and_parse_optional_property!(parts, "Prio", Text);
parts.remove("Pos");
parts.remove("Id");
parts.remove("Prio");
let song_info = DbSongInfo::parse_map(parts)?;
Ok(CurrentSongResponse {
position,
id,
priority,
song_info,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CurrentSongResponse {}
impl Command for CurrentSong {
type Request = CurrentSongRequest;
type Response = CurrentSongResponse;
const COMMAND: &'static str = "currentsong";
fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::CurrentSong, ""))
}
fn parse_response(
_parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
unimplemented!()
}
}
+30 -61
View File
@@ -1,68 +1,37 @@
use std::str::FromStr;
use std::str::{FromStr, SplitWhitespace};
use crate::{
commands::{Command, CommandRequest, RequestParserError, empty_command_response},
request_tokenizer::RequestTokenizer,
types::SubSystem,
use crate::common::SubSystem;
use crate::commands::{
Command, Request, RequestParserResult, ResponseAttributes, ResponseParserError,
};
pub struct Idle;
pub struct IdleRequest(Option<Vec<SubSystem>>);
impl IdleRequest {
pub fn new(subsystems: Option<Vec<SubSystem>>) -> Self {
IdleRequest(subsystems)
}
}
impl CommandRequest for IdleRequest {
const COMMAND: &'static str = "idle";
const MIN_ARGS: u32 = 0;
const MAX_ARGS: Option<u32> = None;
fn into_request_enum(self) -> crate::Request {
crate::Request::Idle(self.0)
}
fn from_request_enum(request: crate::Request) -> Option<Self> {
match request {
crate::Request::Idle(subsystems) => Some(IdleRequest(subsystems)),
_ => None,
}
}
fn serialize(&self) -> String {
match &self.0 {
Some(subsystems) => {
let subsystems_str = subsystems
.iter()
.map(|subsystem| subsystem.to_string())
.collect::<Vec<_>>()
.join(",");
format!("{} {}\n", Self::COMMAND, subsystems_str)
}
None => Self::COMMAND.to_string() + "\n",
}
}
fn parse(parts: RequestTokenizer<'_>) -> Result<Self, RequestParserError> {
let mut parts = parts;
let result = parts.next().map_or(Ok(None), |subsystems| {
let subsystems = subsystems
.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;
type Response = ();
const COMMAND: &'static str = "idle";
fn parse_request(mut parts: SplitWhitespace<'_>) -> RequestParserResult<'_> {
let result = parts
.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(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError> {
debug_assert!(parts.is_empty());
Ok(())
}
}

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