1
0
Fork 0
mirror of https://github.com/librespot-org/librespot.git synced 2025-10-03 17:59:24 +02:00

refactor: update to Rust 1.85 and edition 2024, use inline log args

- Update MSRV to 1.85 and Rust edition to 2024.
- Refactor all logging macros to use inline argument formatting.
- Fix import order in main.rs and examples.
- Add async environment variable setter to main.rs as safe facade.
This commit is contained in:
Roderick van Domburg 2025-08-13 16:19:39 +02:00
parent 0aec38b07a
commit 6288e7e03c
No known key found for this signature in database
GPG key ID: 607FA06CB5236AE0
30 changed files with 419 additions and 448 deletions

View file

@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- [core] MSRV is now 1.81 (breaking) - [core] MSRV is now 1.85 with Rust edition 2024 (breaking)
- [core] AP connect and handshake have a combined 5 second timeout. - [core] AP connect and handshake have a combined 5 second timeout.
- [core] `stream_from_cdn` now accepts the URL as `TryInto<Uri>` instead of `CdnUrl` (breaking) - [core] `stream_from_cdn` now accepts the URL as `TryInto<Uri>` instead of `CdnUrl` (breaking)
- [connect] Replaced `has_volume_ctrl` with `disable_volume` in `ConnectConfig` (breaking) - [connect] Replaced `has_volume_ctrl` with `disable_volume` in `ConnectConfig` (breaking)

View file

@ -1,14 +1,14 @@
[package] [package]
name = "librespot" name = "librespot"
version = "0.6.0-dev" version = "0.6.0-dev"
rust-version = "1.81" rust-version = "1.85"
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"
keywords = ["spotify"] keywords = ["spotify"]
repository = "https://github.com/librespot-org/librespot" repository = "https://github.com/librespot-org/librespot"
readme = "README.md" readme = "README.md"
edition = "2021" edition = "2024"
[workspace] [workspace]
@ -126,4 +126,4 @@ assets = [
] ]
[workspace.package] [workspace.package]
rust-version = "1.81" rust-version = "1.85"

View file

@ -366,11 +366,11 @@ impl AudioFile {
bytes_per_second: usize, bytes_per_second: usize,
) -> Result<AudioFile, Error> { ) -> Result<AudioFile, Error> {
if let Some(file) = session.cache().and_then(|cache| cache.file(file_id)) { if let Some(file) = session.cache().and_then(|cache| cache.file(file_id)) {
debug!("File {} already in cache", file_id); debug!("File {file_id} already in cache");
return Ok(AudioFile::Cached(file)); return Ok(AudioFile::Cached(file));
} }
debug!("Downloading file {}", file_id); debug!("Downloading file {file_id}");
let (complete_tx, complete_rx) = oneshot::channel(); let (complete_tx, complete_rx) = oneshot::channel();
@ -379,14 +379,14 @@ impl AudioFile {
let session_ = session.clone(); let session_ = session.clone();
session.spawn(complete_rx.map_ok(move |mut file| { session.spawn(complete_rx.map_ok(move |mut file| {
debug!("Downloading file {} complete", file_id); debug!("Downloading file {file_id} complete");
if let Some(cache) = session_.cache() { if let Some(cache) = session_.cache() {
if let Some(cache_id) = cache.file_path(file_id) { if let Some(cache_id) = cache.file_path(file_id) {
if let Err(e) = cache.save_file(file_id, &mut file) { if let Err(e) = cache.save_file(file_id, &mut file) {
error!("Error caching file {} to {:?}: {}", file_id, cache_id, e); error!("Error caching file {file_id} to {cache_id:?}: {e}");
} else { } else {
debug!("File {} cached to {:?}", file_id, cache_id); debug!("File {file_id} cached to {cache_id:?}");
} }
} }
} }
@ -465,14 +465,11 @@ impl AudioFileStreaming {
))); )));
}; };
trace!("Streaming from {}", url); trace!("Streaming from {url}");
let code = response.status(); let code = response.status();
if code != StatusCode::PARTIAL_CONTENT { if code != StatusCode::PARTIAL_CONTENT {
debug!( debug!("Opening audio file expected partial content but got: {code}");
"Opening audio file expected partial content but got: {}",
code
);
return Err(AudioFileError::StatusCode(code).into()); return Err(AudioFileError::StatusCode(code).into());
} }

View file

@ -166,7 +166,7 @@ impl Spirc {
} }
let spirc_id = SPIRC_COUNTER.fetch_add(1, Ordering::AcqRel); let spirc_id = SPIRC_COUNTER.fetch_add(1, Ordering::AcqRel);
debug!("new Spirc[{}]", spirc_id); debug!("new Spirc[{spirc_id}]");
let connect_state = ConnectState::new(config, &session); let connect_state = ConnectState::new(config, &session);
@ -446,14 +446,14 @@ impl SpircTask {
cluster_update = self.connect_state_update.next() => unwrap! { cluster_update = self.connect_state_update.next() => unwrap! {
cluster_update, cluster_update,
match |cluster_update| if let Err(e) = self.handle_cluster_update(cluster_update).await { match |cluster_update| if let Err(e) = self.handle_cluster_update(cluster_update).await {
error!("could not dispatch connect state update: {}", e); error!("could not dispatch connect state update: {e}");
} }
}, },
// main dealer request handling (dealer expects an answer) // main dealer request handling (dealer expects an answer)
request = self.connect_state_command.next() => unwrap! { request = self.connect_state_command.next() => unwrap! {
request, request,
|request| if let Err(e) = self.handle_connect_state_request(request).await { |request| if let Err(e) = self.handle_connect_state_request(request).await {
error!("couldn't handle connect state command: {}", e); error!("couldn't handle connect state command: {e}");
} }
}, },
// volume request handling is send separately (it's more like a fire forget) // volume request handling is send separately (it's more like a fire forget)
@ -491,12 +491,12 @@ impl SpircTask {
}, },
cmd = async { commands?.recv().await }, if commands.is_some() => if let Some(cmd) = cmd { cmd = async { commands?.recv().await }, if commands.is_some() => if let Some(cmd) = cmd {
if let Err(e) = self.handle_command(cmd).await { if let Err(e) = self.handle_command(cmd).await {
debug!("could not dispatch command: {}", e); debug!("could not dispatch command: {e}");
} }
}, },
event = async { player_events?.recv().await }, if player_events.is_some() => if let Some(event) = event { event = async { player_events?.recv().await }, if player_events.is_some() => if let Some(event) = event {
if let Err(e) = self.handle_player_event(event) { if let Err(e) = self.handle_player_event(event) {
error!("could not dispatch player event: {}", e); error!("could not dispatch player event: {e}");
} }
}, },
_ = async { sleep(UPDATE_STATE_DELAY).await }, if self.update_state => { _ = async { sleep(UPDATE_STATE_DELAY).await }, if self.update_state => {
@ -606,7 +606,7 @@ impl SpircTask {
} }
async fn handle_command(&mut self, cmd: SpircCommand) -> Result<(), Error> { async fn handle_command(&mut self, cmd: SpircCommand) -> Result<(), Error> {
trace!("Received SpircCommand::{:?}", cmd); trace!("Received SpircCommand::{cmd:?}");
match cmd { match cmd {
SpircCommand::Shutdown => { SpircCommand::Shutdown => {
trace!("Received SpircCommand::Shutdown"); trace!("Received SpircCommand::Shutdown");
@ -618,16 +618,15 @@ impl SpircTask {
} }
} }
SpircCommand::Activate if !self.connect_state.is_active() => { SpircCommand::Activate if !self.connect_state.is_active() => {
trace!("Received SpircCommand::{:?}", cmd); trace!("Received SpircCommand::{cmd:?}");
self.handle_activate(); self.handle_activate();
return self.notify().await; return self.notify().await;
} }
SpircCommand::Activate => warn!( SpircCommand::Activate => {
"SpircCommand::{:?} will be ignored while already active", warn!("SpircCommand::{cmd:?} will be ignored while already active")
cmd }
),
_ if !self.connect_state.is_active() => { _ if !self.connect_state.is_active() => {
warn!("SpircCommand::{:?} will be ignored while Not Active", cmd) warn!("SpircCommand::{cmd:?} will be ignored while Not Active")
} }
SpircCommand::Disconnect { pause } => { SpircCommand::Disconnect { pause } => {
if pause { if pause {
@ -787,7 +786,7 @@ impl SpircTask {
} }
async fn handle_connection_id_update(&mut self, connection_id: String) -> Result<(), Error> { async fn handle_connection_id_update(&mut self, connection_id: String) -> Result<(), Error> {
trace!("Received connection ID update: {:?}", connection_id); trace!("Received connection ID update: {connection_id:?}");
self.session.set_connection_id(&connection_id); self.session.set_connection_id(&connection_id);
let cluster = match self let cluster = match self
@ -837,7 +836,7 @@ impl SpircTask {
} }
fn handle_user_attributes_update(&mut self, update: UserAttributesUpdate) { fn handle_user_attributes_update(&mut self, update: UserAttributesUpdate) {
trace!("Received attributes update: {:#?}", update); trace!("Received attributes update: {update:#?}");
let attributes: UserAttributes = update let attributes: UserAttributes = update
.pairs .pairs
.iter() .iter()
@ -863,12 +862,7 @@ impl SpircTask {
}; };
self.session.set_user_attribute(key, new_value); self.session.set_user_attribute(key, new_value);
trace!( trace!("Received attribute mutation, {key} was {old_value} is now {new_value}");
"Received attribute mutation, {} was {} is now {}",
key,
old_value,
new_value
);
if key == "filter-explicit-content" && new_value == "1" { if key == "filter-explicit-content" && new_value == "1" {
self.player self.player
@ -882,10 +876,7 @@ impl SpircTask {
self.add_autoplay_resolving_when_required() self.add_autoplay_resolving_when_required()
} }
} else { } else {
trace!( trace!("Received attribute mutation for {key} but key was not found!");
"Received attribute mutation for {} but key was not found!",
key
);
} }
} }
} }
@ -1743,7 +1734,7 @@ impl SpircTask {
} }
fn set_volume(&mut self, volume: u16) { fn set_volume(&mut self, volume: u16) {
debug!("SpircTask::set_volume({})", volume); debug!("SpircTask::set_volume({volume})");
let old_volume = self.connect_state.device_info().volume; let old_volume = self.connect_state.device_info().volume;
let new_volume = volume as u32; let new_volume = volume as u32;

View file

@ -108,7 +108,7 @@ impl ApResolver {
if inner.data.is_any_empty() { if inner.data.is_any_empty() {
warn!("Failed to resolve all access points, using fallbacks"); warn!("Failed to resolve all access points, using fallbacks");
if let Some(error) = error { if let Some(error) = error {
warn!("Resolve access points error: {}", error); warn!("Resolve access points error: {error}");
} }
let fallback = self.parse_resolve_to_access_points(ApResolveData::fallback()); let fallback = self.parse_resolve_to_access_points(ApResolveData::fallback());

View file

@ -70,11 +70,7 @@ impl AudioKeyManager {
.map_err(|_| AudioKeyError::Channel)? .map_err(|_| AudioKeyError::Channel)?
} }
_ => { _ => {
trace!( trace!("Did not expect {cmd:?} AES key packet with data {data:#?}");
"Did not expect {:?} AES key packet with data {:#?}",
cmd,
data
);
return Err(AudioKeyError::Packet(cmd as u8).into()); return Err(AudioKeyError::Packet(cmd as u8).into());
} }
} }

View file

@ -141,7 +141,7 @@ impl FsSizeLimiter {
let list_dir = match fs::read_dir(path) { let list_dir = match fs::read_dir(path) {
Ok(list_dir) => list_dir, Ok(list_dir) => list_dir,
Err(e) => { Err(e) => {
warn!("Could not read directory {:?} in cache dir: {}", path, e); warn!("Could not read directory {path:?} in cache dir: {e}");
return; return;
} }
}; };
@ -150,7 +150,7 @@ impl FsSizeLimiter {
let entry = match entry { let entry = match entry {
Ok(entry) => entry, Ok(entry) => entry,
Err(e) => { Err(e) => {
warn!("Could not directory {:?} in cache dir: {}", path, e); warn!("Could not directory {path:?} in cache dir: {e}");
return; return;
} }
}; };
@ -166,7 +166,7 @@ impl FsSizeLimiter {
limiter.add(&path, size, access_time); limiter.add(&path, size, access_time);
} }
Err(e) => { Err(e) => {
warn!("Could not read file {:?} in cache dir: {}", path, e) warn!("Could not read file {path:?} in cache dir: {e}")
} }
} }
} }
@ -213,7 +213,7 @@ impl FsSizeLimiter {
let res = fs::remove_file(&file); let res = fs::remove_file(&file);
if let Err(e) = res { if let Err(e) = res {
warn!("Could not remove file {:?} from cache dir: {}", file, e); warn!("Could not remove file {file:?} from cache dir: {e}");
last_error = Some(e); last_error = Some(e);
} else { } else {
count += 1; count += 1;
@ -221,7 +221,7 @@ impl FsSizeLimiter {
} }
if count > 0 { if count > 0 {
info!("Removed {} cache files.", count); info!("Removed {count} cache files.");
} }
if let Some(err) = last_error { if let Some(err) = last_error {
@ -317,7 +317,7 @@ impl Cache {
// If the file did not exist, the file was probably not written // If the file did not exist, the file was probably not written
// before. Otherwise, log the error. // before. Otherwise, log the error.
if e.kind != ErrorKind::NotFound { if e.kind != ErrorKind::NotFound {
warn!("Error reading credentials from cache: {}", e); warn!("Error reading credentials from cache: {e}");
} }
None None
} }
@ -332,7 +332,7 @@ impl Cache {
}); });
if let Err(e) = result { if let Err(e) = result {
warn!("Cannot save credentials to cache: {}", e) warn!("Cannot save credentials to cache: {e}")
} }
} }
} }
@ -351,7 +351,7 @@ impl Cache {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(e) => { Err(e) => {
if e.kind != ErrorKind::NotFound { if e.kind != ErrorKind::NotFound {
warn!("Error reading volume from cache: {}", e); warn!("Error reading volume from cache: {e}");
} }
None None
} }
@ -362,7 +362,7 @@ impl Cache {
if let Some(ref location) = self.volume_location { if let Some(ref location) = self.volume_location {
let result = File::create(location).and_then(|mut file| write!(file, "{volume}")); let result = File::create(location).and_then(|mut file| write!(file, "{volume}"));
if let Err(e) = result { if let Err(e) = result {
warn!("Cannot save volume to cache: {}", e); warn!("Cannot save volume to cache: {e}");
} }
} }
} }
@ -375,7 +375,7 @@ impl Cache {
path path
}), }),
Err(e) => { Err(e) => {
warn!("Invalid FileId: {}", e); warn!("Invalid FileId: {e}");
None None
} }
} }
@ -387,14 +387,14 @@ impl Cache {
Ok(file) => { Ok(file) => {
if let Some(limiter) = self.size_limiter.as_deref() { if let Some(limiter) = self.size_limiter.as_deref() {
if !limiter.touch(&path) { if !limiter.touch(&path) {
error!("limiter could not touch {:?}", path); error!("limiter could not touch {path:?}");
} }
} }
Some(file) Some(file)
} }
Err(e) => { Err(e) => {
if e.kind() != io::ErrorKind::NotFound { if e.kind() != io::ErrorKind::NotFound {
warn!("Error reading file from cache: {}", e) warn!("Error reading file from cache: {e}")
} }
None None
} }

View file

@ -73,7 +73,7 @@ impl CdnUrl {
let cdn_url = Self { file_id, urls }; let cdn_url = Self { file_id, urls };
trace!("Resolved CDN storage: {:#?}", cdn_url); trace!("Resolved CDN storage: {cdn_url:#?}");
Ok(cdn_url) Ok(cdn_url)
} }

View file

@ -186,11 +186,7 @@ pub async fn authenticate(
Err(error_data.into()) Err(error_data.into())
} }
_ => { _ => {
trace!( trace!("Did not expect {cmd:?} AES key packet with data {data:#?}");
"Did not expect {:?} AES key packet with data {:#?}",
cmd,
data
);
Err(AuthenticationError::Packet(cmd)) Err(AuthenticationError::Packet(cmd))
} }
}; };

View file

@ -89,7 +89,7 @@ impl Responder {
.to_string(); .to_string();
if let Err(e) = self.tx.send(WsMessage::Text(response.into())) { if let Err(e) = self.tx.send(WsMessage::Text(response.into())) {
warn!("Wasn't able to reply to dealer request: {}", e); warn!("Wasn't able to reply to dealer request: {e}");
} }
} }
@ -452,7 +452,7 @@ impl Dealer {
if let Some(handle) = self.handle.take() { if let Some(handle) = self.handle.take() {
if let Err(e) = CancelOnDrop(handle).await { if let Err(e) = CancelOnDrop(handle).await {
error!("error aborting dealer operations: {}", e); error!("error aborting dealer operations: {e}");
} }
} }
} }
@ -524,13 +524,13 @@ async fn connect(
Ok(close_frame) => ws_tx.send(WsMessage::Close(close_frame)).await, Ok(close_frame) => ws_tx.send(WsMessage::Close(close_frame)).await,
Err(WsError::AlreadyClosed) | Err(WsError::ConnectionClosed) => ws_tx.flush().await, Err(WsError::AlreadyClosed) | Err(WsError::ConnectionClosed) => ws_tx.flush().await,
Err(e) => { Err(e) => {
warn!("Dealer finished with an error: {}", e); warn!("Dealer finished with an error: {e}");
ws_tx.send(WsMessage::Close(None)).await ws_tx.send(WsMessage::Close(None)).await
} }
}; };
if let Err(e) = result { if let Err(e) = result {
warn!("Error while closing websocket: {}", e); warn!("Error while closing websocket: {e}");
} }
debug!("Dropping send task"); debug!("Dropping send task");
@ -565,7 +565,7 @@ async fn connect(
_ => (), // tungstenite handles Close and Ping automatically _ => (), // tungstenite handles Close and Ping automatically
}, },
Some(Err(e)) => { Some(Err(e)) => {
warn!("Websocket connection failed: {}", e); warn!("Websocket connection failed: {e}");
break; break;
} }
None => { None => {
@ -648,13 +648,13 @@ where
() = shared.closed() => break, () = shared.closed() => break,
r = t0 => { r = t0 => {
if let Err(e) = r { if let Err(e) = r {
error!("timeout on task 0: {}", e); error!("timeout on task 0: {e}");
} }
tasks.0.take(); tasks.0.take();
}, },
r = t1 => { r = t1 => {
if let Err(e) = r { if let Err(e) = r {
error!("timeout on task 1: {}", e); error!("timeout on task 1: {e}");
} }
tasks.1.take(); tasks.1.take();
} }
@ -671,7 +671,7 @@ where
match connect(&url, proxy.as_ref(), &shared).await { match connect(&url, proxy.as_ref(), &shared).await {
Ok((s, r)) => tasks = (init_task(s), init_task(r)), Ok((s, r)) => tasks = (init_task(s), init_task(r)),
Err(e) => { Err(e) => {
error!("Error while connecting: {}", e); error!("Error while connecting: {e}");
tokio::time::sleep(RECONNECT_INTERVAL).await; tokio::time::sleep(RECONNECT_INTERVAL).await;
} }
} }

View file

@ -124,7 +124,7 @@ impl HttpClient {
); );
let user_agent = HeaderValue::from_str(user_agent_str).unwrap_or_else(|err| { let user_agent = HeaderValue::from_str(user_agent_str).unwrap_or_else(|err| {
error!("Invalid user agent <{}>: {}", user_agent_str, err); error!("Invalid user agent <{user_agent_str}>: {err}");
HeaderValue::from_static(FALLBACK_USER_AGENT) HeaderValue::from_static(FALLBACK_USER_AGENT)
}); });
@ -176,7 +176,7 @@ impl HttpClient {
} }
pub async fn request(&self, req: Request<Bytes>) -> Result<Response<Incoming>, Error> { pub async fn request(&self, req: Request<Bytes>) -> Result<Response<Incoming>, Error> {
debug!("Requesting {}", req.uri().to_string()); debug!("Requesting {}", req.uri());
// `Request` does not implement `Clone` because its `Body` may be a single-shot stream. // `Request` does not implement `Clone` because its `Body` may be a single-shot stream.
// As correct as that may be technically, we now need all this boilerplate to clone it // As correct as that may be technically, we now need all this boilerplate to clone it

View file

@ -199,7 +199,7 @@ impl Login5Manager {
inner.auth_token.clone() inner.auth_token.clone()
}); });
trace!("Got auth token: {:?}", auth_token); trace!("Got auth token: {auth_token:?}");
token.ok_or(Login5Error::NoStoredCredentials.into()) token.ok_or(Login5Error::NoStoredCredentials.into())
} }

View file

@ -131,12 +131,12 @@ impl MercuryManager {
Ok(mut sub) => { Ok(mut sub) => {
let sub_uri = sub.take_uri(); let sub_uri = sub.take_uri();
debug!("subscribed sub_uri={}", sub_uri); debug!("subscribed sub_uri={sub_uri}");
inner.subscriptions.push((sub_uri, tx.clone())); inner.subscriptions.push((sub_uri, tx.clone()));
} }
Err(e) => { Err(e) => {
error!("could not subscribe to {}: {}", uri, e); error!("could not subscribe to {uri}: {e}");
} }
} }
} }
@ -163,7 +163,7 @@ impl MercuryManager {
manager.lock(move |inner| { manager.lock(move |inner| {
if !inner.invalid { if !inner.invalid {
debug!("listening to uri={}", uri); debug!("listening to uri={uri}");
inner.subscriptions.push((uri, tx)); inner.subscriptions.push((uri, tx));
} }
}); });
@ -283,14 +283,14 @@ impl MercuryManager {
Ok(()) Ok(())
} else { } else {
debug!("unknown subscription uri={}", &response.uri); debug!("unknown subscription uri={}", &response.uri);
trace!("response pushed over Mercury: {:?}", response); trace!("response pushed over Mercury: {response:?}");
Err(MercuryError::Response(response).into()) Err(MercuryError::Response(response).into())
} }
} else if let Some(cb) = pending.callback { } else if let Some(cb) = pending.callback {
cb.send(Ok(response)).map_err(|_| MercuryError::Channel)?; cb.send(Ok(response)).map_err(|_| MercuryError::Channel)?;
Ok(()) Ok(())
} else { } else {
error!("can't handle Mercury response: {:?}", response); error!("can't handle Mercury response: {response:?}");
Err(MercuryError::Response(response).into()) Err(MercuryError::Response(response).into())
} }
} }

View file

@ -273,7 +273,7 @@ impl Session {
let session_weak = self.weak(); let session_weak = self.weak();
tokio::spawn(async move { tokio::spawn(async move {
if let Err(e) = sender_task.await { if let Err(e) = sender_task.await {
error!("{}", e); error!("{e}");
if let Some(session) = session_weak.try_upgrade() { if let Some(session) = session_weak.try_upgrade() {
if !session.is_invalid() { if !session.is_invalid() {
session.shutdown(); session.shutdown();
@ -360,7 +360,7 @@ impl Session {
fn check_catalogue(attributes: &UserAttributes) { fn check_catalogue(attributes: &UserAttributes) {
if let Some(account_type) = attributes.get("type") { if let Some(account_type) = attributes.get("type") {
if account_type != "premium" { if account_type != "premium" {
error!("librespot does not support {:?} accounts.", account_type); error!("librespot does not support {account_type:?} accounts.");
info!("Please support Spotify and your artists and sign up for a premium account."); info!("Please support Spotify and your artists and sign up for a premium account.");
// TODO: logout instead of exiting // TODO: logout instead of exiting
@ -566,7 +566,7 @@ impl KeepAliveState {
.map(|t| t.as_secs_f64()) .map(|t| t.as_secs_f64())
.unwrap_or(f64::INFINITY); .unwrap_or(f64::INFINITY);
trace!("keep-alive state: {:?}, timeout in {:.1}", self, delay); trace!("keep-alive state: {self:?}, timeout in {delay:.1}");
} }
} }
@ -619,7 +619,7 @@ where
let cmd = match packet_type { let cmd = match packet_type {
Some(cmd) => cmd, Some(cmd) => cmd,
None => { None => {
trace!("Ignoring unknown packet {:x}", cmd); trace!("Ignoring unknown packet {cmd:x}");
return Err(SessionError::Packet(cmd).into()); return Err(SessionError::Packet(cmd).into());
} }
}; };
@ -667,7 +667,7 @@ where
} }
Some(CountryCode) => { Some(CountryCode) => {
let country = String::from_utf8(data.as_ref().to_owned())?; let country = String::from_utf8(data.as_ref().to_owned())?;
info!("Country: {:?}", country); info!("Country: {country:?}");
session.0.data.write().user_data.country = country; session.0.data.write().user_data.country = country;
Ok(()) Ok(())
} }
@ -710,7 +710,7 @@ where
} }
} }
trace!("Received product info: {:#?}", user_attributes); trace!("Received product info: {user_attributes:#?}");
Session::check_catalogue(&user_attributes); Session::check_catalogue(&user_attributes);
session.0.data.write().user_data.attributes = user_attributes; session.0.data.write().user_data.attributes = user_attributes;
@ -721,7 +721,7 @@ where
| Some(UnknownDataAllZeros) | Some(UnknownDataAllZeros)
| Some(LicenseVersion) => Ok(()), | Some(LicenseVersion) => Ok(()),
_ => { _ => {
trace!("Ignoring {:?} packet with data {:#?}", cmd, data); trace!("Ignoring {cmd:?} packet with data {data:#?}");
Err(SessionError::Packet(cmd as u8).into()) Err(SessionError::Packet(cmd as u8).into())
} }
} }
@ -749,7 +749,7 @@ where
Poll::Ready(Some(Ok((cmd, data)))) => { Poll::Ready(Some(Ok((cmd, data)))) => {
let result = self.as_mut().dispatch(&session, cmd, data); let result = self.as_mut().dispatch(&session, cmd, data);
if let Err(e) = result { if let Err(e) = result {
debug!("could not dispatch command: {}", e); debug!("could not dispatch command: {e}");
} }
} }
Poll::Ready(None) => { Poll::Ready(None) => {

View file

@ -7,7 +7,7 @@ use crate::proxytunnel;
pub async fn connect(host: &str, port: u16, proxy: Option<&Url>) -> io::Result<TcpStream> { pub async fn connect(host: &str, port: u16, proxy: Option<&Url>) -> io::Result<TcpStream> {
let socket = if let Some(proxy_url) = proxy { let socket = if let Some(proxy_url) = proxy {
info!("Using proxy \"{}\"", proxy_url); info!("Using proxy \"{proxy_url}\"");
let socket_addr = proxy_url.socket_addrs(|| None).and_then(|addrs| { let socket_addr = proxy_url.socket_addrs(|| None).and_then(|addrs| {
addrs.into_iter().next().ok_or_else(|| { addrs.into_iter().next().ok_or_else(|| {

View file

@ -311,20 +311,12 @@ impl SpClient {
continue; continue;
} }
Err(e) => { Err(e) => {
trace!( trace!("Answer not accepted {count}/{MAX_TRIES}: {e}");
"Answer not accepted {}/{}: {}",
count,
MAX_TRIES,
e
);
} }
} }
} }
Err(e) => trace!( Err(e) => trace!(
"Unable to solve hash cash challenge {}/{}: {}", "Unable to solve hash cash challenge {count}/{MAX_TRIES}: {e}"
count,
MAX_TRIES,
e
), ),
} }
@ -373,7 +365,7 @@ impl SpClient {
inner.client_token = Some(client_token); inner.client_token = Some(client_token);
}); });
trace!("Got client token: {:?}", granted_token); trace!("Got client token: {granted_token:?}");
Ok(access_token) Ok(access_token)
} }
@ -542,7 +534,7 @@ impl SpClient {
} }
} }
debug!("Error was: {:?}", last_response); debug!("Error was: {last_response:?}");
} }
last_response last_response

View file

@ -86,8 +86,7 @@ impl TokenProvider {
} }
trace!( trace!(
"Requested token in scopes {:?} unavailable or expired, requesting new token.", "Requested token in scopes {scopes:?} unavailable or expired, requesting new token."
scopes
); );
let query_uri = format!( let query_uri = format!(
@ -100,7 +99,7 @@ impl TokenProvider {
let response = request.await?; let response = request.await?;
let data = response.payload.first().ok_or(TokenError::Empty)?.to_vec(); let data = response.payload.first().ok_or(TokenError::Empty)?.to_vec();
let token = Token::from_json(String::from_utf8(data)?)?; let token = Token::from_json(String::from_utf8(data)?)?;
trace!("Got token: {:#?}", token); trace!("Got token: {token:#?}");
self.lock(|inner| inner.tokens.push(token.clone())); self.lock(|inner| inner.tokens.push(token.clone()));
Ok(token) Ok(token)
} }

View file

@ -419,7 +419,7 @@ fn launch_libmdns(
}; };
if let Err(e) = inner() { if let Err(e) = inner() {
log::error!("libmdns error: {}", e); log::error!("libmdns error: {e}");
let _ = status_tx.send(DiscoveryEvent::ZeroconfError(e)); let _ = status_tx.send(DiscoveryEvent::ZeroconfError(e));
} }
}); });

View file

@ -170,7 +170,7 @@ impl RequestHandler {
.map_err(|_| DiscoveryError::HmacError(base_key.to_vec()))?; .map_err(|_| DiscoveryError::HmacError(base_key.to_vec()))?;
h.update(encrypted); h.update(encrypted);
if h.verify_slice(cksum).is_err() { if h.verify_slice(cksum).is_err() {
warn!("Login error for user {:?}: MAC mismatch", username); warn!("Login error for user {username:?}: MAC mismatch");
let result = json!({ let result = json!({
"status": 102, "status": 102,
"spotifyError": 1, "spotifyError": 1,
@ -314,7 +314,7 @@ impl DiscoveryServer {
discovery discovery
.clone() .clone()
.handle(request) .handle(request)
.inspect_err(|e| error!("could not handle discovery request: {}", e)) .inspect_err(|e| error!("could not handle discovery request: {e}"))
.and_then(|x| async move { Ok(x) }) .and_then(|x| async move { Ok(x) })
.map(Result::unwrap) // guaranteed by `and_then` above .map(Result::unwrap) // guaranteed by `and_then` above
}); });

View file

@ -1,7 +1,7 @@
use librespot::{ use librespot::{
connect::{ConnectConfig, LoadRequest, LoadRequestOptions, Spirc}, connect::{ConnectConfig, LoadRequest, LoadRequestOptions, Spirc},
core::{ core::{
authentication::Credentials, cache::Cache, config::SessionConfig, session::Session, Error, Error, authentication::Credentials, cache::Cache, config::SessionConfig, session::Session,
}, },
playback::mixer::MixerConfig, playback::mixer::MixerConfig,
playback::{ playback::{

View file

@ -78,10 +78,7 @@ impl Playlist {
let length = tracks.len(); let length = tracks.len();
let expected_length = self.length as usize; let expected_length = self.length as usize;
if length != expected_length { if length != expected_length {
warn!( warn!("Got {length} tracks, but the list should contain {expected_length} tracks.",);
"Got {} tracks, but the list should contain {} tracks.",
length, expected_length,
);
} }
tracks tracks

View file

@ -18,7 +18,6 @@ use oauth2::{
EndpointSet, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, TokenUrl, EndpointSet, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, TokenUrl,
}; };
use oauth2::{EmptyExtraTokenFields, PkceCodeVerifier, RefreshToken, StandardTokenResponse}; use oauth2::{EmptyExtraTokenFields, PkceCodeVerifier, RefreshToken, StandardTokenResponse};
use reqwest;
use std::io; use std::io;
use std::sync::mpsc; use std::sync::mpsc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -156,7 +155,7 @@ fn get_authcode_listener(
addr: socket_address, addr: socket_address,
e, e,
})?; })?;
info!("OAuth server listening on {:?}", socket_address); info!("OAuth server listening on {socket_address:?}");
// The server will terminate itself after collecting the first code. // The server will terminate itself after collecting the first code.
let mut stream = listener let mut stream = listener

View file

@ -226,7 +226,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
let buffer_size = { let buffer_size = {
let max = match hwp.get_buffer_size_max() { let max = match hwp.get_buffer_size_max() {
Err(e) => { Err(e) => {
trace!("Error getting the device's max Buffer size: {}", e); trace!("Error getting the device's max Buffer size: {e}");
ZERO_FRAMES ZERO_FRAMES
} }
Ok(s) => s, Ok(s) => s,
@ -234,7 +234,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
let min = match hwp.get_buffer_size_min() { let min = match hwp.get_buffer_size_min() {
Err(e) => { Err(e) => {
trace!("Error getting the device's min Buffer size: {}", e); trace!("Error getting the device's min Buffer size: {e}");
ZERO_FRAMES ZERO_FRAMES
} }
Ok(s) => s, Ok(s) => s,
@ -246,11 +246,11 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
.find(|f| (min..=max).contains(f)) .find(|f| (min..=max).contains(f))
{ {
Some(size) => { Some(size) => {
trace!("Desired Frames per Buffer: {:?}", size); trace!("Desired Frames per Buffer: {size:?}");
match hwp.set_buffer_size_near(size) { match hwp.set_buffer_size_near(size) {
Err(e) => { Err(e) => {
trace!("Error setting the device's Buffer size: {}", e); trace!("Error setting the device's Buffer size: {e}");
ZERO_FRAMES ZERO_FRAMES
} }
Ok(s) => s, Ok(s) => s,
@ -267,17 +267,9 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
}; };
if buffer_size == ZERO_FRAMES { if buffer_size == ZERO_FRAMES {
trace!( trace!("Desired Buffer Frame range: {MIN_BUFFER:?} - {MAX_BUFFER:?}",);
"Desired Buffer Frame range: {:?} - {:?}",
MIN_BUFFER,
MAX_BUFFER
);
trace!( trace!("Actual Buffer Frame range as reported by the device: {min:?} - {max:?}",);
"Actual Buffer Frame range as reported by the device: {:?} - {:?}",
min,
max
);
} }
buffer_size buffer_size
@ -289,7 +281,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
} else { } else {
let max = match hwp.get_period_size_max() { let max = match hwp.get_period_size_max() {
Err(e) => { Err(e) => {
trace!("Error getting the device's max Period size: {}", e); trace!("Error getting the device's max Period size: {e}");
ZERO_FRAMES ZERO_FRAMES
} }
Ok(s) => s, Ok(s) => s,
@ -297,7 +289,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
let min = match hwp.get_period_size_min() { let min = match hwp.get_period_size_min() {
Err(e) => { Err(e) => {
trace!("Error getting the device's min Period size: {}", e); trace!("Error getting the device's min Period size: {e}");
ZERO_FRAMES ZERO_FRAMES
} }
Ok(s) => s, Ok(s) => s,
@ -312,11 +304,11 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
.find(|f| (min..=max).contains(f)) .find(|f| (min..=max).contains(f))
{ {
Some(size) => { Some(size) => {
trace!("Desired Frames per Period: {:?}", size); trace!("Desired Frames per Period: {size:?}");
match hwp.set_period_size_near(size, ValueOr::Nearest) { match hwp.set_period_size_near(size, ValueOr::Nearest) {
Err(e) => { Err(e) => {
trace!("Error setting the device's Period size: {}", e); trace!("Error setting the device's Period size: {e}");
ZERO_FRAMES ZERO_FRAMES
} }
Ok(s) => s, Ok(s) => s,
@ -334,20 +326,14 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
}; };
if period_size == ZERO_FRAMES { if period_size == ZERO_FRAMES {
trace!("Buffer size: {:?}", buffer_size); trace!("Buffer size: {buffer_size:?}");
trace!( trace!(
"Desired Period Frame range: {:?} (Buffer size / {:?}) - {:?} (Buffer size / {:?})", "Desired Period Frame range: {min_period:?} (Buffer size / {MIN_PERIOD_DIVISOR:?}) - {max_period:?} (Buffer size / {MAX_PERIOD_DIVISOR:?})",
min_period,
MIN_PERIOD_DIVISOR,
max_period,
MAX_PERIOD_DIVISOR,
); );
trace!( trace!(
"Actual Period Frame range as reported by the device: {:?} - {:?}", "Actual Period Frame range as reported by the device: {min:?} - {max:?}",
min,
max
); );
} }
@ -381,14 +367,14 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
pcm.sw_params(&swp).map_err(AlsaError::Pcm)?; pcm.sw_params(&swp).map_err(AlsaError::Pcm)?;
trace!("Actual Frames per Buffer: {:?}", frames_per_buffer); trace!("Actual Frames per Buffer: {frames_per_buffer:?}");
trace!("Actual Frames per Period: {:?}", frames_per_period); trace!("Actual Frames per Period: {frames_per_period:?}");
// Let ALSA do the math for us. // Let ALSA do the math for us.
pcm.frames_to_bytes(frames_per_period) as usize pcm.frames_to_bytes(frames_per_period) as usize
}; };
trace!("Period Buffer size in bytes: {:?}", bytes_per_period); trace!("Period Buffer size in bytes: {bytes_per_period:?}");
Ok((pcm, bytes_per_period)) Ok((pcm, bytes_per_period))
} }
@ -401,7 +387,7 @@ impl Open for AlsaSink {
exit(0); exit(0);
} }
Err(e) => { Err(e) => {
error!("{}", e); error!("{e}");
exit(1); exit(1);
} }
}, },
@ -410,7 +396,7 @@ impl Open for AlsaSink {
} }
.to_string(); .to_string();
info!("Using AlsaSink with format: {:?}", format); info!("Using AlsaSink with format: {format:?}");
Self { Self {
pcm: None, pcm: None,
@ -500,10 +486,7 @@ impl AlsaSink {
Err(e) => { Err(e) => {
// Capture and log the original error as a warning, and then try to recover. // Capture and log the original error as a warning, and then try to recover.
// If recovery fails then forward that error back to player. // If recovery fails then forward that error back to player.
warn!( warn!("Error writing from AlsaSink buffer to PCM, trying to recover, {e}");
"Error writing from AlsaSink buffer to PCM, trying to recover, {}",
e
);
pcm.try_recover(e, false).map_err(AlsaError::OnWrite) pcm.try_recover(e, false).map_err(AlsaError::OnWrite)
} }

View file

@ -48,7 +48,7 @@ impl Open for StdoutSink {
exit(0); exit(0);
} }
info!("Using StdoutSink (pipe) with format: {:?}", format); info!("Using StdoutSink (pipe) with format: {format:?}");
Self { Self {
output: None, output: None,

View file

@ -72,7 +72,7 @@ impl Open for SubprocessSink {
exit(0); exit(0);
} }
info!("Using SubprocessSink with format: {:?}", format); info!("Using SubprocessSink with format: {format:?}");
Self { Self {
shell_command, shell_command,

View file

@ -33,10 +33,7 @@ impl MappedCtrl for VolumeCtrl {
} }
} else { } else {
// Ensure not to return -inf or NaN due to division by zero. // Ensure not to return -inf or NaN due to division by zero.
error!( error!("{self:?} does not work with 0 dB range, using linear mapping instead");
"{:?} does not work with 0 dB range, using linear mapping instead",
self
);
normalized_volume normalized_volume
}; };
@ -67,10 +64,7 @@ impl MappedCtrl for VolumeCtrl {
} }
} else { } else {
// Ensure not to return -inf or NaN due to division by zero. // Ensure not to return -inf or NaN due to division by zero.
error!( error!("{self:?} does not work with 0 dB range, using linear mapping instead");
"{:?} does not work with 0 dB range, using linear mapping instead",
self
);
mapped_volume mapped_volume
}; };
@ -88,10 +82,10 @@ impl MappedCtrl for VolumeCtrl {
fn set_db_range(&mut self, new_db_range: f64) { fn set_db_range(&mut self, new_db_range: f64) {
match self { match self {
Self::Cubic(ref mut db_range) | Self::Log(ref mut db_range) => *db_range = new_db_range, Self::Cubic(ref mut db_range) | Self::Log(ref mut db_range) => *db_range = new_db_range,
_ => error!("Invalid to set dB range for volume control type {:?}", self), _ => error!("Invalid to set dB range for volume control type {self:?}"),
} }
debug!("Volume control is now {:?}", self) debug!("Volume control is now {self:?}")
} }
fn range_ok(&self) -> bool { fn range_ok(&self) -> bool {

View file

@ -17,7 +17,7 @@ pub struct SoftMixer {
impl Mixer for SoftMixer { impl Mixer for SoftMixer {
fn open(config: MixerConfig) -> Result<Self, Error> { fn open(config: MixerConfig) -> Result<Self, Error> {
let volume_ctrl = config.volume_ctrl; let volume_ctrl = config.volume_ctrl;
info!("Mixing with softvol and volume control: {:?}", volume_ctrl); info!("Mixing with softvol and volume control: {volume_ctrl:?}");
Ok(Self { Ok(Self {
volume: Arc::new(AtomicU64::new(f64::to_bits(0.5))), volume: Arc::new(AtomicU64::new(f64::to_bits(0.5))),

View file

@ -324,8 +324,7 @@ impl NormalisationData {
let newpos = file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET))?; let newpos = file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET))?;
if newpos != SPOTIFY_NORMALIZATION_HEADER_START_OFFSET { if newpos != SPOTIFY_NORMALIZATION_HEADER_START_OFFSET {
error!( error!(
"NormalisationData::parse_from_file seeking to {} but position is now {}", "NormalisationData::parse_from_file seeking to {SPOTIFY_NORMALIZATION_HEADER_START_OFFSET} but position is now {newpos}"
SPOTIFY_NORMALIZATION_HEADER_START_OFFSET, newpos
); );
error!("Falling back to default (non-track and non-album) normalisation data."); error!("Falling back to default (non-track and non-album) normalisation data.");
@ -396,8 +395,7 @@ impl NormalisationData {
let limiting_db = factor_db + config.normalisation_threshold_dbfs.abs(); let limiting_db = factor_db + config.normalisation_threshold_dbfs.abs();
warn!( warn!(
"This track may exceed dBFS by {:.2} dB and be subject to {:.2} dB of dynamic limiting at its peak.", "This track may exceed dBFS by {factor_db:.2} dB and be subject to {limiting_db:.2} dB of dynamic limiting at its peak."
factor_db, limiting_db
); );
} else if factor > threshold_ratio { } else if factor > threshold_ratio {
let limiting_db = gain_db let limiting_db = gain_db
@ -405,15 +403,14 @@ impl NormalisationData {
+ config.normalisation_threshold_dbfs.abs(); + config.normalisation_threshold_dbfs.abs();
info!( info!(
"This track may be subject to {:.2} dB of dynamic limiting at its peak.", "This track may be subject to {limiting_db:.2} dB of dynamic limiting at its peak."
limiting_db
); );
} }
factor factor
}; };
debug!("Normalisation Data: {:?}", data); debug!("Normalisation Data: {data:?}");
debug!( debug!(
"Calculated Normalisation Factor for {:?}: {:.2}%", "Calculated Normalisation Factor for {:?}: {:.2}%",
config.normalisation_type, config.normalisation_type,
@ -464,7 +461,7 @@ impl Player {
let handle = thread::spawn(move || { let handle = thread::spawn(move || {
let player_id = PLAYER_COUNTER.fetch_add(1, Ordering::AcqRel); let player_id = PLAYER_COUNTER.fetch_add(1, Ordering::AcqRel);
debug!("new Player [{}]", player_id); debug!("new Player [{player_id}]");
let converter = Converter::new(config.ditherer); let converter = Converter::new(config.ditherer);
@ -517,7 +514,7 @@ impl Player {
fn command(&self, cmd: PlayerCommand) { fn command(&self, cmd: PlayerCommand) {
if let Some(commands) = self.commands.as_ref() { if let Some(commands) = self.commands.as_ref() {
if let Err(e) = commands.send(cmd) { if let Err(e) = commands.send(cmd) {
error!("Player Commands Error: {}", e); error!("Player Commands Error: {e}");
} }
} }
} }
@ -636,7 +633,7 @@ impl Drop for Player {
self.commands = None; self.commands = None;
if let Some(handle) = self.thread_handle.take() { if let Some(handle) = self.thread_handle.take() {
if let Err(e) = handle.join() { if let Err(e) = handle.join() {
error!("Player thread Error: {:?}", e); error!("Player thread Error: {e:?}");
} }
} }
} }
@ -787,10 +784,7 @@ impl PlayerState {
}; };
} }
_ => { _ => {
error!( error!("Called playing_to_end_of_track in non-playing state: {new_state:?}");
"Called playing_to_end_of_track in non-playing state: {:?}",
new_state
);
exit(1); exit(1);
} }
} }
@ -832,10 +826,7 @@ impl PlayerState {
}; };
} }
_ => { _ => {
error!( error!("PlayerState::paused_to_playing in invalid state: {new_state:?}");
"PlayerState::paused_to_playing in invalid state: {:?}",
new_state
);
exit(1); exit(1);
} }
} }
@ -876,10 +867,7 @@ impl PlayerState {
}; };
} }
_ => { _ => {
error!( error!("PlayerState::playing_to_paused in invalid state: {new_state:?}");
"PlayerState::playing_to_paused in invalid state: {:?}",
new_state
);
exit(1); exit(1);
} }
} }
@ -894,7 +882,7 @@ struct PlayerTrackLoader {
impl PlayerTrackLoader { impl PlayerTrackLoader {
async fn find_available_alternative(&self, audio_item: AudioItem) -> Option<AudioItem> { async fn find_available_alternative(&self, audio_item: AudioItem) -> Option<AudioItem> {
if let Err(e) = audio_item.availability { if let Err(e) = audio_item.availability {
error!("Track is unavailable: {}", e); error!("Track is unavailable: {e}");
None None
} else if !audio_item.files.is_empty() { } else if !audio_item.files.is_empty() {
Some(audio_item) Some(audio_item)
@ -958,7 +946,7 @@ impl PlayerTrackLoader {
} }
}, },
Err(e) => { Err(e) => {
error!("Unable to load audio item: {:?}", e); error!("Unable to load audio item: {e:?}");
return None; return None;
} }
}; };
@ -1026,7 +1014,7 @@ impl PlayerTrackLoader {
let encrypted_file = match encrypted_file.await { let encrypted_file = match encrypted_file.await {
Ok(encrypted_file) => encrypted_file, Ok(encrypted_file) => encrypted_file,
Err(e) => { Err(e) => {
error!("Unable to load encrypted file: {:?}", e); error!("Unable to load encrypted file: {e:?}");
return None; return None;
} }
}; };
@ -1041,7 +1029,7 @@ impl PlayerTrackLoader {
let key = match self.session.audio_key().request(spotify_id, file_id).await { let key = match self.session.audio_key().request(spotify_id, file_id).await {
Ok(key) => Some(key), Ok(key) => Some(key),
Err(e) => { Err(e) => {
warn!("Unable to load key, continuing without decryption: {}", e); warn!("Unable to load key, continuing without decryption: {e}");
None None
} }
}; };
@ -1064,7 +1052,7 @@ impl PlayerTrackLoader {
) { ) {
Ok(audio_file) => audio_file, Ok(audio_file) => audio_file,
Err(e) => { Err(e) => {
error!("PlayerTrackLoader::load_track error opening subfile: {}", e); error!("PlayerTrackLoader::load_track error opening subfile: {e}");
return None; return None;
} }
}; };
@ -1098,10 +1086,7 @@ impl PlayerTrackLoader {
let mut decoder = match decoder_type { let mut decoder = match decoder_type {
Ok(decoder) => decoder, Ok(decoder) => decoder,
Err(e) if is_cached => { Err(e) if is_cached => {
warn!( warn!("Unable to read cached audio file: {e}. Trying to download it.");
"Unable to read cached audio file: {}. Trying to download it.",
e
);
match self.session.cache() { match self.session.cache() {
Some(cache) => { Some(cache) => {
@ -1120,7 +1105,7 @@ impl PlayerTrackLoader {
continue; continue;
} }
Err(e) => { Err(e) => {
error!("Unable to read audio file: {}", e); error!("Unable to read audio file: {e}");
return None; return None;
} }
}; };
@ -1130,7 +1115,7 @@ impl PlayerTrackLoader {
// If the position is invalid just start from // If the position is invalid just start from
// the beginning of the track. // the beginning of the track.
let position_ms = if position_ms > duration_ms { let position_ms = if position_ms > duration_ms {
warn!("Invalid start position of {} ms exceeds track's duration of {} ms, starting track from the beginning", position_ms, duration_ms); warn!("Invalid start position of {position_ms} ms exceeds track's duration of {duration_ms} ms, starting track from the beginning");
0 0
} else { } else {
position_ms position_ms
@ -1144,8 +1129,7 @@ impl PlayerTrackLoader {
Ok(new_position_ms) => new_position_ms, Ok(new_position_ms) => new_position_ms,
Err(e) => { Err(e) => {
error!( error!(
"PlayerTrackLoader::load_track error seeking to starting position {}: {}", "PlayerTrackLoader::load_track error seeking to starting position {position_ms}: {e}"
position_ms, e
); );
return None; return None;
} }
@ -1195,7 +1179,7 @@ impl Future for PlayerInternal {
if let Some(cmd) = cmd { if let Some(cmd) = cmd {
if let Err(e) = self.handle_command(cmd) { if let Err(e) = self.handle_command(cmd) {
error!("Error handling command: {}", e); error!("Error handling command: {e}");
} }
} }
@ -1225,8 +1209,7 @@ impl Future for PlayerInternal {
} }
Poll::Ready(Err(e)) => { Poll::Ready(Err(e)) => {
error!( error!(
"Skipping to next track, unable to load track <{:?}>: {:?}", "Skipping to next track, unable to load track <{track_id:?}>: {e:?}"
track_id, e
); );
self.send_event(PlayerEvent::Unavailable { self.send_event(PlayerEvent::Unavailable {
track_id, track_id,
@ -1253,7 +1236,7 @@ impl Future for PlayerInternal {
}; };
} }
Poll::Ready(Err(_)) => { Poll::Ready(Err(_)) => {
debug!("Unable to preload {:?}", track_id); debug!("Unable to preload {track_id:?}");
self.preload = PlayerPreload::None; self.preload = PlayerPreload::None;
// Let Spirc know that the track was unavailable. // Let Spirc know that the track was unavailable.
if let PlayerState::Playing { if let PlayerState::Playing {
@ -1368,7 +1351,7 @@ impl Future for PlayerInternal {
} }
} }
Err(e) => { Err(e) => {
error!("Skipping to next track, unable to decode samples for track <{:?}>: {:?}", track_id, e); error!("Skipping to next track, unable to decode samples for track <{track_id:?}>: {e:?}");
self.send_event(PlayerEvent::EndOfTrack { self.send_event(PlayerEvent::EndOfTrack {
track_id, track_id,
play_request_id, play_request_id,
@ -1381,7 +1364,7 @@ impl Future for PlayerInternal {
self.handle_packet(result, normalisation_factor); self.handle_packet(result, normalisation_factor);
} }
Err(e) => { Err(e) => {
error!("Skipping to next track, unable to get next packet for track <{:?}>: {:?}", track_id, e); error!("Skipping to next track, unable to get next packet for track <{track_id:?}>: {e:?}");
self.send_event(PlayerEvent::EndOfTrack { self.send_event(PlayerEvent::EndOfTrack {
track_id, track_id,
play_request_id, play_request_id,
@ -1443,7 +1426,7 @@ impl PlayerInternal {
match self.sink.start() { match self.sink.start() {
Ok(()) => self.sink_status = SinkStatus::Running, Ok(()) => self.sink_status = SinkStatus::Running,
Err(e) => { Err(e) => {
error!("{}", e); error!("{e}");
self.handle_pause(); self.handle_pause();
} }
} }
@ -1466,7 +1449,7 @@ impl PlayerInternal {
} }
} }
Err(e) => { Err(e) => {
error!("{}", e); error!("{e}");
exit(1); exit(1);
} }
} }
@ -1694,7 +1677,7 @@ impl PlayerInternal {
} }
if let Err(e) = self.sink.write(packet, &mut self.converter) { if let Err(e) = self.sink.write(packet, &mut self.converter) {
error!("{}", e); error!("{e}");
self.handle_pause(); self.handle_pause();
} }
} }
@ -2085,7 +2068,7 @@ impl PlayerInternal {
}); });
} }
} }
Err(e) => error!("PlayerInternal::handle_command_seek error: {}", e), Err(e) => error!("PlayerInternal::handle_command_seek error: {e}"),
} }
} else { } else {
error!("Player::seek called from invalid state: {:?}", self.state); error!("Player::seek called from invalid state: {:?}", self.state);
@ -2107,7 +2090,7 @@ impl PlayerInternal {
} }
fn handle_command(&mut self, cmd: PlayerCommand) -> PlayerResult { fn handle_command(&mut self, cmd: PlayerCommand) -> PlayerResult {
debug!("command={:?}", cmd); debug!("command={cmd:?}");
match cmd { match cmd {
PlayerCommand::Load { PlayerCommand::Load {
track_id, track_id,

View file

@ -5,18 +5,18 @@ use librespot::playback::mixer::alsamixer::AlsaMixer;
use librespot::{ use librespot::{
connect::{ConnectConfig, Spirc}, connect::{ConnectConfig, Spirc},
core::{ core::{
authentication::Credentials, cache::Cache, config::DeviceType, version, Session, Session, SessionConfig, authentication::Credentials, cache::Cache, config::DeviceType,
SessionConfig, version,
}, },
discovery::DnsSdServiceBuilder, discovery::DnsSdServiceBuilder,
playback::{ playback::{
audio_backend::{self, SinkBuilder, BACKENDS}, audio_backend::{self, BACKENDS, SinkBuilder},
config::{ config::{
AudioFormat, Bitrate, NormalisationMethod, NormalisationType, PlayerConfig, VolumeCtrl, AudioFormat, Bitrate, NormalisationMethod, NormalisationType, PlayerConfig, VolumeCtrl,
}, },
dither, dither,
mixer::{self, MixerConfig, MixerFn}, mixer::{self, MixerConfig, MixerFn},
player::{coefficient_to_duration, duration_to_coefficient, Player}, player::{Player, coefficient_to_duration, duration_to_coefficient},
}, },
}; };
use librespot_oauth::OAuthClientBuilder; use librespot_oauth::OAuthClientBuilder;
@ -24,6 +24,7 @@ use log::{debug, error, info, trace, warn};
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use std::{ use std::{
env, env,
ffi::OsStr,
fs::create_dir_all, fs::create_dir_all,
ops::RangeInclusive, ops::RangeInclusive,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -34,10 +35,11 @@ use std::{
}; };
use sysinfo::{ProcessesToUpdate, System}; use sysinfo::{ProcessesToUpdate, System};
use thiserror::Error; use thiserror::Error;
use tokio::sync::Semaphore;
use url::Url; use url::Url;
mod player_event_handler; mod player_event_handler;
use player_event_handler::{run_program_on_sink_events, EventHandler}; use player_event_handler::{EventHandler, run_program_on_sink_events};
fn device_id(name: &str) -> String { fn device_id(name: &str) -> String {
HEXLOWER.encode(&Sha1::digest(name.as_bytes())) HEXLOWER.encode(&Sha1::digest(name.as_bytes()))
@ -75,7 +77,9 @@ fn setup_logging(quiet: bool, verbose: bool) {
builder.init(); builder.init();
if verbose && quiet { if verbose && quiet {
warn!("`--verbose` and `--quiet` are mutually exclusive. Logging can not be both verbose and quiet. Using verbose mode."); warn!(
"`--verbose` and `--quiet` are mutually exclusive. Logging can not be both verbose and quiet. Using verbose mode."
);
} }
} }
} }
@ -219,7 +223,7 @@ struct Setup {
zeroconf_backend: Option<DnsSdServiceBuilder>, zeroconf_backend: Option<DnsSdServiceBuilder>,
} }
fn get_setup() -> Setup { async fn get_setup() -> Setup {
const VALID_INITIAL_VOLUME_RANGE: RangeInclusive<u16> = 0..=100; const VALID_INITIAL_VOLUME_RANGE: RangeInclusive<u16> = 0..=100;
const VALID_VOLUME_RANGE: RangeInclusive<f64> = 0.0..=100.0; const VALID_VOLUME_RANGE: RangeInclusive<f64> = 0.0..=100.0;
const VALID_NORMALISATION_KNEE_RANGE: RangeInclusive<f64> = 0.0..=10.0; const VALID_NORMALISATION_KNEE_RANGE: RangeInclusive<f64> = 0.0..=10.0;
@ -810,7 +814,9 @@ fn get_setup() -> Setup {
ALSA_MIXER_CONTROL, ALSA_MIXER_CONTROL,
] { ] {
if opt_present(a) { if opt_present(a) {
warn!("Alsa specific options have no effect if the alsa backend is not enabled at build time."); warn!(
"Alsa specific options have no effect if the alsa backend is not enabled at build time."
);
break; break;
} }
} }
@ -1196,7 +1202,9 @@ fn get_setup() -> Setup {
empty_string_error_msg(USERNAME, USERNAME_SHORT); empty_string_error_msg(USERNAME, USERNAME_SHORT);
} }
if opt_present(PASSWORD) { if opt_present(PASSWORD) {
error!("Invalid `--{PASSWORD}` / `-{PASSWORD_SHORT}`: Password authentication no longer supported, use OAuth"); error!(
"Invalid `--{PASSWORD}` / `-{PASSWORD_SHORT}`: Password authentication no longer supported, use OAuth"
);
exit(1); exit(1);
} }
match cached_creds { match cached_creds {
@ -1265,9 +1273,7 @@ fn get_setup() -> Setup {
if let Some(reason) = no_discovery_reason.as_deref() { if let Some(reason) = no_discovery_reason.as_deref() {
if opt_present(ZEROCONF_PORT) { if opt_present(ZEROCONF_PORT) {
warn!( warn!("With {reason} `--{ZEROCONF_PORT}` / `-{ZEROCONF_PORT_SHORT}` has no effect.");
"With {reason} `--{ZEROCONF_PORT}` / `-{ZEROCONF_PORT_SHORT}` has no effect."
);
} }
} }
@ -1393,31 +1399,31 @@ fn get_setup() -> Setup {
name.clone() name.clone()
}; };
env::set_var("PULSE_PROP_application.name", pulseaudio_name); set_env_var("PULSE_PROP_application.name", pulseaudio_name).await;
} }
if env::var("PULSE_PROP_application.version").is_err() { if env::var("PULSE_PROP_application.version").is_err() {
env::set_var("PULSE_PROP_application.version", version::SEMVER); set_env_var("PULSE_PROP_application.version", version::SEMVER).await;
} }
if env::var("PULSE_PROP_application.icon_name").is_err() { if env::var("PULSE_PROP_application.icon_name").is_err() {
env::set_var("PULSE_PROP_application.icon_name", "audio-x-generic"); set_env_var("PULSE_PROP_application.icon_name", "audio-x-generic").await;
} }
if env::var("PULSE_PROP_application.process.binary").is_err() { if env::var("PULSE_PROP_application.process.binary").is_err() {
env::set_var("PULSE_PROP_application.process.binary", "librespot"); set_env_var("PULSE_PROP_application.process.binary", "librespot").await;
} }
if env::var("PULSE_PROP_stream.description").is_err() { if env::var("PULSE_PROP_stream.description").is_err() {
env::set_var("PULSE_PROP_stream.description", "Spotify Connect endpoint"); set_env_var("PULSE_PROP_stream.description", "Spotify Connect endpoint").await;
} }
if env::var("PULSE_PROP_media.software").is_err() { if env::var("PULSE_PROP_media.software").is_err() {
env::set_var("PULSE_PROP_media.software", "Spotify"); set_env_var("PULSE_PROP_media.software", "Spotify").await;
} }
if env::var("PULSE_PROP_media.role").is_err() { if env::var("PULSE_PROP_media.role").is_err() {
env::set_var("PULSE_PROP_media.role", "music"); set_env_var("PULSE_PROP_media.role", "music").await;
} }
} }
@ -1839,6 +1845,23 @@ fn get_setup() -> Setup {
} }
} }
// Initialize a static semaphore with only one permit, which is used to
// prevent setting environment variables from running in parallel.
static PERMIT: Semaphore = Semaphore::const_new(1);
async fn set_env_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
let permit = PERMIT
.acquire()
.await
.expect("Failed to acquire semaphore permit");
// SAFETY: This is safe because setting the environment variable will wait if the permit is
// already acquired by other callers.
unsafe { env::set_var(key, value) }
// Drop the permit manually, so the compiler doesn't optimize it away as unused variable.
drop(permit);
}
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
const RUST_BACKTRACE: &str = "RUST_BACKTRACE"; const RUST_BACKTRACE: &str = "RUST_BACKTRACE";
@ -1847,10 +1870,10 @@ async fn main() {
const RECONNECT_RATE_LIMIT: usize = 5; const RECONNECT_RATE_LIMIT: usize = 5;
if env::var(RUST_BACKTRACE).is_err() { if env::var(RUST_BACKTRACE).is_err() {
env::set_var(RUST_BACKTRACE, "full") set_env_var(RUST_BACKTRACE, "full").await;
} }
let setup = get_setup(); let setup = get_setup().await;
let mut last_credentials = None; let mut last_credentials = None;
let mut spirc: Option<Spirc> = None; let mut spirc: Option<Spirc> = None;

View file

@ -14,7 +14,8 @@ pub struct EventHandler {
impl EventHandler { impl EventHandler {
pub fn new(mut player_events: PlayerEventChannel, onevent: &str) -> Self { pub fn new(mut player_events: PlayerEventChannel, onevent: &str) -> Self {
let on_event = onevent.to_string(); let on_event = onevent.to_string();
let thread_handle = Some(thread::spawn(move || loop { let thread_handle = Some(thread::spawn(move || {
loop {
match player_events.blocking_recv() { match player_events.blocking_recv() {
None => break, None => break,
Some(event) => { Some(event) => {
@ -22,7 +23,8 @@ impl EventHandler {
match event { match event {
PlayerEvent::PlayRequestIdChanged { play_request_id } => { PlayerEvent::PlayRequestIdChanged { play_request_id } => {
env_vars.insert("PLAYER_EVENT", "play_request_id_changed".to_string()); env_vars
.insert("PLAYER_EVENT", "play_request_id_changed".to_string());
env_vars.insert("PLAY_REQUEST_ID", play_request_id.to_string()); env_vars.insert("PLAY_REQUEST_ID", play_request_id.to_string());
} }
PlayerEvent::TrackChanged { audio_item } => { PlayerEvent::TrackChanged { audio_item } => {
@ -31,7 +33,8 @@ impl EventHandler {
warn!("PlayerEvent::TrackChanged: Invalid track id: {e}") warn!("PlayerEvent::TrackChanged: Invalid track id: {e}")
} }
Ok(id) => { Ok(id) => {
env_vars.insert("PLAYER_EVENT", "track_changed".to_string()); env_vars
.insert("PLAYER_EVENT", "track_changed".to_string());
env_vars.insert("TRACK_ID", id); env_vars.insert("TRACK_ID", id);
env_vars.insert("URI", audio_item.uri); env_vars.insert("URI", audio_item.uri);
env_vars.insert("NAME", audio_item.name); env_vars.insert("NAME", audio_item.name);
@ -45,10 +48,14 @@ impl EventHandler {
.join("\n"), .join("\n"),
); );
env_vars.insert("LANGUAGE", audio_item.language.join("\n")); env_vars.insert("LANGUAGE", audio_item.language.join("\n"));
env_vars env_vars.insert(
.insert("DURATION_MS", audio_item.duration_ms.to_string()); "DURATION_MS",
env_vars audio_item.duration_ms.to_string(),
.insert("IS_EXPLICIT", audio_item.is_explicit.to_string()); );
env_vars.insert(
"IS_EXPLICIT",
audio_item.is_explicit.to_string(),
);
match audio_item.unique_fields { match audio_item.unique_fields {
UniqueFields::Track { UniqueFields::Track {
@ -69,12 +76,16 @@ impl EventHandler {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"), .join("\n"),
); );
env_vars env_vars.insert(
.insert("ALBUM_ARTISTS", album_artists.join("\n")); "ALBUM_ARTISTS",
album_artists.join("\n"),
);
env_vars.insert("ALBUM", album); env_vars.insert("ALBUM", album);
env_vars.insert("POPULARITY", popularity.to_string()); env_vars
.insert("POPULARITY", popularity.to_string());
env_vars.insert("NUMBER", number.to_string()); env_vars.insert("NUMBER", number.to_string());
env_vars.insert("DISC_NUMBER", disc_number.to_string()); env_vars
.insert("DISC_NUMBER", disc_number.to_string());
} }
UniqueFields::Episode { UniqueFields::Episode {
description, description,
@ -131,13 +142,17 @@ impl EventHandler {
env_vars.insert("TRACK_ID", id); env_vars.insert("TRACK_ID", id);
} }
}, },
PlayerEvent::Preloading { track_id, .. } => match track_id.to_base62() { PlayerEvent::Preloading { track_id, .. } => {
Err(e) => warn!("PlayerEvent::Preloading: Invalid track id: {e}"), match track_id.to_base62() {
Err(e) => {
warn!("PlayerEvent::Preloading: Invalid track id: {e}")
}
Ok(id) => { Ok(id) => {
env_vars.insert("PLAYER_EVENT", "preloading".to_string()); env_vars.insert("PLAYER_EVENT", "preloading".to_string());
env_vars.insert("TRACK_ID", id); env_vars.insert("TRACK_ID", id);
} }
}, }
}
PlayerEvent::TimeToPreloadNextTrack { track_id, .. } => { PlayerEvent::TimeToPreloadNextTrack { track_id, .. } => {
match track_id.to_base62() { match track_id.to_base62() {
Err(e) => warn!( Err(e) => warn!(
@ -149,14 +164,19 @@ impl EventHandler {
} }
} }
} }
PlayerEvent::EndOfTrack { track_id, .. } => match track_id.to_base62() { PlayerEvent::EndOfTrack { track_id, .. } => {
Err(e) => warn!("PlayerEvent::EndOfTrack: Invalid track id: {e}"), match track_id.to_base62() {
Err(e) => {
warn!("PlayerEvent::EndOfTrack: Invalid track id: {e}")
}
Ok(id) => { Ok(id) => {
env_vars.insert("PLAYER_EVENT", "end_of_track".to_string()); env_vars.insert("PLAYER_EVENT", "end_of_track".to_string());
env_vars.insert("TRACK_ID", id); env_vars.insert("TRACK_ID", id);
} }
}, }
PlayerEvent::Unavailable { track_id, .. } => match track_id.to_base62() { }
PlayerEvent::Unavailable { track_id, .. } => match track_id.to_base62()
{
Err(e) => warn!("PlayerEvent::Unavailable: Invalid track id: {e}"), Err(e) => warn!("PlayerEvent::Unavailable: Invalid track id: {e}"),
Ok(id) => { Ok(id) => {
env_vars.insert("PLAYER_EVENT", "unavailable".to_string()); env_vars.insert("PLAYER_EVENT", "unavailable".to_string());
@ -188,7 +208,8 @@ impl EventHandler {
warn!("PlayerEvent::PositionCorrection: Invalid track id: {e}") warn!("PlayerEvent::PositionCorrection: Invalid track id: {e}")
} }
Ok(id) => { Ok(id) => {
env_vars.insert("PLAYER_EVENT", "position_correction".to_string()); env_vars
.insert("PLAYER_EVENT", "position_correction".to_string());
env_vars.insert("TRACK_ID", id); env_vars.insert("TRACK_ID", id);
env_vars.insert("POSITION_MS", position_ms.to_string()); env_vars.insert("POSITION_MS", position_ms.to_string());
} }
@ -215,7 +236,8 @@ impl EventHandler {
client_brand_name, client_brand_name,
client_model_name, client_model_name,
} => { } => {
env_vars.insert("PLAYER_EVENT", "session_client_changed".to_string()); env_vars
.insert("PLAYER_EVENT", "session_client_changed".to_string());
env_vars.insert("CLIENT_ID", client_id); env_vars.insert("CLIENT_ID", client_id);
env_vars.insert("CLIENT_NAME", client_name); env_vars.insert("CLIENT_NAME", client_name);
env_vars.insert("CLIENT_BRAND_NAME", client_brand_name); env_vars.insert("CLIENT_BRAND_NAME", client_brand_name);
@ -251,6 +273,7 @@ impl EventHandler {
} }
} }
} }
}
})); }));
Self { thread_handle } Self { thread_handle }
@ -287,9 +310,7 @@ pub fn run_program_on_sink_events(sink_status: SinkStatus, onevent: &str) {
fn run_program(env_vars: HashMap<&str, String>, onevent: &str) { fn run_program(env_vars: HashMap<&str, String>, onevent: &str) {
let mut v: Vec<&str> = onevent.split_whitespace().collect(); let mut v: Vec<&str> = onevent.split_whitespace().collect();
debug!( debug!("Running {onevent} with environment variables:\n{env_vars:#?}");
"Running {onevent} with environment variables:\n{env_vars:#?}"
);
match Command::new(v.remove(0)) match Command::new(v.remove(0))
.args(&v) .args(&v)