mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Discovery: Refactor and add Avahi DBus backend (#1347)
* discovery: use opaque error type for DnsSdError This helps to decouple discovery and core by not leaking implementation details of the zeroconf backend into Error conversion impls in core. * discovery: map all MDNS/DNS-SD errors to DiscoveryError::DnsSdError previously, libmdns errors would use a generic conversion from std::io::Error to core::Error * discovery: use an opaque type for the handle to the DNS-SD service * discovery: make features additive i.e. add with-libmdns instead of using not(with-dns-sd). The logic is such that enabling with-dns-sd in addition to the default with-libmdns will still end up using dns-sd, as before. If only with-libmdns is enabled, that will be the default. If none of the features is enabled, attempting to build a `Discovery` will yield an error. * discovery: add --zeroconf-backend CLI flag * discovery: Add minimal Avahi zeroconf backend * bump MSRV to 1.75 required by zbus >= 4 * discovery: ensure that server and dns-sd backend shutdown gracefully Previously, on drop the the shutdown_tx/close_tx, it wasn't guaranteed the corresponding tasks would continue to be polled until they actually completed their shutdown. Since dns_sd::Service is not Send and non-async, and because libmdns is non-async, put them on their own threads. * discovery: use a shared channel for server and zeroconf status messages * discovery: add Avahi reconnection logic This deals gracefully with the case where the Avahi daemon is restarted or not running initially. * discovery: allow running when compiled without zeroconf backend... ...but exit with an error if there's no way to authenticate * better error messages for invalid options with no short flag
This commit is contained in:
parent
d2324ddd1b
commit
94d174c33d
15 changed files with 1156 additions and 129 deletions
|
@ -1,6 +1,6 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
ARG debian_version=slim-bookworm
|
ARG debian_version=slim-bookworm
|
||||||
ARG rust_version=1.74.0
|
ARG rust_version=1.75.0
|
||||||
FROM rust:${rust_version}-${debian_version}
|
FROM rust:${rust_version}-${debian_version}
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
@ -109,7 +109,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
toolchain:
|
toolchain:
|
||||||
- "1.74" # MSRV (Minimum supported rust version)
|
- "1.75" # MSRV (Minimum supported rust version)
|
||||||
- stable
|
- stable
|
||||||
experimental: [false]
|
experimental: [false]
|
||||||
# Ignore failures in beta
|
# Ignore failures in beta
|
||||||
|
@ -164,7 +164,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest]
|
os: [windows-latest]
|
||||||
toolchain:
|
toolchain:
|
||||||
- "1.74" # MSRV (Minimum supported rust version)
|
- "1.75" # MSRV (Minimum supported rust version)
|
||||||
- stable
|
- stable
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
@ -215,7 +215,7 @@ jobs:
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
- riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
toolchain:
|
toolchain:
|
||||||
- "1.74" # MSRV (Minimum supported rust version)
|
- "1.75" # MSRV (Minimum supported rust version)
|
||||||
- stable
|
- stable
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
|
@ -10,11 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- [core] The `access_token` for http requests is now acquired by `login5`
|
- [core] The `access_token` for http requests is now acquired by `login5`
|
||||||
|
- [core] MSRV is now 1.75 (breaking)
|
||||||
|
- [discovery] librespot can now be compiled with multiple MDNS/DNS-SD backends
|
||||||
|
(avahi, dns_sd, libmdns) which can be selected using a CLI flag. The defaults
|
||||||
|
are unchanged (breaking).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- [core] Add `login` (mobile) and `auth_token` retrieval via login5
|
- [core] Add `login` (mobile) and `auth_token` retrieval via login5
|
||||||
- [core] Add `OS` and `os_version` to `config.rs`
|
- [core] Add `OS` and `os_version` to `config.rs`
|
||||||
|
- [discovery] Added a new MDNS/DNS-SD backend which connects to Avahi via D-Bus.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
|
11
COMPILING.md
11
COMPILING.md
|
@ -56,6 +56,17 @@ On Fedora systems:
|
||||||
sudo dnf install alsa-lib-devel
|
sudo dnf install alsa-lib-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Zeroconf library dependencies
|
||||||
|
Depending on the chosen backend, specific development libraries are required.
|
||||||
|
|
||||||
|
*_Note this is an non-exhaustive list, open a PR to add to it!_*
|
||||||
|
|
||||||
|
| Zeroconf backend | Debian/Ubuntu | Fedora | macOS |
|
||||||
|
|--------------------|------------------------------|-----------------------------------|-------------|
|
||||||
|
|avahi | | | |
|
||||||
|
|dns_sd | `libavahi-compat-libdnssd-dev pkg-config` | `avahi-compat-libdns_sd-devel` | |
|
||||||
|
|libmdns (default) | | | |
|
||||||
|
|
||||||
### Getting the Source
|
### Getting the Source
|
||||||
|
|
||||||
The recommended method is to first fork the repo, so that you have a copy that you have read/write access to. After that, it’s a simple case of cloning your fork.
|
The recommended method is to first fork the repo, so that you have a copy that you have read/write access to. After that, it’s a simple case of cloning your fork.
|
||||||
|
|
444
Cargo.lock
generated
444
Cargo.lock
generated
|
@ -135,6 +135,114 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-broadcast"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-io"
|
||||||
|
version = "2.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8"
|
||||||
|
dependencies = [
|
||||||
|
"async-lock",
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"parking",
|
||||||
|
"polling",
|
||||||
|
"rustix",
|
||||||
|
"slab",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-lock"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-process"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-signal",
|
||||||
|
"async-task",
|
||||||
|
"blocking",
|
||||||
|
"cfg-if",
|
||||||
|
"event-listener",
|
||||||
|
"futures-lite",
|
||||||
|
"rustix",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-recursion"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.79",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-signal"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"atomic-waker",
|
||||||
|
"cfg-if",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"rustix",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"slab",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-task"
|
||||||
|
version = "4.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.83"
|
version = "0.1.83"
|
||||||
|
@ -292,6 +400,19 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blocking"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-task",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"piper",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.16.0"
|
||||||
|
@ -358,6 +479,12 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.38"
|
version = "0.4.38"
|
||||||
|
@ -419,6 +546,15 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
@ -494,6 +630,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -654,6 +796,33 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "endi"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2"
|
||||||
|
version = "0.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
|
||||||
|
dependencies = [
|
||||||
|
"enumflags2_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2_derive"
|
||||||
|
version = "0.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.79",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -692,6 +861,27 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "5.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener-strategy"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
@ -773,6 +963,19 @@ version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-lite"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
@ -1152,6 +1355,12 @@ version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
@ -1753,7 +1962,6 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"dns-sd",
|
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -1821,10 +2029,13 @@ dependencies = [
|
||||||
"librespot-core",
|
"librespot-core",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_repr",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1931,6 +2142,15 @@ version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
|
@ -1958,7 +2178,7 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.3.9",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
|
@ -2014,6 +2234,19 @@ dependencies = [
|
||||||
"jni-sys",
|
"jni-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
"memoffset",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "no-std-compat"
|
name = "no-std-compat"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -2253,6 +2486,22 @@ dependencies = [
|
||||||
"paste",
|
"paste",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-stream"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
@ -2332,6 +2581,17 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "piper"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"fastrand",
|
||||||
|
"futures-io",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkcs1"
|
name = "pkcs1"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
|
@ -2359,6 +2619,21 @@ version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polling"
|
||||||
|
version = "3.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"hermit-abi 0.4.0",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustix",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -2442,9 +2717,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf"
|
name = "protobuf"
|
||||||
version = "3.6.0"
|
version = "3.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3018844a02746180074f621e847703737d27d89d7f0721a7a4da317f88b16385"
|
checksum = "a3a7c64d9bf75b1b8d981124c14c179074e8caa7dfe7b6a12e6222ddcd0c8f72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"protobuf-support",
|
"protobuf-support",
|
||||||
|
@ -2453,9 +2728,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf-codegen"
|
name = "protobuf-codegen"
|
||||||
version = "3.6.0"
|
version = "3.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "411c15a212b4de05eb8bc989fd066a74c86bd3c04e27d6e86bd7703b806d7734"
|
checksum = "e26b833f144769a30e04b1db0146b2aaa53fd2fd83acf10a6b5f996606c18144"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -2468,9 +2743,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf-parse"
|
name = "protobuf-parse"
|
||||||
version = "3.6.0"
|
version = "3.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06f45f16b522d92336e839b5e40680095a045e36a1e7f742ba682ddc85236772"
|
checksum = "322330e133eab455718444b4e033ebfac7c6528972c784fcde28d2cc783c6257"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
@ -2484,9 +2759,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf-support"
|
name = "protobuf-support"
|
||||||
version = "3.6.0"
|
version = "3.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "faf96d872914fcda2b66d66ea3fff2be7c66865d31c7bb2790cff32c0e714880"
|
checksum = "b088fd20b938a875ea00843b6faf48579462630015c3788d397ad6a786663252"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
@ -2946,6 +3221,17 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_repr"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.79",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
|
@ -3079,6 +3365,12 @@ dependencies = [
|
||||||
"der",
|
"der",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
@ -3362,6 +3654,7 @@ dependencies = [
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
|
"tracing",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3495,9 +3788,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.79",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.32"
|
version = "0.1.32"
|
||||||
|
@ -3539,6 +3844,17 @@ version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uds_windows"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
|
||||||
|
dependencies = [
|
||||||
|
"memoffset",
|
||||||
|
"tempfile",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
|
@ -3598,9 +3914,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.10.0"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -4148,6 +4464,73 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xdg-home"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus"
|
||||||
|
version = "4.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||||
|
dependencies = [
|
||||||
|
"async-broadcast",
|
||||||
|
"async-process",
|
||||||
|
"async-recursion",
|
||||||
|
"async-trait",
|
||||||
|
"enumflags2",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"hex",
|
||||||
|
"nix",
|
||||||
|
"ordered-stream",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"sha1",
|
||||||
|
"static_assertions",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"uds_windows",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
"xdg-home",
|
||||||
|
"zbus_macros",
|
||||||
|
"zbus_names",
|
||||||
|
"zvariant",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_macros"
|
||||||
|
version = "4.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.79",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_names"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"static_assertions",
|
||||||
|
"zvariant",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.35"
|
version = "0.7.35"
|
||||||
|
@ -4174,3 +4557,40 @@ name = "zeroize"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
|
||||||
|
dependencies = [
|
||||||
|
"endi",
|
||||||
|
"enumflags2",
|
||||||
|
"serde",
|
||||||
|
"static_assertions",
|
||||||
|
"zvariant_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_derive"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.79",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_utils"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.79",
|
||||||
|
]
|
||||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot"
|
name = "librespot"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
rust-version = "1.74"
|
rust-version = "1.75"
|
||||||
authors = ["Librespot Org"]
|
authors = ["Librespot Org"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "An open source client library for Spotify, with support for Spotify Connect"
|
description = "An open source client library for Spotify, with support for Spotify Connect"
|
||||||
|
@ -36,6 +36,7 @@ version = "0.5.0"
|
||||||
[dependencies.librespot-discovery]
|
[dependencies.librespot-discovery]
|
||||||
path = "discovery"
|
path = "discovery"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dependencies.librespot-metadata]
|
[dependencies.librespot-metadata]
|
||||||
path = "metadata"
|
path = "metadata"
|
||||||
|
@ -62,7 +63,7 @@ log = "0.4"
|
||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
sysinfo = { version = "0.31.3", default-features = false, features = ["system"] }
|
sysinfo = { version = "0.31.3", default-features = false, features = ["system"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1", features = ["rt", "macros", "signal", "sync", "parking_lot", "process"] }
|
tokio = { version = "1.40", features = ["rt", "macros", "signal", "sync", "parking_lot", "process"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -75,11 +76,13 @@ rodiojack-backend = ["librespot-playback/rodiojack-backend"]
|
||||||
sdl-backend = ["librespot-playback/sdl-backend"]
|
sdl-backend = ["librespot-playback/sdl-backend"]
|
||||||
gstreamer-backend = ["librespot-playback/gstreamer-backend"]
|
gstreamer-backend = ["librespot-playback/gstreamer-backend"]
|
||||||
|
|
||||||
with-dns-sd = ["librespot-core/with-dns-sd", "librespot-discovery/with-dns-sd"]
|
with-avahi = ["librespot-discovery/with-avahi"]
|
||||||
|
with-dns-sd = ["librespot-discovery/with-dns-sd"]
|
||||||
|
with-libmdns = ["librespot-discovery/with-libmdns"]
|
||||||
|
|
||||||
passthrough-decoder = ["librespot-playback/passthrough-decoder"]
|
passthrough-decoder = ["librespot-playback/passthrough-decoder"]
|
||||||
|
|
||||||
default = ["rodio-backend"]
|
default = ["rodio-backend", "with-libmdns"]
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
maintainer = "librespot-org"
|
maintainer = "librespot-org"
|
||||||
|
|
|
@ -29,7 +29,7 @@ RUN apt-get install -y curl git build-essential crossbuild-essential-arm64 cross
|
||||||
RUN apt-get install -y libasound2-dev libasound2-dev:arm64 libasound2-dev:armel libasound2-dev:armhf
|
RUN apt-get install -y libasound2-dev libasound2-dev:arm64 libasound2-dev:armel libasound2-dev:armhf
|
||||||
RUN apt-get install -y libpulse0 libpulse0:arm64 libpulse0:armel libpulse0:armhf
|
RUN apt-get install -y libpulse0 libpulse0:arm64 libpulse0:armel libpulse0:armhf
|
||||||
|
|
||||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain 1.74 -y
|
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain 1.75 -y
|
||||||
ENV PATH="/root/.cargo/bin/:${PATH}"
|
ENV PATH="/root/.cargo/bin/:${PATH}"
|
||||||
RUN rustup target add aarch64-unknown-linux-gnu
|
RUN rustup target add aarch64-unknown-linux-gnu
|
||||||
RUN rustup target add arm-unknown-linux-gnueabi
|
RUN rustup target add arm-unknown-linux-gnueabi
|
||||||
|
|
|
@ -22,7 +22,6 @@ aes = "0.8"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
dns-sd = { version = "0.1", optional = true }
|
|
||||||
form_urlencoded = "1.0"
|
form_urlencoded = "1.0"
|
||||||
futures-core = "0.3"
|
futures-core = "0.3"
|
||||||
futures-util = { version = "0.3", features = ["alloc", "bilock", "sink", "unstable"] }
|
futures-util = { version = "0.3", features = ["alloc", "bilock", "sink", "unstable"] }
|
||||||
|
@ -71,6 +70,3 @@ vergen-gitcl = { version = "1.0.0", default-features = false, features = ["build
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["macros", "parking_lot"] }
|
tokio = { version = "1", features = ["macros", "parking_lot"] }
|
||||||
|
|
||||||
[features]
|
|
||||||
with-dns-sd = ["dns-sd"]
|
|
||||||
|
|
|
@ -21,9 +21,6 @@ use url::ParseError;
|
||||||
|
|
||||||
use librespot_oauth::OAuthError;
|
use librespot_oauth::OAuthError;
|
||||||
|
|
||||||
#[cfg(feature = "with-dns-sd")]
|
|
||||||
use dns_sd::DNSError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
pub kind: ErrorKind,
|
pub kind: ErrorKind,
|
||||||
|
@ -314,13 +311,6 @@ impl From<DecodeError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with-dns-sd")]
|
|
||||||
impl From<DNSError> for Error {
|
|
||||||
fn from(err: DNSError) -> Self {
|
|
||||||
Self::new(ErrorKind::Unavailable, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<http::Error> for Error {
|
impl From<http::Error> for Error {
|
||||||
fn from(err: http::Error) -> Self {
|
fn from(err: http::Error) -> Self {
|
||||||
if err.is::<InvalidHeaderName>()
|
if err.is::<InvalidHeaderName>()
|
||||||
|
|
|
@ -21,13 +21,16 @@ hmac = "0.12"
|
||||||
hyper = { version = "1.3", features = ["http1"] }
|
hyper = { version = "1.3", features = ["http1"] }
|
||||||
hyper-util = { version = "0.1", features = ["server-auto", "server-graceful", "service"] }
|
hyper-util = { version = "0.1", features = ["server-auto", "server-graceful", "service"] }
|
||||||
http-body-util = "0.1.1"
|
http-body-util = "0.1.1"
|
||||||
libmdns = "0.9"
|
libmdns = { version = "0.9", optional = true }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
serde = { version = "1", default-features = false, features = ["derive"], optional = true }
|
||||||
|
serde_repr = "0.1"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1", features = ["parking_lot", "sync", "rt"] }
|
tokio = { version = "1", features = ["parking_lot", "sync", "rt"] }
|
||||||
|
zbus = { version = "4", default-features = false, features = ["tokio"], optional = true }
|
||||||
|
|
||||||
[dependencies.librespot-core]
|
[dependencies.librespot-core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
|
@ -39,4 +42,8 @@ hex = "0.4"
|
||||||
tokio = { version = "1", features = ["macros", "parking_lot", "rt"] }
|
tokio = { version = "1", features = ["macros", "parking_lot", "rt"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
with-dns-sd = ["dns-sd", "librespot-core/with-dns-sd"]
|
with-avahi = ["zbus", "serde"]
|
||||||
|
with-dns-sd = ["dns-sd"]
|
||||||
|
with-libmdns = ["libmdns"]
|
||||||
|
|
||||||
|
default = ["with-libmdns"]
|
||||||
|
|
151
discovery/src/avahi.rs
Normal file
151
discovery/src/avahi.rs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
#![cfg(feature = "with-avahi")]
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub use server::ServerProxy;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub use entry_group::{
|
||||||
|
EntryGroupProxy, EntryGroupState, StateChangedStream as EntryGroupStateChangedStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod server {
|
||||||
|
// This is not the full interface, just the methods we need!
|
||||||
|
// Avahi also implements a newer version of the interface ("org.freedesktop.Avahi.Server2"), but
|
||||||
|
// the additions are not relevant for us, and the older version is not intended to be deprecated.
|
||||||
|
// cf. the release notes for 0.8 at https://github.com/avahi/avahi/blob/master/docs/NEWS
|
||||||
|
#[zbus::proxy(
|
||||||
|
interface = "org.freedesktop.Avahi.Server",
|
||||||
|
default_service = "org.freedesktop.Avahi",
|
||||||
|
default_path = "/",
|
||||||
|
gen_blocking = false
|
||||||
|
)]
|
||||||
|
trait Server {
|
||||||
|
/// EntryGroupNew method
|
||||||
|
#[zbus(object = "super::entry_group::EntryGroup")]
|
||||||
|
fn entry_group_new(&self);
|
||||||
|
|
||||||
|
/// GetState method
|
||||||
|
fn get_state(&self) -> zbus::Result<i32>;
|
||||||
|
|
||||||
|
/// StateChanged signal
|
||||||
|
#[zbus(signal)]
|
||||||
|
fn state_changed(&self, state: i32, error: &str) -> zbus::Result<()>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod entry_group {
|
||||||
|
use serde_repr::Deserialize_repr;
|
||||||
|
use zbus::zvariant;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize_repr)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum EntryGroupState {
|
||||||
|
// The group has not yet been committed, the user must still call avahi_entry_group_commit()
|
||||||
|
Uncommited = 0,
|
||||||
|
// The entries of the group are currently being registered
|
||||||
|
Registering = 1,
|
||||||
|
// The entries have successfully been established
|
||||||
|
Established = 2,
|
||||||
|
// A name collision for one of the entries in the group has been detected, the entries have been withdrawn
|
||||||
|
Collision = 3,
|
||||||
|
// Some kind of failure happened, the entries have been withdrawn
|
||||||
|
Failure = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl zvariant::Type for EntryGroupState {
|
||||||
|
fn signature() -> zvariant::Signature<'static> {
|
||||||
|
zvariant::Signature::try_from("i").unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus::proxy(
|
||||||
|
interface = "org.freedesktop.Avahi.EntryGroup",
|
||||||
|
default_service = "org.freedesktop.Avahi",
|
||||||
|
gen_blocking = false
|
||||||
|
)]
|
||||||
|
trait EntryGroup {
|
||||||
|
/// AddAddress method
|
||||||
|
fn add_address(
|
||||||
|
&self,
|
||||||
|
interface: i32,
|
||||||
|
protocol: i32,
|
||||||
|
flags: u32,
|
||||||
|
name: &str,
|
||||||
|
address: &str,
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// AddRecord method
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn add_record(
|
||||||
|
&self,
|
||||||
|
interface: i32,
|
||||||
|
protocol: i32,
|
||||||
|
flags: u32,
|
||||||
|
name: &str,
|
||||||
|
clazz: u16,
|
||||||
|
type_: u16,
|
||||||
|
ttl: u32,
|
||||||
|
rdata: &[u8],
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// AddService method
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn add_service(
|
||||||
|
&self,
|
||||||
|
interface: i32,
|
||||||
|
protocol: i32,
|
||||||
|
flags: u32,
|
||||||
|
name: &str,
|
||||||
|
type_: &str,
|
||||||
|
domain: &str,
|
||||||
|
host: &str,
|
||||||
|
port: u16,
|
||||||
|
txt: &[&[u8]],
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// AddServiceSubtype method
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn add_service_subtype(
|
||||||
|
&self,
|
||||||
|
interface: i32,
|
||||||
|
protocol: i32,
|
||||||
|
flags: u32,
|
||||||
|
name: &str,
|
||||||
|
type_: &str,
|
||||||
|
domain: &str,
|
||||||
|
subtype: &str,
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// Commit method
|
||||||
|
fn commit(&self) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// Free method
|
||||||
|
fn free(&self) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// GetState method
|
||||||
|
fn get_state(&self) -> zbus::Result<EntryGroupState>;
|
||||||
|
|
||||||
|
/// IsEmpty method
|
||||||
|
fn is_empty(&self) -> zbus::Result<bool>;
|
||||||
|
|
||||||
|
/// Reset method
|
||||||
|
fn reset(&self) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// UpdateServiceTxt method
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn update_service_txt(
|
||||||
|
&self,
|
||||||
|
interface: i32,
|
||||||
|
protocol: i32,
|
||||||
|
flags: u32,
|
||||||
|
name: &str,
|
||||||
|
type_: &str,
|
||||||
|
domain: &str,
|
||||||
|
txt: &[&[u8]],
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// StateChanged signal
|
||||||
|
#[zbus(signal)]
|
||||||
|
fn state_changed(&self, state: EntryGroupState, error: &str) -> zbus::Result<()>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,17 +7,19 @@
|
||||||
//! This library uses mDNS and DNS-SD so that other devices can find it,
|
//! This library uses mDNS and DNS-SD so that other devices can find it,
|
||||||
//! and spawns an http server to answer requests of Spotify clients.
|
//! and spawns an http server to answer requests of Spotify clients.
|
||||||
|
|
||||||
|
mod avahi;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
io,
|
error::Error as StdError,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
use self::server::DiscoveryServer;
|
use self::server::DiscoveryServer;
|
||||||
|
|
||||||
|
@ -30,6 +32,88 @@ pub use crate::core::authentication::Credentials;
|
||||||
/// Determining the icon in the list of available devices.
|
/// Determining the icon in the list of available devices.
|
||||||
pub use crate::core::config::DeviceType;
|
pub use crate::core::config::DeviceType;
|
||||||
|
|
||||||
|
pub enum DiscoveryEvent {
|
||||||
|
Credentials(Credentials),
|
||||||
|
ServerError(DiscoveryError),
|
||||||
|
ZeroconfError(DiscoveryError),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ZeroconfCmd {
|
||||||
|
Shutdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DnsSdHandle {
|
||||||
|
task_handle: tokio::task::JoinHandle<()>,
|
||||||
|
shutdown_tx: oneshot::Sender<ZeroconfCmd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DnsSdHandle {
|
||||||
|
async fn shutdown(self) {
|
||||||
|
log::debug!("Shutting down zeroconf responder");
|
||||||
|
let Self {
|
||||||
|
task_handle,
|
||||||
|
shutdown_tx,
|
||||||
|
} = self;
|
||||||
|
if shutdown_tx.send(ZeroconfCmd::Shutdown).is_err() {
|
||||||
|
log::warn!("Zeroconf responder unexpectedly disappeared");
|
||||||
|
} else {
|
||||||
|
let _ = task_handle.await;
|
||||||
|
log::debug!("Zeroconf responder stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DnsSdServiceBuilder = fn(
|
||||||
|
Cow<'static, str>,
|
||||||
|
Vec<std::net::IpAddr>,
|
||||||
|
u16,
|
||||||
|
mpsc::UnboundedSender<DiscoveryEvent>,
|
||||||
|
) -> Result<DnsSdHandle, Error>;
|
||||||
|
|
||||||
|
// Default goes first: This matches the behaviour when feature flags were exlusive, i.e. when there
|
||||||
|
// was only `feature = "with-dns-sd"` or `not(feature = "with-dns-sd")`
|
||||||
|
pub const BACKENDS: &[(
|
||||||
|
&str,
|
||||||
|
// If None, the backend is known but wasn't compiled.
|
||||||
|
Option<DnsSdServiceBuilder>,
|
||||||
|
)] = &[
|
||||||
|
#[cfg(feature = "with-avahi")]
|
||||||
|
("avahi", Some(launch_avahi)),
|
||||||
|
#[cfg(not(feature = "with-avahi"))]
|
||||||
|
("avahi", None),
|
||||||
|
#[cfg(feature = "with-dns-sd")]
|
||||||
|
("dns-sd", Some(launch_dns_sd)),
|
||||||
|
#[cfg(not(feature = "with-dns-sd"))]
|
||||||
|
("dns-sd", None),
|
||||||
|
#[cfg(feature = "with-libmdns")]
|
||||||
|
("libmdns", Some(launch_libmdns)),
|
||||||
|
#[cfg(not(feature = "with-libmdns"))]
|
||||||
|
("libmdns", None),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn find(name: Option<&str>) -> Result<DnsSdServiceBuilder, Error> {
|
||||||
|
if let Some(ref name) = name {
|
||||||
|
match BACKENDS.iter().find(|(id, _)| name == id) {
|
||||||
|
Some((_id, Some(launch_svc))) => Ok(*launch_svc),
|
||||||
|
Some((_id, None)) => Err(Error::unavailable(format!(
|
||||||
|
"librespot built without '{}' support",
|
||||||
|
name
|
||||||
|
))),
|
||||||
|
None => Err(Error::not_found(format!(
|
||||||
|
"unknown zeroconf backend '{}'",
|
||||||
|
name
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BACKENDS
|
||||||
|
.iter()
|
||||||
|
.find_map(|(_, launch_svc)| *launch_svc)
|
||||||
|
.ok_or(Error::unavailable(
|
||||||
|
"librespot built without zeroconf backends",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Makes this device visible to Spotify clients in the local network.
|
/// Makes this device visible to Spotify clients in the local network.
|
||||||
///
|
///
|
||||||
/// `Discovery` implements the [`Stream`] trait. Every time this device
|
/// `Discovery` implements the [`Stream`] trait. Every time this device
|
||||||
|
@ -37,10 +121,11 @@ pub use crate::core::config::DeviceType;
|
||||||
pub struct Discovery {
|
pub struct Discovery {
|
||||||
server: DiscoveryServer,
|
server: DiscoveryServer,
|
||||||
|
|
||||||
#[cfg(not(feature = "with-dns-sd"))]
|
/// An opaque handle to the DNS-SD service. Dropping this will unregister the service.
|
||||||
_svc: libmdns::Service,
|
#[allow(unused)]
|
||||||
#[cfg(feature = "with-dns-sd")]
|
svc: DnsSdHandle,
|
||||||
_svc: dns_sd::DNSService,
|
|
||||||
|
event_rx: mpsc::UnboundedReceiver<DiscoveryEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for [`Discovery`].
|
/// A builder for [`Discovery`].
|
||||||
|
@ -48,6 +133,7 @@ pub struct Builder {
|
||||||
server_config: server::Config,
|
server_config: server::Config,
|
||||||
port: u16,
|
port: u16,
|
||||||
zeroconf_ip: Vec<std::net::IpAddr>,
|
zeroconf_ip: Vec<std::net::IpAddr>,
|
||||||
|
zeroconf_backend: Option<DnsSdServiceBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that can occur while setting up a [`Discovery`] instance.
|
/// Errors that can occur while setting up a [`Discovery`] instance.
|
||||||
|
@ -55,16 +141,27 @@ pub struct Builder {
|
||||||
pub enum DiscoveryError {
|
pub enum DiscoveryError {
|
||||||
#[error("Creating SHA1 block cipher failed")]
|
#[error("Creating SHA1 block cipher failed")]
|
||||||
AesError(#[from] aes::cipher::InvalidLength),
|
AesError(#[from] aes::cipher::InvalidLength),
|
||||||
|
|
||||||
#[error("Setting up dns-sd failed: {0}")]
|
#[error("Setting up dns-sd failed: {0}")]
|
||||||
DnsSdError(#[from] io::Error),
|
DnsSdError(#[source] Box<dyn StdError + Send + Sync>),
|
||||||
|
|
||||||
#[error("Creating SHA1 HMAC failed for base key {0:?}")]
|
#[error("Creating SHA1 HMAC failed for base key {0:?}")]
|
||||||
HmacError(Vec<u8>),
|
HmacError(Vec<u8>),
|
||||||
|
|
||||||
#[error("Setting up the HTTP server failed: {0}")]
|
#[error("Setting up the HTTP server failed: {0}")]
|
||||||
HttpServerError(#[from] hyper::Error),
|
HttpServerError(#[from] hyper::Error),
|
||||||
|
|
||||||
#[error("Missing params for key {0}")]
|
#[error("Missing params for key {0}")]
|
||||||
ParamsError(&'static str),
|
ParamsError(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "with-avahi")]
|
||||||
|
impl From<zbus::Error> for DiscoveryError {
|
||||||
|
fn from(error: zbus::Error) -> Self {
|
||||||
|
Self::DnsSdError(Box::new(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<DiscoveryError> for Error {
|
impl From<DiscoveryError> for Error {
|
||||||
fn from(err: DiscoveryError) -> Self {
|
fn from(err: DiscoveryError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
|
@ -77,6 +174,264 @@ impl From<DiscoveryError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
const DNS_SD_SERVICE_NAME: &str = "_spotify-connect._tcp";
|
||||||
|
#[allow(unused)]
|
||||||
|
const TXT_RECORD: [&str; 2] = ["VERSION=1.0", "CPath=/"];
|
||||||
|
|
||||||
|
#[cfg(feature = "with-avahi")]
|
||||||
|
async fn avahi_task(
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
port: u16,
|
||||||
|
entry_group: &mut Option<avahi::EntryGroupProxy<'_>>,
|
||||||
|
) -> Result<(), DiscoveryError> {
|
||||||
|
use self::avahi::{EntryGroupState, ServerProxy};
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
|
let conn = zbus::Connection::system().await?;
|
||||||
|
|
||||||
|
// Wait for the daemon to show up.
|
||||||
|
// On error: Failed to listen for NameOwnerChanged signal => Fatal DBus issue
|
||||||
|
let bus = zbus::fdo::DBusProxy::new(&conn).await?;
|
||||||
|
let mut stream = bus
|
||||||
|
.receive_name_owner_changed_with_args(&[(0, "org.freedesktop.Avahi")])
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Wait for Avahi daemon to be started
|
||||||
|
'wait_avahi: {
|
||||||
|
while let Poll::Ready(Some(_)) = futures_util::poll!(stream.next()) {
|
||||||
|
// Drain queued name owner changes, since we're going to connect in a second
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping after we connected to the signal since it might have shown up in the meantime
|
||||||
|
if let Ok(avahi_peer) =
|
||||||
|
zbus::fdo::PeerProxy::new(&conn, "org.freedesktop.Avahi", "/").await
|
||||||
|
{
|
||||||
|
if avahi_peer.ping().await.is_ok() {
|
||||||
|
log::debug!("Pinged Avahi: Available");
|
||||||
|
break 'wait_avahi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::warn!("Failed to connect to Avahi, zeroconf discovery will not work until avahi-daemon is started. Check that it is installed and running");
|
||||||
|
|
||||||
|
// If it didn't, wait for the signal
|
||||||
|
match stream.next().await {
|
||||||
|
Some(_signal) => {
|
||||||
|
log::debug!("Avahi appeared");
|
||||||
|
break 'wait_avahi;
|
||||||
|
}
|
||||||
|
// The stream ended, but this should never happen
|
||||||
|
None => {
|
||||||
|
return Err(zbus::Error::Failure("DBus disappeared".to_owned()).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to Avahi and publish the service
|
||||||
|
let avahi_server = ServerProxy::new(&conn).await?;
|
||||||
|
log::trace!("Connected to Avahi");
|
||||||
|
|
||||||
|
*entry_group = Some(avahi_server.entry_group_new().await?);
|
||||||
|
|
||||||
|
let mut entry_group_state_stream = entry_group
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.receive_state_changed()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
entry_group
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.add_service(
|
||||||
|
-1, // AVAHI_IF_UNSPEC
|
||||||
|
-1, // IPv4 and IPv6
|
||||||
|
0, // flags
|
||||||
|
&name,
|
||||||
|
DNS_SD_SERVICE_NAME, // type
|
||||||
|
"", // domain: let the server choose
|
||||||
|
"", // host: let the server choose
|
||||||
|
port,
|
||||||
|
&TXT_RECORD.map(|s| s.as_bytes()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
entry_group.as_mut().unwrap().commit().await?;
|
||||||
|
log::debug!("Commited zeroconf service with name {}", &name);
|
||||||
|
|
||||||
|
'monitor_service: loop {
|
||||||
|
tokio::select! {
|
||||||
|
Some(state_changed) = entry_group_state_stream.next() => {
|
||||||
|
let (state, error) = match state_changed.args() {
|
||||||
|
Ok(sc) => (sc.state, sc.error),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Error on receiving EntryGroup state from Avahi: {}", e);
|
||||||
|
continue 'monitor_service;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match state {
|
||||||
|
EntryGroupState::Uncommited | EntryGroupState::Registering => {
|
||||||
|
// Not yet registered, ignore.
|
||||||
|
}
|
||||||
|
EntryGroupState::Established => {
|
||||||
|
log::info!("Published zeroconf service");
|
||||||
|
}
|
||||||
|
EntryGroupState::Collision => {
|
||||||
|
// This most likely means that librespot has unintentionally been started twice.
|
||||||
|
// Thus, don't retry with a new name, but abort.
|
||||||
|
//
|
||||||
|
// Note that the error would usually already be returned by
|
||||||
|
// entry_group.add_service above, so this state_changed handler
|
||||||
|
// won't be hit.
|
||||||
|
//
|
||||||
|
// EntryGroup has been withdrawn at this point already!
|
||||||
|
log::error!("zeroconf collision for name '{}'", &name);
|
||||||
|
return Err(zbus::Error::Failure(format!("zeroconf collision for name: {}", name)).into());
|
||||||
|
}
|
||||||
|
EntryGroupState::Failure => {
|
||||||
|
// TODO: Back off/treat as fatal?
|
||||||
|
// EntryGroup has been withdrawn at this point already!
|
||||||
|
// There seems to be no code in Avahi that actually sets this state.
|
||||||
|
log::error!("zeroconf failure: {}", error);
|
||||||
|
return Err(zbus::Error::Failure(format!("zeroconf failure: {}", error)).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_name_owner_change = stream.next() => {
|
||||||
|
break 'monitor_service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avahi disappeared (or the service was immediately taken over by a
|
||||||
|
// new daemon) => drop all handles, and reconnect
|
||||||
|
log::info!("Avahi disappeared, trying to reconnect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "with-avahi")]
|
||||||
|
fn launch_avahi(
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
_zeroconf_ip: Vec<std::net::IpAddr>,
|
||||||
|
port: u16,
|
||||||
|
status_tx: mpsc::UnboundedSender<DiscoveryEvent>,
|
||||||
|
) -> Result<DnsSdHandle, Error> {
|
||||||
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let task_handle = tokio::spawn(async move {
|
||||||
|
let mut entry_group = None;
|
||||||
|
tokio::select! {
|
||||||
|
res = avahi_task(name, port, &mut entry_group) => {
|
||||||
|
if let Err(e) = res {
|
||||||
|
log::error!("Avahi error: {}", e);
|
||||||
|
let _ = status_tx.send(DiscoveryEvent::ZeroconfError(e));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = shutdown_rx => {
|
||||||
|
if let Some(entry_group) = entry_group.as_mut() {
|
||||||
|
if let Err(e) = entry_group.free().await {
|
||||||
|
log::warn!("Failed to un-publish zeroconf service: {}", e);
|
||||||
|
} else {
|
||||||
|
log::debug!("Un-published zeroconf service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(DnsSdHandle {
|
||||||
|
task_handle,
|
||||||
|
shutdown_tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "with-dns-sd")]
|
||||||
|
fn launch_dns_sd(
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
_zeroconf_ip: Vec<std::net::IpAddr>,
|
||||||
|
port: u16,
|
||||||
|
status_tx: mpsc::UnboundedSender<DiscoveryEvent>,
|
||||||
|
) -> Result<DnsSdHandle, Error> {
|
||||||
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let task_handle = tokio::task::spawn_blocking(move || {
|
||||||
|
let inner = move || -> Result<(), DiscoveryError> {
|
||||||
|
let svc = dns_sd::DNSService::register(
|
||||||
|
Some(name.as_ref()),
|
||||||
|
DNS_SD_SERVICE_NAME,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
port,
|
||||||
|
&TXT_RECORD,
|
||||||
|
)
|
||||||
|
.map_err(|e| DiscoveryError::DnsSdError(Box::new(e)))?;
|
||||||
|
|
||||||
|
let _ = shutdown_rx.blocking_recv();
|
||||||
|
|
||||||
|
std::mem::drop(svc);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = inner() {
|
||||||
|
log::error!("dns_sd error: {}", e);
|
||||||
|
let _ = status_tx.send(DiscoveryEvent::ZeroconfError(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(DnsSdHandle {
|
||||||
|
shutdown_tx,
|
||||||
|
task_handle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "with-libmdns")]
|
||||||
|
fn launch_libmdns(
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
zeroconf_ip: Vec<std::net::IpAddr>,
|
||||||
|
port: u16,
|
||||||
|
status_tx: mpsc::UnboundedSender<DiscoveryEvent>,
|
||||||
|
) -> Result<DnsSdHandle, Error> {
|
||||||
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let task_handle = tokio::task::spawn_blocking(move || {
|
||||||
|
let inner = move || -> Result<(), DiscoveryError> {
|
||||||
|
let svc = if !zeroconf_ip.is_empty() {
|
||||||
|
libmdns::Responder::spawn_with_ip_list(
|
||||||
|
&tokio::runtime::Handle::current(),
|
||||||
|
zeroconf_ip,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
libmdns::Responder::spawn(&tokio::runtime::Handle::current())
|
||||||
|
}
|
||||||
|
.map_err(|e| DiscoveryError::DnsSdError(Box::new(e)))
|
||||||
|
.unwrap()
|
||||||
|
.register(
|
||||||
|
DNS_SD_SERVICE_NAME.to_owned(),
|
||||||
|
name.into_owned(),
|
||||||
|
port,
|
||||||
|
&TXT_RECORD,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = shutdown_rx.blocking_recv();
|
||||||
|
|
||||||
|
std::mem::drop(svc);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = inner() {
|
||||||
|
log::error!("libmdns error: {}", e);
|
||||||
|
let _ = status_tx.send(DiscoveryEvent::ZeroconfError(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(DnsSdHandle {
|
||||||
|
shutdown_tx,
|
||||||
|
task_handle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
/// Starts a new builder using the provided device and client IDs.
|
/// Starts a new builder using the provided device and client IDs.
|
||||||
pub fn new<T: Into<String>>(device_id: T, client_id: T) -> Self {
|
pub fn new<T: Into<String>>(device_id: T, client_id: T) -> Self {
|
||||||
|
@ -90,6 +445,7 @@ impl Builder {
|
||||||
},
|
},
|
||||||
port: 0,
|
port: 0,
|
||||||
zeroconf_ip: vec![],
|
zeroconf_ip: vec![],
|
||||||
|
zeroconf_backend: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +473,12 @@ impl Builder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the zeroconf (MDNS and DNS-SD) implementation to use.
|
||||||
|
pub fn zeroconf_backend(mut self, zeroconf_backend: DnsSdServiceBuilder) -> Self {
|
||||||
|
self.zeroconf_backend = Some(zeroconf_backend);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the port on which it should listen to incoming connections.
|
/// Sets the port on which it should listen to incoming connections.
|
||||||
/// The default value `0` means any port.
|
/// The default value `0` means any port.
|
||||||
pub fn port(mut self, port: u16) -> Self {
|
pub fn port(mut self, port: u16) -> Self {
|
||||||
|
@ -129,43 +491,21 @@ impl Builder {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// If setting up the mdns service or creating the server fails, this function returns an error.
|
/// If setting up the mdns service or creating the server fails, this function returns an error.
|
||||||
pub fn launch(self) -> Result<Discovery, Error> {
|
pub fn launch(self) -> Result<Discovery, Error> {
|
||||||
|
let name = self.server_config.name.clone();
|
||||||
|
let zeroconf_ip = self.zeroconf_ip;
|
||||||
|
|
||||||
|
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
let mut port = self.port;
|
let mut port = self.port;
|
||||||
let name = self.server_config.name.clone().into_owned();
|
let server = DiscoveryServer::new(self.server_config, &mut port, event_tx.clone())?;
|
||||||
let server = DiscoveryServer::new(self.server_config, &mut port)?;
|
|
||||||
let _zeroconf_ip = self.zeroconf_ip;
|
|
||||||
let svc;
|
|
||||||
|
|
||||||
#[cfg(feature = "with-dns-sd")]
|
let launch_svc = self.zeroconf_backend.unwrap_or(find(None)?);
|
||||||
{
|
let svc = launch_svc(name, zeroconf_ip, port, event_tx)?;
|
||||||
svc = dns_sd::DNSService::register(
|
Ok(Discovery {
|
||||||
Some(name.as_ref()),
|
server,
|
||||||
"_spotify-connect._tcp",
|
svc,
|
||||||
None,
|
event_rx,
|
||||||
None,
|
})
|
||||||
port,
|
|
||||||
&["VERSION=1.0", "CPath=/"],
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "with-dns-sd"))]
|
|
||||||
{
|
|
||||||
let _svc = if !_zeroconf_ip.is_empty() {
|
|
||||||
libmdns::Responder::spawn_with_ip_list(
|
|
||||||
&tokio::runtime::Handle::current(),
|
|
||||||
_zeroconf_ip,
|
|
||||||
)?
|
|
||||||
} else {
|
|
||||||
libmdns::Responder::spawn(&tokio::runtime::Handle::current())?
|
|
||||||
};
|
|
||||||
svc = _svc.register(
|
|
||||||
"_spotify-connect._tcp".to_owned(),
|
|
||||||
name,
|
|
||||||
port,
|
|
||||||
&["VERSION=1.0", "CPath=/"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Discovery { server, _svc: svc })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,12 +519,25 @@ impl Discovery {
|
||||||
pub fn new<T: Into<String>>(device_id: T, client_id: T) -> Result<Self, Error> {
|
pub fn new<T: Into<String>>(device_id: T, client_id: T) -> Result<Self, Error> {
|
||||||
Self::builder(device_id, client_id).launch()
|
Self::builder(device_id, client_id).launch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn shutdown(self) {
|
||||||
|
tokio::join!(self.server.shutdown(), self.svc.shutdown(),);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for Discovery {
|
impl Stream for Discovery {
|
||||||
type Item = Credentials;
|
type Item = Credentials;
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
Pin::new(&mut self.server).poll_next(cx)
|
match Pin::new(&mut self.event_rx).poll_recv(cx) {
|
||||||
|
// Yields credentials
|
||||||
|
Poll::Ready(Some(DiscoveryEvent::Credentials(creds))) => Poll::Ready(Some(creds)),
|
||||||
|
// Also terminate the stream on fatal server or MDNS/DNS-SD errors.
|
||||||
|
Poll::Ready(Some(
|
||||||
|
DiscoveryEvent::ServerError(_) | DiscoveryEvent::ZeroconfError(_),
|
||||||
|
)) => Poll::Ready(None),
|
||||||
|
Poll::Ready(None) => Poll::Ready(None),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
convert::Infallible,
|
|
||||||
net::{Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener},
|
net::{Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener},
|
||||||
pin::Pin,
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use aes::cipher::{KeyIvInit, StreamCipher};
|
use aes::cipher::{KeyIvInit, StreamCipher};
|
||||||
use base64::engine::general_purpose::STANDARD as BASE64;
|
use base64::engine::general_purpose::STANDARD as BASE64;
|
||||||
use base64::engine::Engine as _;
|
use base64::engine::Engine as _;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
|
||||||
use futures_util::{FutureExt, TryFutureExt};
|
use futures_util::{FutureExt, TryFutureExt};
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use http_body_util::{BodyExt, Full};
|
use http_body_util::{BodyExt, Full};
|
||||||
|
@ -24,7 +20,7 @@ use serde_json::json;
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
use super::DiscoveryError;
|
use super::{DiscoveryError, DiscoveryEvent};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::config::DeviceType,
|
core::config::DeviceType,
|
||||||
|
@ -47,21 +43,17 @@ struct RequestHandler {
|
||||||
config: Config,
|
config: Config,
|
||||||
username: Mutex<Option<String>>,
|
username: Mutex<Option<String>>,
|
||||||
keys: DhLocalKeys,
|
keys: DhLocalKeys,
|
||||||
tx: mpsc::UnboundedSender<Credentials>,
|
event_tx: mpsc::UnboundedSender<DiscoveryEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestHandler {
|
impl RequestHandler {
|
||||||
fn new(config: Config) -> (Self, mpsc::UnboundedReceiver<Credentials>) {
|
fn new(config: Config, event_tx: mpsc::UnboundedSender<DiscoveryEvent>) -> Self {
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
Self {
|
||||||
|
|
||||||
let discovery = Self {
|
|
||||||
config,
|
config,
|
||||||
username: Mutex::new(None),
|
username: Mutex::new(None),
|
||||||
keys: DhLocalKeys::random(&mut rand::thread_rng()),
|
keys: DhLocalKeys::random(&mut rand::thread_rng()),
|
||||||
tx,
|
event_tx,
|
||||||
};
|
}
|
||||||
|
|
||||||
(discovery, rx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_user(&self) -> String {
|
fn active_user(&self) -> String {
|
||||||
|
@ -202,7 +194,8 @@ impl RequestHandler {
|
||||||
|
|
||||||
{
|
{
|
||||||
let maybe_username = self.username.lock();
|
let maybe_username = self.username.lock();
|
||||||
self.tx.send(credentials)?;
|
self.event_tx
|
||||||
|
.send(DiscoveryEvent::Credentials(credentials))?;
|
||||||
if let Ok(mut username_field) = maybe_username {
|
if let Ok(mut username_field) = maybe_username {
|
||||||
*username_field = Some(String::from(username));
|
*username_field = Some(String::from(username));
|
||||||
} else {
|
} else {
|
||||||
|
@ -258,14 +251,22 @@ impl RequestHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum DiscoveryServerCmd {
|
||||||
|
Shutdown,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DiscoveryServer {
|
pub struct DiscoveryServer {
|
||||||
cred_rx: mpsc::UnboundedReceiver<Credentials>,
|
close_tx: oneshot::Sender<DiscoveryServerCmd>,
|
||||||
_close_tx: oneshot::Sender<Infallible>,
|
task_handle: tokio::task::JoinHandle<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiscoveryServer {
|
impl DiscoveryServer {
|
||||||
pub fn new(config: Config, port: &mut u16) -> Result<Self, Error> {
|
pub fn new(
|
||||||
let (discovery, cred_rx) = RequestHandler::new(config);
|
config: Config,
|
||||||
|
port: &mut u16,
|
||||||
|
event_tx: mpsc::UnboundedSender<DiscoveryEvent>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let discovery = RequestHandler::new(config, event_tx);
|
||||||
let address = if cfg!(windows) {
|
let address = if cfg!(windows) {
|
||||||
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), *port)
|
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), *port)
|
||||||
} else {
|
} else {
|
||||||
|
@ -297,7 +298,7 @@ impl DiscoveryServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokio::spawn(async move {
|
let task_handle = tokio::spawn(async move {
|
||||||
let discovery = Arc::new(discovery);
|
let discovery = Arc::new(discovery);
|
||||||
|
|
||||||
let server = hyper::server::conn::http1::Builder::new();
|
let server = hyper::server::conn::http1::Builder::new();
|
||||||
|
@ -326,27 +327,32 @@ impl DiscoveryServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ = &mut close_rx => {
|
_ = &mut close_rx => {
|
||||||
debug!("Shutting down discovery server");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graceful.shutdown().await;
|
graceful.shutdown().await;
|
||||||
debug!("Discovery server stopped");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
cred_rx,
|
close_tx,
|
||||||
_close_tx: close_tx,
|
task_handle,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Stream for DiscoveryServer {
|
pub async fn shutdown(self) {
|
||||||
type Item = Credentials;
|
let Self {
|
||||||
|
close_tx,
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Credentials>> {
|
task_handle,
|
||||||
self.cred_rx.poll_recv(cx)
|
..
|
||||||
|
} = self;
|
||||||
|
log::debug!("Shutting down discovery server");
|
||||||
|
if close_tx.send(DiscoveryServerCmd::Shutdown).is_err() {
|
||||||
|
log::warn!("Discovery server unexpectedly disappeared");
|
||||||
|
} else {
|
||||||
|
let _ = task_handle.await;
|
||||||
|
log::debug!("Discovery server stopped");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,4 @@ thiserror = "1.0"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = { version = "0.11.2", default-features = false, features = ["color", "humantime", "auto-color"] }
|
env_logger = { version = "0.11.2", default-features = false, features = ["color", "humantime", "auto-color"] }
|
||||||
|
|
123
src/main.rs
123
src/main.rs
|
@ -22,6 +22,7 @@ use librespot::{
|
||||||
authentication::Credentials, cache::Cache, config::DeviceType, version, Session,
|
authentication::Credentials, cache::Cache, config::DeviceType, version, Session,
|
||||||
SessionConfig,
|
SessionConfig,
|
||||||
},
|
},
|
||||||
|
discovery::DnsSdServiceBuilder,
|
||||||
playback::{
|
playback::{
|
||||||
audio_backend::{self, SinkBuilder, BACKENDS},
|
audio_backend::{self, SinkBuilder, BACKENDS},
|
||||||
config::{
|
config::{
|
||||||
|
@ -212,11 +213,11 @@ struct Setup {
|
||||||
credentials: Option<Credentials>,
|
credentials: Option<Credentials>,
|
||||||
enable_oauth: bool,
|
enable_oauth: bool,
|
||||||
oauth_port: Option<u16>,
|
oauth_port: Option<u16>,
|
||||||
enable_discovery: bool,
|
|
||||||
zeroconf_port: u16,
|
zeroconf_port: u16,
|
||||||
player_event_program: Option<String>,
|
player_event_program: Option<String>,
|
||||||
emit_sink_events: bool,
|
emit_sink_events: bool,
|
||||||
zeroconf_ip: Vec<std::net::IpAddr>,
|
zeroconf_ip: Vec<std::net::IpAddr>,
|
||||||
|
zeroconf_backend: Option<DnsSdServiceBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_setup() -> Setup {
|
fn get_setup() -> Setup {
|
||||||
|
@ -277,6 +278,7 @@ fn get_setup() -> Setup {
|
||||||
const VOLUME_RANGE: &str = "volume-range";
|
const VOLUME_RANGE: &str = "volume-range";
|
||||||
const ZEROCONF_PORT: &str = "zeroconf-port";
|
const ZEROCONF_PORT: &str = "zeroconf-port";
|
||||||
const ZEROCONF_INTERFACE: &str = "zeroconf-interface";
|
const ZEROCONF_INTERFACE: &str = "zeroconf-interface";
|
||||||
|
const ZEROCONF_BACKEND: &str = "zeroconf-backend";
|
||||||
|
|
||||||
// Mostly arbitrary.
|
// Mostly arbitrary.
|
||||||
const AP_PORT_SHORT: &str = "a";
|
const AP_PORT_SHORT: &str = "a";
|
||||||
|
@ -327,6 +329,7 @@ fn get_setup() -> Setup {
|
||||||
const NORMALISATION_RELEASE_SHORT: &str = "y";
|
const NORMALISATION_RELEASE_SHORT: &str = "y";
|
||||||
const NORMALISATION_THRESHOLD_SHORT: &str = "Z";
|
const NORMALISATION_THRESHOLD_SHORT: &str = "Z";
|
||||||
const ZEROCONF_PORT_SHORT: &str = "z";
|
const ZEROCONF_PORT_SHORT: &str = "z";
|
||||||
|
const ZEROCONF_BACKEND_SHORT: &str = ""; // no short flag
|
||||||
|
|
||||||
// Options that have different descriptions
|
// Options that have different descriptions
|
||||||
// depending on what backends were enabled at build time.
|
// depending on what backends were enabled at build time.
|
||||||
|
@ -638,6 +641,12 @@ fn get_setup() -> Setup {
|
||||||
ZEROCONF_INTERFACE,
|
ZEROCONF_INTERFACE,
|
||||||
"Comma-separated interface IP addresses on which zeroconf will bind. Defaults to all interfaces. Ignored by DNS-SD.",
|
"Comma-separated interface IP addresses on which zeroconf will bind. Defaults to all interfaces. Ignored by DNS-SD.",
|
||||||
"IP"
|
"IP"
|
||||||
|
)
|
||||||
|
.optopt(
|
||||||
|
ZEROCONF_BACKEND_SHORT,
|
||||||
|
ZEROCONF_BACKEND,
|
||||||
|
"Zeroconf (MDNS/DNS-SD) backend to use. Valid values are 'avahi', 'dns-sd' and 'libmdns', if librespot is compiled with the corresponding feature flags.",
|
||||||
|
"BACKEND"
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "passthrough-decoder")]
|
#[cfg(feature = "passthrough-decoder")]
|
||||||
|
@ -803,12 +812,22 @@ fn get_setup() -> Setup {
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Can't use `-> fmt::Arguments` due to https://github.com/rust-lang/rust/issues/92698
|
||||||
|
fn format_flag(long: &str, short: &str) -> String {
|
||||||
|
if short.is_empty() {
|
||||||
|
format!("`--{long}`")
|
||||||
|
} else {
|
||||||
|
format!("`--{long}` / `-{short}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let invalid_error_msg =
|
let invalid_error_msg =
|
||||||
|long: &str, short: &str, invalid: &str, valid_values: &str, default_value: &str| {
|
|long: &str, short: &str, invalid: &str, valid_values: &str, default_value: &str| {
|
||||||
error!("Invalid `--{long}` / `-{short}`: \"{invalid}\"");
|
let flag = format_flag(long, short);
|
||||||
|
error!("Invalid {flag}: \"{invalid}\"");
|
||||||
|
|
||||||
if !valid_values.is_empty() {
|
if !valid_values.is_empty() {
|
||||||
println!("Valid `--{long}` / `-{short}` values: {valid_values}");
|
println!("Valid {flag} values: {valid_values}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !default_value.is_empty() {
|
if !default_value.is_empty() {
|
||||||
|
@ -1190,9 +1209,22 @@ fn get_setup() -> Setup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let enable_discovery = !opt_present(DISABLE_DISCOVERY);
|
let no_discovery_reason = if !cfg!(any(
|
||||||
|
feature = "with-libmdns",
|
||||||
|
feature = "with-dns-sd",
|
||||||
|
feature = "with-avahi"
|
||||||
|
)) {
|
||||||
|
Some("librespot compiled without zeroconf backend".to_owned())
|
||||||
|
} else if opt_present(DISABLE_DISCOVERY) {
|
||||||
|
Some(format!(
|
||||||
|
"the `--{}` / `-{}` flag set",
|
||||||
|
DISABLE_DISCOVERY, DISABLE_DISCOVERY_SHORT,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if credentials.is_none() && !enable_discovery && !enable_oauth {
|
if credentials.is_none() && no_discovery_reason.is_some() && !enable_oauth {
|
||||||
error!("Credentials are required if discovery and oauth login are disabled.");
|
error!("Credentials are required if discovery and oauth login are disabled.");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
@ -1225,14 +1257,16 @@ fn get_setup() -> Setup {
|
||||||
Some(5588)
|
Some(5588)
|
||||||
};
|
};
|
||||||
|
|
||||||
if !enable_discovery && opt_present(ZEROCONF_PORT) {
|
if let Some(reason) = no_discovery_reason.as_deref() {
|
||||||
warn!(
|
if opt_present(ZEROCONF_PORT) {
|
||||||
"With the `--{}` / `-{}` flag set `--{}` / `-{}` has no effect.",
|
warn!(
|
||||||
DISABLE_DISCOVERY, DISABLE_DISCOVERY_SHORT, ZEROCONF_PORT, ZEROCONF_PORT_SHORT
|
"With {} `--{}` / `-{}` has no effect.",
|
||||||
);
|
reason, ZEROCONF_PORT, ZEROCONF_PORT_SHORT
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let zeroconf_port = if enable_discovery {
|
let zeroconf_port = if no_discovery_reason.is_none() {
|
||||||
opt_str(ZEROCONF_PORT)
|
opt_str(ZEROCONF_PORT)
|
||||||
.map(|port| match port.parse::<u16>() {
|
.map(|port| match port.parse::<u16>() {
|
||||||
Ok(value) if value != 0 => value,
|
Ok(value) if value != 0 => value,
|
||||||
|
@ -1268,6 +1302,16 @@ fn get_setup() -> Setup {
|
||||||
None => SessionConfig::default().autoplay,
|
None => SessionConfig::default().autoplay,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(reason) = no_discovery_reason.as_deref() {
|
||||||
|
if opt_present(ZEROCONF_INTERFACE) {
|
||||||
|
warn!(
|
||||||
|
"With {} {} has no effect.",
|
||||||
|
reason,
|
||||||
|
format_flag(ZEROCONF_INTERFACE, ZEROCONF_INTERFACE_SHORT),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let zeroconf_ip: Vec<std::net::IpAddr> = if opt_present(ZEROCONF_INTERFACE) {
|
let zeroconf_ip: Vec<std::net::IpAddr> = if opt_present(ZEROCONF_INTERFACE) {
|
||||||
if let Some(zeroconf_ip) = opt_str(ZEROCONF_INTERFACE) {
|
if let Some(zeroconf_ip) = opt_str(ZEROCONF_INTERFACE) {
|
||||||
zeroconf_ip
|
zeroconf_ip
|
||||||
|
@ -1293,6 +1337,39 @@ fn get_setup() -> Setup {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(reason) = no_discovery_reason.as_deref() {
|
||||||
|
if opt_present(ZEROCONF_BACKEND) {
|
||||||
|
warn!(
|
||||||
|
"With {} `--{}` / `-{}` has no effect.",
|
||||||
|
reason, ZEROCONF_BACKEND, ZEROCONF_BACKEND_SHORT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let zeroconf_backend_name = opt_str(ZEROCONF_BACKEND);
|
||||||
|
let zeroconf_backend = no_discovery_reason.is_none().then(|| {
|
||||||
|
librespot::discovery::find(zeroconf_backend_name.as_deref()).unwrap_or_else(|_| {
|
||||||
|
let available_backends: Vec<_> = librespot::discovery::BACKENDS
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(id, launch_svc)| launch_svc.map(|_| *id))
|
||||||
|
.collect();
|
||||||
|
let default_backend = librespot::discovery::BACKENDS
|
||||||
|
.iter()
|
||||||
|
.find_map(|(id, launch_svc)| launch_svc.map(|_| *id))
|
||||||
|
.unwrap_or("<none>");
|
||||||
|
|
||||||
|
invalid_error_msg(
|
||||||
|
ZEROCONF_BACKEND,
|
||||||
|
ZEROCONF_BACKEND_SHORT,
|
||||||
|
&zeroconf_backend_name.unwrap_or_default(),
|
||||||
|
&available_backends.join(", "),
|
||||||
|
default_backend,
|
||||||
|
);
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let connect_config = {
|
let connect_config = {
|
||||||
let connect_default_config = ConnectConfig::default();
|
let connect_default_config = ConnectConfig::default();
|
||||||
|
|
||||||
|
@ -1734,11 +1811,11 @@ fn get_setup() -> Setup {
|
||||||
credentials,
|
credentials,
|
||||||
enable_oauth,
|
enable_oauth,
|
||||||
oauth_port,
|
oauth_port,
|
||||||
enable_discovery,
|
|
||||||
zeroconf_port,
|
zeroconf_port,
|
||||||
player_event_program,
|
player_event_program,
|
||||||
emit_sink_events,
|
emit_sink_events,
|
||||||
zeroconf_ip,
|
zeroconf_ip,
|
||||||
|
zeroconf_backend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1767,7 +1844,7 @@ async fn main() {
|
||||||
|
|
||||||
let mut sys = System::new();
|
let mut sys = System::new();
|
||||||
|
|
||||||
if setup.enable_discovery {
|
if let Some(zeroconf_backend) = setup.zeroconf_backend {
|
||||||
// When started at boot as a service discovery may fail due to it
|
// When started at boot as a service discovery may fail due to it
|
||||||
// trying to bind to interfaces before the network is actually up.
|
// trying to bind to interfaces before the network is actually up.
|
||||||
// This could be prevented in systemd by starting the service after
|
// This could be prevented in systemd by starting the service after
|
||||||
|
@ -1787,6 +1864,7 @@ async fn main() {
|
||||||
.is_group(setup.connect_config.is_group)
|
.is_group(setup.connect_config.is_group)
|
||||||
.port(setup.zeroconf_port)
|
.port(setup.zeroconf_port)
|
||||||
.zeroconf_ip(setup.zeroconf_ip.clone())
|
.zeroconf_ip(setup.zeroconf_ip.clone())
|
||||||
|
.zeroconf_backend(zeroconf_backend)
|
||||||
.launch()
|
.launch()
|
||||||
{
|
{
|
||||||
Ok(d) => break Some(d),
|
Ok(d) => break Some(d),
|
||||||
|
@ -1955,18 +2033,25 @@ async fn main() {
|
||||||
|
|
||||||
info!("Gracefully shutting down");
|
info!("Gracefully shutting down");
|
||||||
|
|
||||||
|
let mut shutdown_tasks = tokio::task::JoinSet::new();
|
||||||
|
|
||||||
// Shutdown spirc if necessary
|
// Shutdown spirc if necessary
|
||||||
if let Some(spirc) = spirc {
|
if let Some(spirc) = spirc {
|
||||||
if let Err(e) = spirc.shutdown() {
|
if let Err(e) = spirc.shutdown() {
|
||||||
error!("error sending spirc shutdown message: {}", e);
|
error!("error sending spirc shutdown message: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut spirc_task) = spirc_task {
|
if let Some(spirc_task) = spirc_task {
|
||||||
tokio::select! {
|
shutdown_tasks.spawn(spirc_task);
|
||||||
_ = tokio::signal::ctrl_c() => (),
|
|
||||||
_ = spirc_task.as_mut() => (),
|
|
||||||
else => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(discovery) = discovery {
|
||||||
|
shutdown_tasks.spawn(discovery.shutdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = tokio::signal::ctrl_c() => (),
|
||||||
|
_ = shutdown_tasks.join_all() => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue