diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6550ef..986487b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 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] `stream_from_cdn` now accepts the URL as `TryInto` instead of `CdnUrl` (breaking) - [connect] Replaced `has_volume_ctrl` with `disable_volume` in `ConnectConfig` (breaking) diff --git a/Cargo.toml b/Cargo.toml index 831b4f5e..a930b39e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "librespot" version = "0.6.0-dev" -rust-version = "1.81" +rust-version = "1.85" authors = ["Librespot Org"] license = "MIT" description = "An open source client library for Spotify, with support for Spotify Connect" keywords = ["spotify"] repository = "https://github.com/librespot-org/librespot" readme = "README.md" -edition = "2021" +edition = "2024" [workspace] @@ -126,4 +126,4 @@ assets = [ ] [workspace.package] -rust-version = "1.81" +rust-version = "1.85" diff --git a/audio/src/fetch/mod.rs b/audio/src/fetch/mod.rs index 414ce3cf..96a6eb96 100644 --- a/audio/src/fetch/mod.rs +++ b/audio/src/fetch/mod.rs @@ -366,11 +366,11 @@ impl AudioFile { bytes_per_second: usize, ) -> Result { 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)); } - debug!("Downloading file {}", file_id); + debug!("Downloading file {file_id}"); let (complete_tx, complete_rx) = oneshot::channel(); @@ -379,14 +379,14 @@ impl AudioFile { let session_ = session.clone(); 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_id) = cache.file_path(file_id) { 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 { - 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(); if code != StatusCode::PARTIAL_CONTENT { - debug!( - "Opening audio file expected partial content but got: {}", - code - ); + debug!("Opening audio file expected partial content but got: {code}"); return Err(AudioFileError::StatusCode(code).into()); } diff --git a/connect/src/spirc.rs b/connect/src/spirc.rs index 14c0f015..57f8b978 100644 --- a/connect/src/spirc.rs +++ b/connect/src/spirc.rs @@ -166,7 +166,7 @@ impl Spirc { } 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); @@ -446,14 +446,14 @@ impl SpircTask { cluster_update = self.connect_state_update.next() => unwrap! { cluster_update, 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) request = self.connect_state_command.next() => unwrap! { request, |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) @@ -491,12 +491,12 @@ impl SpircTask { }, cmd = async { commands?.recv().await }, if commands.is_some() => if let Some(cmd) = cmd { 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 { 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 => { @@ -606,7 +606,7 @@ impl SpircTask { } async fn handle_command(&mut self, cmd: SpircCommand) -> Result<(), Error> { - trace!("Received SpircCommand::{:?}", cmd); + trace!("Received SpircCommand::{cmd:?}"); match cmd { SpircCommand::Shutdown => { trace!("Received SpircCommand::Shutdown"); @@ -618,16 +618,15 @@ impl SpircTask { } } SpircCommand::Activate if !self.connect_state.is_active() => { - trace!("Received SpircCommand::{:?}", cmd); + trace!("Received SpircCommand::{cmd:?}"); self.handle_activate(); return self.notify().await; } - SpircCommand::Activate => warn!( - "SpircCommand::{:?} will be ignored while already active", - cmd - ), + SpircCommand::Activate => { + warn!("SpircCommand::{cmd:?} will be ignored while already 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 } => { if pause { @@ -787,7 +786,7 @@ impl SpircTask { } 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); let cluster = match self @@ -837,7 +836,7 @@ impl SpircTask { } fn handle_user_attributes_update(&mut self, update: UserAttributesUpdate) { - trace!("Received attributes update: {:#?}", update); + trace!("Received attributes update: {update:#?}"); let attributes: UserAttributes = update .pairs .iter() @@ -863,12 +862,7 @@ impl SpircTask { }; self.session.set_user_attribute(key, new_value); - trace!( - "Received attribute mutation, {} was {} is now {}", - key, - old_value, - new_value - ); + trace!("Received attribute mutation, {key} was {old_value} is now {new_value}"); if key == "filter-explicit-content" && new_value == "1" { self.player @@ -882,10 +876,7 @@ impl SpircTask { self.add_autoplay_resolving_when_required() } } else { - trace!( - "Received attribute mutation for {} but key was not found!", - key - ); + trace!("Received attribute mutation for {key} but key was not found!"); } } } @@ -1743,7 +1734,7 @@ impl SpircTask { } 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 new_volume = volume as u32; diff --git a/core/src/apresolve.rs b/core/src/apresolve.rs index 37e4ba3d..82d37e73 100644 --- a/core/src/apresolve.rs +++ b/core/src/apresolve.rs @@ -108,7 +108,7 @@ impl ApResolver { if inner.data.is_any_empty() { warn!("Failed to resolve all access points, using fallbacks"); 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()); diff --git a/core/src/audio_key.rs b/core/src/audio_key.rs index 0ffa8383..79710ec5 100644 --- a/core/src/audio_key.rs +++ b/core/src/audio_key.rs @@ -70,11 +70,7 @@ impl AudioKeyManager { .map_err(|_| AudioKeyError::Channel)? } _ => { - trace!( - "Did not expect {:?} AES key packet with data {:#?}", - cmd, - data - ); + trace!("Did not expect {cmd:?} AES key packet with data {data:#?}"); return Err(AudioKeyError::Packet(cmd as u8).into()); } } diff --git a/core/src/cache.rs b/core/src/cache.rs index af373ff1..fe38b681 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -141,7 +141,7 @@ impl FsSizeLimiter { let list_dir = match fs::read_dir(path) { Ok(list_dir) => list_dir, Err(e) => { - warn!("Could not read directory {:?} in cache dir: {}", path, e); + warn!("Could not read directory {path:?} in cache dir: {e}"); return; } }; @@ -150,7 +150,7 @@ impl FsSizeLimiter { let entry = match entry { Ok(entry) => entry, Err(e) => { - warn!("Could not directory {:?} in cache dir: {}", path, e); + warn!("Could not directory {path:?} in cache dir: {e}"); return; } }; @@ -166,7 +166,7 @@ impl FsSizeLimiter { limiter.add(&path, size, access_time); } 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); 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); } else { count += 1; @@ -221,7 +221,7 @@ impl FsSizeLimiter { } if count > 0 { - info!("Removed {} cache files.", count); + info!("Removed {count} cache files."); } if let Some(err) = last_error { @@ -317,7 +317,7 @@ impl Cache { // If the file did not exist, the file was probably not written // before. Otherwise, log the error. if e.kind != ErrorKind::NotFound { - warn!("Error reading credentials from cache: {}", e); + warn!("Error reading credentials from cache: {e}"); } None } @@ -332,7 +332,7 @@ impl Cache { }); 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), Err(e) => { if e.kind != ErrorKind::NotFound { - warn!("Error reading volume from cache: {}", e); + warn!("Error reading volume from cache: {e}"); } None } @@ -362,7 +362,7 @@ impl Cache { if let Some(ref location) = self.volume_location { let result = File::create(location).and_then(|mut file| write!(file, "{volume}")); 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 }), Err(e) => { - warn!("Invalid FileId: {}", e); + warn!("Invalid FileId: {e}"); None } } @@ -387,14 +387,14 @@ impl Cache { Ok(file) => { if let Some(limiter) = self.size_limiter.as_deref() { if !limiter.touch(&path) { - error!("limiter could not touch {:?}", path); + error!("limiter could not touch {path:?}"); } } Some(file) } Err(e) => { if e.kind() != io::ErrorKind::NotFound { - warn!("Error reading file from cache: {}", e) + warn!("Error reading file from cache: {e}") } None } diff --git a/core/src/cdn_url.rs b/core/src/cdn_url.rs index 44f4488f..49e4757c 100644 --- a/core/src/cdn_url.rs +++ b/core/src/cdn_url.rs @@ -73,7 +73,7 @@ impl CdnUrl { let cdn_url = Self { file_id, urls }; - trace!("Resolved CDN storage: {:#?}", cdn_url); + trace!("Resolved CDN storage: {cdn_url:#?}"); Ok(cdn_url) } diff --git a/core/src/connection/mod.rs b/core/src/connection/mod.rs index ca89e87b..f83827c0 100644 --- a/core/src/connection/mod.rs +++ b/core/src/connection/mod.rs @@ -186,11 +186,7 @@ pub async fn authenticate( Err(error_data.into()) } _ => { - trace!( - "Did not expect {:?} AES key packet with data {:#?}", - cmd, - data - ); + trace!("Did not expect {cmd:?} AES key packet with data {data:#?}"); Err(AuthenticationError::Packet(cmd)) } }; diff --git a/core/src/dealer/mod.rs b/core/src/dealer/mod.rs index 8934daab..c5158c84 100644 --- a/core/src/dealer/mod.rs +++ b/core/src/dealer/mod.rs @@ -89,7 +89,7 @@ impl Responder { .to_string(); 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 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, Err(WsError::AlreadyClosed) | Err(WsError::ConnectionClosed) => ws_tx.flush().await, Err(e) => { - warn!("Dealer finished with an error: {}", e); + warn!("Dealer finished with an error: {e}"); ws_tx.send(WsMessage::Close(None)).await } }; if let Err(e) = result { - warn!("Error while closing websocket: {}", e); + warn!("Error while closing websocket: {e}"); } debug!("Dropping send task"); @@ -565,7 +565,7 @@ async fn connect( _ => (), // tungstenite handles Close and Ping automatically }, Some(Err(e)) => { - warn!("Websocket connection failed: {}", e); + warn!("Websocket connection failed: {e}"); break; } None => { @@ -648,13 +648,13 @@ where () = shared.closed() => break, r = t0 => { if let Err(e) = r { - error!("timeout on task 0: {}", e); + error!("timeout on task 0: {e}"); } tasks.0.take(); }, r = t1 => { if let Err(e) = r { - error!("timeout on task 1: {}", e); + error!("timeout on task 1: {e}"); } tasks.1.take(); } @@ -671,7 +671,7 @@ where match connect(&url, proxy.as_ref(), &shared).await { Ok((s, r)) => tasks = (init_task(s), init_task(r)), Err(e) => { - error!("Error while connecting: {}", e); + error!("Error while connecting: {e}"); tokio::time::sleep(RECONNECT_INTERVAL).await; } } diff --git a/core/src/http_client.rs b/core/src/http_client.rs index 1bb464d8..728857f9 100644 --- a/core/src/http_client.rs +++ b/core/src/http_client.rs @@ -124,7 +124,7 @@ impl HttpClient { ); 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) }); @@ -176,7 +176,7 @@ impl HttpClient { } pub async fn request(&self, req: Request) -> Result, 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. // As correct as that may be technically, we now need all this boilerplate to clone it diff --git a/core/src/login5.rs b/core/src/login5.rs index 75f739a1..e94978f6 100644 --- a/core/src/login5.rs +++ b/core/src/login5.rs @@ -199,7 +199,7 @@ impl Login5Manager { inner.auth_token.clone() }); - trace!("Got auth token: {:?}", auth_token); + trace!("Got auth token: {auth_token:?}"); token.ok_or(Login5Error::NoStoredCredentials.into()) } diff --git a/core/src/mercury/mod.rs b/core/src/mercury/mod.rs index 76b060a3..db8f1c0a 100644 --- a/core/src/mercury/mod.rs +++ b/core/src/mercury/mod.rs @@ -131,12 +131,12 @@ impl MercuryManager { Ok(mut sub) => { 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())); } 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| { if !inner.invalid { - debug!("listening to uri={}", uri); + debug!("listening to uri={uri}"); inner.subscriptions.push((uri, tx)); } }); @@ -283,14 +283,14 @@ impl MercuryManager { Ok(()) } else { debug!("unknown subscription uri={}", &response.uri); - trace!("response pushed over Mercury: {:?}", response); + trace!("response pushed over Mercury: {response:?}"); Err(MercuryError::Response(response).into()) } } else if let Some(cb) = pending.callback { cb.send(Ok(response)).map_err(|_| MercuryError::Channel)?; Ok(()) } else { - error!("can't handle Mercury response: {:?}", response); + error!("can't handle Mercury response: {response:?}"); Err(MercuryError::Response(response).into()) } } diff --git a/core/src/session.rs b/core/src/session.rs index 3d39b8c7..2d0b5987 100644 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -273,7 +273,7 @@ impl Session { let session_weak = self.weak(); tokio::spawn(async move { if let Err(e) = sender_task.await { - error!("{}", e); + error!("{e}"); if let Some(session) = session_weak.try_upgrade() { if !session.is_invalid() { session.shutdown(); @@ -360,7 +360,7 @@ impl Session { fn check_catalogue(attributes: &UserAttributes) { if let Some(account_type) = attributes.get("type") { 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."); // TODO: logout instead of exiting @@ -566,7 +566,7 @@ impl KeepAliveState { .map(|t| t.as_secs_f64()) .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 { Some(cmd) => cmd, None => { - trace!("Ignoring unknown packet {:x}", cmd); + trace!("Ignoring unknown packet {cmd:x}"); return Err(SessionError::Packet(cmd).into()); } }; @@ -667,7 +667,7 @@ where } Some(CountryCode) => { let country = String::from_utf8(data.as_ref().to_owned())?; - info!("Country: {:?}", country); + info!("Country: {country:?}"); session.0.data.write().user_data.country = country; Ok(()) } @@ -710,7 +710,7 @@ where } } - trace!("Received product info: {:#?}", user_attributes); + trace!("Received product info: {user_attributes:#?}"); Session::check_catalogue(&user_attributes); session.0.data.write().user_data.attributes = user_attributes; @@ -721,7 +721,7 @@ where | Some(UnknownDataAllZeros) | Some(LicenseVersion) => Ok(()), _ => { - trace!("Ignoring {:?} packet with data {:#?}", cmd, data); + trace!("Ignoring {cmd:?} packet with data {data:#?}"); Err(SessionError::Packet(cmd as u8).into()) } } @@ -749,7 +749,7 @@ where Poll::Ready(Some(Ok((cmd, data)))) => { let result = self.as_mut().dispatch(&session, cmd, data); if let Err(e) = result { - debug!("could not dispatch command: {}", e); + debug!("could not dispatch command: {e}"); } } Poll::Ready(None) => { diff --git a/core/src/socket.rs b/core/src/socket.rs index 84ac6024..71b0d6b9 100644 --- a/core/src/socket.rs +++ b/core/src/socket.rs @@ -7,7 +7,7 @@ use crate::proxytunnel; pub async fn connect(host: &str, port: u16, proxy: Option<&Url>) -> io::Result { 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| { addrs.into_iter().next().ok_or_else(|| { diff --git a/core/src/spclient.rs b/core/src/spclient.rs index 3b3c679f..22df5006 100644 --- a/core/src/spclient.rs +++ b/core/src/spclient.rs @@ -311,20 +311,12 @@ impl SpClient { continue; } Err(e) => { - trace!( - "Answer not accepted {}/{}: {}", - count, - MAX_TRIES, - e - ); + trace!("Answer not accepted {count}/{MAX_TRIES}: {e}"); } } } Err(e) => trace!( - "Unable to solve hash cash challenge {}/{}: {}", - count, - MAX_TRIES, - e + "Unable to solve hash cash challenge {count}/{MAX_TRIES}: {e}" ), } @@ -373,7 +365,7 @@ impl SpClient { inner.client_token = Some(client_token); }); - trace!("Got client token: {:?}", granted_token); + trace!("Got client token: {granted_token:?}"); Ok(access_token) } @@ -542,7 +534,7 @@ impl SpClient { } } - debug!("Error was: {:?}", last_response); + debug!("Error was: {last_response:?}"); } last_response diff --git a/core/src/token.rs b/core/src/token.rs index 445b8aeb..7e604797 100644 --- a/core/src/token.rs +++ b/core/src/token.rs @@ -86,8 +86,7 @@ impl TokenProvider { } trace!( - "Requested token in scopes {:?} unavailable or expired, requesting new token.", - scopes + "Requested token in scopes {scopes:?} unavailable or expired, requesting new token." ); let query_uri = format!( @@ -100,7 +99,7 @@ impl TokenProvider { let response = request.await?; let data = response.payload.first().ok_or(TokenError::Empty)?.to_vec(); 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())); Ok(token) } diff --git a/discovery/src/lib.rs b/discovery/src/lib.rs index 36fd2509..68dfb0b8 100644 --- a/discovery/src/lib.rs +++ b/discovery/src/lib.rs @@ -419,7 +419,7 @@ fn launch_libmdns( }; if let Err(e) = inner() { - log::error!("libmdns error: {}", e); + log::error!("libmdns error: {e}"); let _ = status_tx.send(DiscoveryEvent::ZeroconfError(e)); } }); diff --git a/discovery/src/server.rs b/discovery/src/server.rs index bdf18045..6e4b0135 100644 --- a/discovery/src/server.rs +++ b/discovery/src/server.rs @@ -170,7 +170,7 @@ impl RequestHandler { .map_err(|_| DiscoveryError::HmacError(base_key.to_vec()))?; h.update(encrypted); 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!({ "status": 102, "spotifyError": 1, @@ -314,7 +314,7 @@ impl DiscoveryServer { discovery .clone() .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) }) .map(Result::unwrap) // guaranteed by `and_then` above }); diff --git a/examples/play_connect.rs b/examples/play_connect.rs index bd57df7d..1be6345b 100644 --- a/examples/play_connect.rs +++ b/examples/play_connect.rs @@ -1,7 +1,7 @@ use librespot::{ connect::{ConnectConfig, LoadRequest, LoadRequestOptions, Spirc}, core::{ - authentication::Credentials, cache::Cache, config::SessionConfig, session::Session, Error, + Error, authentication::Credentials, cache::Cache, config::SessionConfig, session::Session, }, playback::mixer::MixerConfig, playback::{ diff --git a/metadata/src/playlist/list.rs b/metadata/src/playlist/list.rs index b81e61e4..7fa0b311 100644 --- a/metadata/src/playlist/list.rs +++ b/metadata/src/playlist/list.rs @@ -78,10 +78,7 @@ impl Playlist { let length = tracks.len(); let expected_length = self.length as usize; if length != expected_length { - warn!( - "Got {} tracks, but the list should contain {} tracks.", - length, expected_length, - ); + warn!("Got {length} tracks, but the list should contain {expected_length} tracks.",); } tracks diff --git a/oauth/src/lib.rs b/oauth/src/lib.rs index 7b08140f..c97b2bf0 100644 --- a/oauth/src/lib.rs +++ b/oauth/src/lib.rs @@ -18,7 +18,6 @@ use oauth2::{ EndpointSet, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, TokenUrl, }; use oauth2::{EmptyExtraTokenFields, PkceCodeVerifier, RefreshToken, StandardTokenResponse}; -use reqwest; use std::io; use std::sync::mpsc; use std::time::{Duration, Instant}; @@ -156,7 +155,7 @@ fn get_authcode_listener( addr: socket_address, 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. let mut stream = listener diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index dc0b87c0..afbf3195 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -226,7 +226,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> let buffer_size = { let max = match hwp.get_buffer_size_max() { Err(e) => { - trace!("Error getting the device's max Buffer size: {}", e); + trace!("Error getting the device's max Buffer size: {e}"); ZERO_FRAMES } 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() { Err(e) => { - trace!("Error getting the device's min Buffer size: {}", e); + trace!("Error getting the device's min Buffer size: {e}"); ZERO_FRAMES } Ok(s) => s, @@ -246,11 +246,11 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> .find(|f| (min..=max).contains(f)) { Some(size) => { - trace!("Desired Frames per Buffer: {:?}", size); + trace!("Desired Frames per Buffer: {size:?}"); match hwp.set_buffer_size_near(size) { Err(e) => { - trace!("Error setting the device's Buffer size: {}", e); + trace!("Error setting the device's Buffer size: {e}"); ZERO_FRAMES } Ok(s) => s, @@ -267,17 +267,9 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> }; if buffer_size == ZERO_FRAMES { - trace!( - "Desired Buffer Frame range: {:?} - {:?}", - MIN_BUFFER, - MAX_BUFFER - ); + trace!("Desired Buffer Frame range: {MIN_BUFFER:?} - {MAX_BUFFER:?}",); - trace!( - "Actual Buffer Frame range as reported by the device: {:?} - {:?}", - min, - max - ); + trace!("Actual Buffer Frame range as reported by the device: {min:?} - {max:?}",); } buffer_size @@ -289,7 +281,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> } else { let max = match hwp.get_period_size_max() { Err(e) => { - trace!("Error getting the device's max Period size: {}", e); + trace!("Error getting the device's max Period size: {e}"); ZERO_FRAMES } 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() { Err(e) => { - trace!("Error getting the device's min Period size: {}", e); + trace!("Error getting the device's min Period size: {e}"); ZERO_FRAMES } Ok(s) => s, @@ -312,11 +304,11 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> .find(|f| (min..=max).contains(f)) { Some(size) => { - trace!("Desired Frames per Period: {:?}", size); + trace!("Desired Frames per Period: {size:?}"); match hwp.set_period_size_near(size, ValueOr::Nearest) { Err(e) => { - trace!("Error setting the device's Period size: {}", e); + trace!("Error setting the device's Period size: {e}"); ZERO_FRAMES } Ok(s) => s, @@ -334,20 +326,14 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> }; if period_size == ZERO_FRAMES { - trace!("Buffer size: {:?}", buffer_size); + trace!("Buffer size: {buffer_size:?}"); trace!( - "Desired Period Frame range: {:?} (Buffer size / {:?}) - {:?} (Buffer size / {:?})", - min_period, - MIN_PERIOD_DIVISOR, - max_period, - MAX_PERIOD_DIVISOR, + "Desired Period Frame range: {min_period:?} (Buffer size / {MIN_PERIOD_DIVISOR:?}) - {max_period:?} (Buffer size / {MAX_PERIOD_DIVISOR:?})", ); trace!( - "Actual Period Frame range as reported by the device: {:?} - {:?}", - min, - max + "Actual Period Frame range as reported by the device: {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)?; - trace!("Actual Frames per Buffer: {:?}", frames_per_buffer); - trace!("Actual Frames per Period: {:?}", frames_per_period); + trace!("Actual Frames per Buffer: {frames_per_buffer:?}"); + trace!("Actual Frames per Period: {frames_per_period:?}"); // Let ALSA do the math for us. 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)) } @@ -401,7 +387,7 @@ impl Open for AlsaSink { exit(0); } Err(e) => { - error!("{}", e); + error!("{e}"); exit(1); } }, @@ -410,7 +396,7 @@ impl Open for AlsaSink { } .to_string(); - info!("Using AlsaSink with format: {:?}", format); + info!("Using AlsaSink with format: {format:?}"); Self { pcm: None, @@ -500,10 +486,7 @@ impl AlsaSink { Err(e) => { // Capture and log the original error as a warning, and then try to recover. // If recovery fails then forward that error back to player. - warn!( - "Error writing from AlsaSink buffer to PCM, trying to recover, {}", - e - ); + warn!("Error writing from AlsaSink buffer to PCM, trying to recover, {e}"); pcm.try_recover(e, false).map_err(AlsaError::OnWrite) } diff --git a/playback/src/audio_backend/pipe.rs b/playback/src/audio_backend/pipe.rs index e680256d..59690e25 100644 --- a/playback/src/audio_backend/pipe.rs +++ b/playback/src/audio_backend/pipe.rs @@ -48,7 +48,7 @@ impl Open for StdoutSink { exit(0); } - info!("Using StdoutSink (pipe) with format: {:?}", format); + info!("Using StdoutSink (pipe) with format: {format:?}"); Self { output: None, diff --git a/playback/src/audio_backend/subprocess.rs b/playback/src/audio_backend/subprocess.rs index 6ce545da..0c2cd116 100644 --- a/playback/src/audio_backend/subprocess.rs +++ b/playback/src/audio_backend/subprocess.rs @@ -72,7 +72,7 @@ impl Open for SubprocessSink { exit(0); } - info!("Using SubprocessSink with format: {:?}", format); + info!("Using SubprocessSink with format: {format:?}"); Self { shell_command, diff --git a/playback/src/mixer/mappings.rs b/playback/src/mixer/mappings.rs index bcb3fa45..bb740997 100644 --- a/playback/src/mixer/mappings.rs +++ b/playback/src/mixer/mappings.rs @@ -33,10 +33,7 @@ impl MappedCtrl for VolumeCtrl { } } else { // Ensure not to return -inf or NaN due to division by zero. - error!( - "{:?} does not work with 0 dB range, using linear mapping instead", - self - ); + error!("{self:?} does not work with 0 dB range, using linear mapping instead"); normalized_volume }; @@ -67,10 +64,7 @@ impl MappedCtrl for VolumeCtrl { } } else { // Ensure not to return -inf or NaN due to division by zero. - error!( - "{:?} does not work with 0 dB range, using linear mapping instead", - self - ); + error!("{self:?} does not work with 0 dB range, using linear mapping instead"); mapped_volume }; @@ -88,10 +82,10 @@ impl MappedCtrl for VolumeCtrl { fn set_db_range(&mut self, new_db_range: f64) { match self { 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 { diff --git a/playback/src/mixer/softmixer.rs b/playback/src/mixer/softmixer.rs index bf5e26ed..c8946a9b 100644 --- a/playback/src/mixer/softmixer.rs +++ b/playback/src/mixer/softmixer.rs @@ -17,7 +17,7 @@ pub struct SoftMixer { impl Mixer for SoftMixer { fn open(config: MixerConfig) -> Result { 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 { volume: Arc::new(AtomicU64::new(f64::to_bits(0.5))), diff --git a/playback/src/player.rs b/playback/src/player.rs index 8e0281e6..13790449 100644 --- a/playback/src/player.rs +++ b/playback/src/player.rs @@ -324,8 +324,7 @@ impl NormalisationData { let newpos = file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET))?; if newpos != SPOTIFY_NORMALIZATION_HEADER_START_OFFSET { error!( - "NormalisationData::parse_from_file seeking to {} but position is now {}", - SPOTIFY_NORMALIZATION_HEADER_START_OFFSET, newpos + "NormalisationData::parse_from_file seeking to {SPOTIFY_NORMALIZATION_HEADER_START_OFFSET} but position is now {newpos}" ); 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(); warn!( - "This track may exceed dBFS by {:.2} dB and be subject to {:.2} dB of dynamic limiting at its peak.", - factor_db, limiting_db + "This track may exceed dBFS by {factor_db:.2} dB and be subject to {limiting_db:.2} dB of dynamic limiting at its peak." ); } else if factor > threshold_ratio { let limiting_db = gain_db @@ -405,15 +403,14 @@ impl NormalisationData { + config.normalisation_threshold_dbfs.abs(); info!( - "This track may be subject to {:.2} dB of dynamic limiting at its peak.", - limiting_db + "This track may be subject to {limiting_db:.2} dB of dynamic limiting at its peak." ); } factor }; - debug!("Normalisation Data: {:?}", data); + debug!("Normalisation Data: {data:?}"); debug!( "Calculated Normalisation Factor for {:?}: {:.2}%", config.normalisation_type, @@ -464,7 +461,7 @@ impl Player { let handle = thread::spawn(move || { 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); @@ -517,7 +514,7 @@ impl Player { fn command(&self, cmd: PlayerCommand) { if let Some(commands) = self.commands.as_ref() { 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; if let Some(handle) = self.thread_handle.take() { if let Err(e) = handle.join() { - error!("Player thread Error: {:?}", e); + error!("Player thread Error: {e:?}"); } } } @@ -787,10 +784,7 @@ impl PlayerState { }; } _ => { - error!( - "Called playing_to_end_of_track in non-playing state: {:?}", - new_state - ); + error!("Called playing_to_end_of_track in non-playing state: {new_state:?}"); exit(1); } } @@ -832,10 +826,7 @@ impl PlayerState { }; } _ => { - error!( - "PlayerState::paused_to_playing in invalid state: {:?}", - new_state - ); + error!("PlayerState::paused_to_playing in invalid state: {new_state:?}"); exit(1); } } @@ -876,10 +867,7 @@ impl PlayerState { }; } _ => { - error!( - "PlayerState::playing_to_paused in invalid state: {:?}", - new_state - ); + error!("PlayerState::playing_to_paused in invalid state: {new_state:?}"); exit(1); } } @@ -894,7 +882,7 @@ struct PlayerTrackLoader { impl PlayerTrackLoader { async fn find_available_alternative(&self, audio_item: AudioItem) -> Option { if let Err(e) = audio_item.availability { - error!("Track is unavailable: {}", e); + error!("Track is unavailable: {e}"); None } else if !audio_item.files.is_empty() { Some(audio_item) @@ -958,7 +946,7 @@ impl PlayerTrackLoader { } }, Err(e) => { - error!("Unable to load audio item: {:?}", e); + error!("Unable to load audio item: {e:?}"); return None; } }; @@ -1026,7 +1014,7 @@ impl PlayerTrackLoader { let encrypted_file = match encrypted_file.await { Ok(encrypted_file) => encrypted_file, Err(e) => { - error!("Unable to load encrypted file: {:?}", e); + error!("Unable to load encrypted file: {e:?}"); return None; } }; @@ -1041,7 +1029,7 @@ impl PlayerTrackLoader { let key = match self.session.audio_key().request(spotify_id, file_id).await { Ok(key) => Some(key), Err(e) => { - warn!("Unable to load key, continuing without decryption: {}", e); + warn!("Unable to load key, continuing without decryption: {e}"); None } }; @@ -1064,7 +1052,7 @@ impl PlayerTrackLoader { ) { Ok(audio_file) => audio_file, Err(e) => { - error!("PlayerTrackLoader::load_track error opening subfile: {}", e); + error!("PlayerTrackLoader::load_track error opening subfile: {e}"); return None; } }; @@ -1098,10 +1086,7 @@ impl PlayerTrackLoader { let mut decoder = match decoder_type { Ok(decoder) => decoder, Err(e) if is_cached => { - warn!( - "Unable to read cached audio file: {}. Trying to download it.", - e - ); + warn!("Unable to read cached audio file: {e}. Trying to download it."); match self.session.cache() { Some(cache) => { @@ -1120,7 +1105,7 @@ impl PlayerTrackLoader { continue; } Err(e) => { - error!("Unable to read audio file: {}", e); + error!("Unable to read audio file: {e}"); return None; } }; @@ -1130,7 +1115,7 @@ impl PlayerTrackLoader { // If the position is invalid just start from // the beginning of the track. 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 } else { position_ms @@ -1144,8 +1129,7 @@ impl PlayerTrackLoader { Ok(new_position_ms) => new_position_ms, Err(e) => { error!( - "PlayerTrackLoader::load_track error seeking to starting position {}: {}", - position_ms, e + "PlayerTrackLoader::load_track error seeking to starting position {position_ms}: {e}" ); return None; } @@ -1195,7 +1179,7 @@ impl Future for PlayerInternal { if let Some(cmd) = 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)) => { error!( - "Skipping to next track, unable to load track <{:?}>: {:?}", - track_id, e + "Skipping to next track, unable to load track <{track_id:?}>: {e:?}" ); self.send_event(PlayerEvent::Unavailable { track_id, @@ -1253,7 +1236,7 @@ impl Future for PlayerInternal { }; } Poll::Ready(Err(_)) => { - debug!("Unable to preload {:?}", track_id); + debug!("Unable to preload {track_id:?}"); self.preload = PlayerPreload::None; // Let Spirc know that the track was unavailable. if let PlayerState::Playing { @@ -1368,7 +1351,7 @@ impl Future for PlayerInternal { } } 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 { track_id, play_request_id, @@ -1381,7 +1364,7 @@ impl Future for PlayerInternal { self.handle_packet(result, normalisation_factor); } 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 { track_id, play_request_id, @@ -1443,7 +1426,7 @@ impl PlayerInternal { match self.sink.start() { Ok(()) => self.sink_status = SinkStatus::Running, Err(e) => { - error!("{}", e); + error!("{e}"); self.handle_pause(); } } @@ -1466,7 +1449,7 @@ impl PlayerInternal { } } Err(e) => { - error!("{}", e); + error!("{e}"); exit(1); } } @@ -1694,7 +1677,7 @@ impl PlayerInternal { } if let Err(e) = self.sink.write(packet, &mut self.converter) { - error!("{}", e); + error!("{e}"); 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 { error!("Player::seek called from invalid state: {:?}", self.state); @@ -2107,7 +2090,7 @@ impl PlayerInternal { } fn handle_command(&mut self, cmd: PlayerCommand) -> PlayerResult { - debug!("command={:?}", cmd); + debug!("command={cmd:?}"); match cmd { PlayerCommand::Load { track_id, diff --git a/src/main.rs b/src/main.rs index bfe158f7..0c6c3956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,18 +5,18 @@ use librespot::playback::mixer::alsamixer::AlsaMixer; use librespot::{ connect::{ConnectConfig, Spirc}, core::{ - authentication::Credentials, cache::Cache, config::DeviceType, version, Session, - SessionConfig, + Session, SessionConfig, authentication::Credentials, cache::Cache, config::DeviceType, + version, }, discovery::DnsSdServiceBuilder, playback::{ - audio_backend::{self, SinkBuilder, BACKENDS}, + audio_backend::{self, BACKENDS, SinkBuilder}, config::{ AudioFormat, Bitrate, NormalisationMethod, NormalisationType, PlayerConfig, VolumeCtrl, }, dither, mixer::{self, MixerConfig, MixerFn}, - player::{coefficient_to_duration, duration_to_coefficient, Player}, + player::{Player, coefficient_to_duration, duration_to_coefficient}, }, }; use librespot_oauth::OAuthClientBuilder; @@ -24,6 +24,7 @@ use log::{debug, error, info, trace, warn}; use sha1::{Digest, Sha1}; use std::{ env, + ffi::OsStr, fs::create_dir_all, ops::RangeInclusive, path::{Path, PathBuf}, @@ -34,10 +35,11 @@ use std::{ }; use sysinfo::{ProcessesToUpdate, System}; use thiserror::Error; +use tokio::sync::Semaphore; use url::Url; 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 { HEXLOWER.encode(&Sha1::digest(name.as_bytes())) @@ -75,7 +77,9 @@ fn setup_logging(quiet: bool, verbose: bool) { builder.init(); 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, } -fn get_setup() -> Setup { +async fn get_setup() -> Setup { const VALID_INITIAL_VOLUME_RANGE: RangeInclusive = 0..=100; const VALID_VOLUME_RANGE: RangeInclusive = 0.0..=100.0; const VALID_NORMALISATION_KNEE_RANGE: RangeInclusive = 0.0..=10.0; @@ -810,7 +814,9 @@ fn get_setup() -> Setup { ALSA_MIXER_CONTROL, ] { 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; } } @@ -1196,7 +1202,9 @@ fn get_setup() -> Setup { empty_string_error_msg(USERNAME, USERNAME_SHORT); } 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); } match cached_creds { @@ -1265,9 +1273,7 @@ fn get_setup() -> Setup { if let Some(reason) = no_discovery_reason.as_deref() { if opt_present(ZEROCONF_PORT) { - warn!( - "With {reason} `--{ZEROCONF_PORT}` / `-{ZEROCONF_PORT_SHORT}` has no effect." - ); + warn!("With {reason} `--{ZEROCONF_PORT}` / `-{ZEROCONF_PORT_SHORT}` has no effect."); } } @@ -1393,31 +1399,31 @@ fn get_setup() -> Setup { 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() { - 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() { - 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() { - 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() { - 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() { - 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() { - 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, V: AsRef>(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")] async fn main() { const RUST_BACKTRACE: &str = "RUST_BACKTRACE"; @@ -1847,10 +1870,10 @@ async fn main() { const RECONNECT_RATE_LIMIT: usize = 5; 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 spirc: Option = None; diff --git a/src/player_event_handler.rs b/src/player_event_handler.rs index c4814572..36695c99 100644 --- a/src/player_event_handler.rs +++ b/src/player_event_handler.rs @@ -14,240 +14,263 @@ pub struct EventHandler { impl EventHandler { pub fn new(mut player_events: PlayerEventChannel, onevent: &str) -> Self { let on_event = onevent.to_string(); - let thread_handle = Some(thread::spawn(move || loop { - match player_events.blocking_recv() { - None => break, - Some(event) => { - let mut env_vars = HashMap::new(); + let thread_handle = Some(thread::spawn(move || { + loop { + match player_events.blocking_recv() { + None => break, + Some(event) => { + let mut env_vars = HashMap::new(); - match event { - PlayerEvent::PlayRequestIdChanged { play_request_id } => { - env_vars.insert("PLAYER_EVENT", "play_request_id_changed".to_string()); - env_vars.insert("PLAY_REQUEST_ID", play_request_id.to_string()); - } - PlayerEvent::TrackChanged { audio_item } => { - match audio_item.track_id.to_base62() { - Err(e) => { - warn!("PlayerEvent::TrackChanged: Invalid track id: {e}") - } - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "track_changed".to_string()); - env_vars.insert("TRACK_ID", id); - env_vars.insert("URI", audio_item.uri); - env_vars.insert("NAME", audio_item.name); - env_vars.insert( - "COVERS", - audio_item - .covers - .into_iter() - .map(|c| c.url) - .collect::>() - .join("\n"), - ); - env_vars.insert("LANGUAGE", audio_item.language.join("\n")); - env_vars - .insert("DURATION_MS", audio_item.duration_ms.to_string()); - env_vars - .insert("IS_EXPLICIT", audio_item.is_explicit.to_string()); + match event { + PlayerEvent::PlayRequestIdChanged { play_request_id } => { + env_vars + .insert("PLAYER_EVENT", "play_request_id_changed".to_string()); + env_vars.insert("PLAY_REQUEST_ID", play_request_id.to_string()); + } + PlayerEvent::TrackChanged { audio_item } => { + match audio_item.track_id.to_base62() { + Err(e) => { + warn!("PlayerEvent::TrackChanged: Invalid track id: {e}") + } + Ok(id) => { + env_vars + .insert("PLAYER_EVENT", "track_changed".to_string()); + env_vars.insert("TRACK_ID", id); + env_vars.insert("URI", audio_item.uri); + env_vars.insert("NAME", audio_item.name); + env_vars.insert( + "COVERS", + audio_item + .covers + .into_iter() + .map(|c| c.url) + .collect::>() + .join("\n"), + ); + env_vars.insert("LANGUAGE", audio_item.language.join("\n")); + env_vars.insert( + "DURATION_MS", + audio_item.duration_ms.to_string(), + ); + env_vars.insert( + "IS_EXPLICIT", + audio_item.is_explicit.to_string(), + ); - match audio_item.unique_fields { - UniqueFields::Track { - artists, - album, - album_artists, - popularity, - number, - disc_number, - } => { - env_vars.insert("ITEM_TYPE", "Track".to_string()); - env_vars.insert( - "ARTISTS", - artists - .0 - .into_iter() - .map(|a| a.name) - .collect::>() - .join("\n"), - ); - env_vars - .insert("ALBUM_ARTISTS", album_artists.join("\n")); - env_vars.insert("ALBUM", album); - env_vars.insert("POPULARITY", popularity.to_string()); - env_vars.insert("NUMBER", number.to_string()); - env_vars.insert("DISC_NUMBER", disc_number.to_string()); - } - UniqueFields::Episode { - description, - publish_time, - show_name, - } => { - env_vars.insert("ITEM_TYPE", "Episode".to_string()); - env_vars.insert("DESCRIPTION", description); - env_vars.insert( - "PUBLISH_TIME", - publish_time.unix_timestamp().to_string(), - ); - env_vars.insert("SHOW_NAME", show_name); + match audio_item.unique_fields { + UniqueFields::Track { + artists, + album, + album_artists, + popularity, + number, + disc_number, + } => { + env_vars.insert("ITEM_TYPE", "Track".to_string()); + env_vars.insert( + "ARTISTS", + artists + .0 + .into_iter() + .map(|a| a.name) + .collect::>() + .join("\n"), + ); + env_vars.insert( + "ALBUM_ARTISTS", + album_artists.join("\n"), + ); + env_vars.insert("ALBUM", album); + env_vars + .insert("POPULARITY", popularity.to_string()); + env_vars.insert("NUMBER", number.to_string()); + env_vars + .insert("DISC_NUMBER", disc_number.to_string()); + } + UniqueFields::Episode { + description, + publish_time, + show_name, + } => { + env_vars.insert("ITEM_TYPE", "Episode".to_string()); + env_vars.insert("DESCRIPTION", description); + env_vars.insert( + "PUBLISH_TIME", + publish_time.unix_timestamp().to_string(), + ); + env_vars.insert("SHOW_NAME", show_name); + } } } } } - } - PlayerEvent::Stopped { track_id, .. } => match track_id.to_base62() { - Err(e) => warn!("PlayerEvent::Stopped: Invalid track id: {e}"), - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "stopped".to_string()); - env_vars.insert("TRACK_ID", id); - } - }, - PlayerEvent::Playing { - track_id, - position_ms, - .. - } => match track_id.to_base62() { - Err(e) => warn!("PlayerEvent::Playing: Invalid track id: {e}"), - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "playing".to_string()); - env_vars.insert("TRACK_ID", id); - env_vars.insert("POSITION_MS", position_ms.to_string()); - } - }, - PlayerEvent::Paused { - track_id, - position_ms, - .. - } => match track_id.to_base62() { - Err(e) => warn!("PlayerEvent::Paused: Invalid track id: {e}"), - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "paused".to_string()); - env_vars.insert("TRACK_ID", id); - env_vars.insert("POSITION_MS", position_ms.to_string()); - } - }, - PlayerEvent::Loading { track_id, .. } => match track_id.to_base62() { - Err(e) => warn!("PlayerEvent::Loading: Invalid track id: {e}"), - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "loading".to_string()); - env_vars.insert("TRACK_ID", id); - } - }, - PlayerEvent::Preloading { track_id, .. } => match track_id.to_base62() { - Err(e) => warn!("PlayerEvent::Preloading: Invalid track id: {e}"), - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "preloading".to_string()); - env_vars.insert("TRACK_ID", id); - } - }, - PlayerEvent::TimeToPreloadNextTrack { track_id, .. } => { - match track_id.to_base62() { - Err(e) => warn!( - "PlayerEvent::TimeToPreloadNextTrack: Invalid track id: {e}" - ), + PlayerEvent::Stopped { track_id, .. } => match track_id.to_base62() { + Err(e) => warn!("PlayerEvent::Stopped: Invalid track id: {e}"), Ok(id) => { - env_vars.insert("PLAYER_EVENT", "preload_next".to_string()); + env_vars.insert("PLAYER_EVENT", "stopped".to_string()); env_vars.insert("TRACK_ID", id); } + }, + PlayerEvent::Playing { + track_id, + position_ms, + .. + } => match track_id.to_base62() { + Err(e) => warn!("PlayerEvent::Playing: Invalid track id: {e}"), + Ok(id) => { + env_vars.insert("PLAYER_EVENT", "playing".to_string()); + env_vars.insert("TRACK_ID", id); + env_vars.insert("POSITION_MS", position_ms.to_string()); + } + }, + PlayerEvent::Paused { + track_id, + position_ms, + .. + } => match track_id.to_base62() { + Err(e) => warn!("PlayerEvent::Paused: Invalid track id: {e}"), + Ok(id) => { + env_vars.insert("PLAYER_EVENT", "paused".to_string()); + env_vars.insert("TRACK_ID", id); + env_vars.insert("POSITION_MS", position_ms.to_string()); + } + }, + PlayerEvent::Loading { track_id, .. } => match track_id.to_base62() { + Err(e) => warn!("PlayerEvent::Loading: Invalid track id: {e}"), + Ok(id) => { + env_vars.insert("PLAYER_EVENT", "loading".to_string()); + env_vars.insert("TRACK_ID", id); + } + }, + PlayerEvent::Preloading { track_id, .. } => { + match track_id.to_base62() { + Err(e) => { + warn!("PlayerEvent::Preloading: Invalid track id: {e}") + } + Ok(id) => { + env_vars.insert("PLAYER_EVENT", "preloading".to_string()); + env_vars.insert("TRACK_ID", id); + } + } } - } - PlayerEvent::EndOfTrack { track_id, .. } => match track_id.to_base62() { - Err(e) => warn!("PlayerEvent::EndOfTrack: Invalid track id: {e}"), - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "end_of_track".to_string()); - env_vars.insert("TRACK_ID", id); + PlayerEvent::TimeToPreloadNextTrack { track_id, .. } => { + match track_id.to_base62() { + Err(e) => warn!( + "PlayerEvent::TimeToPreloadNextTrack: Invalid track id: {e}" + ), + Ok(id) => { + env_vars.insert("PLAYER_EVENT", "preload_next".to_string()); + env_vars.insert("TRACK_ID", id); + } + } } - }, - PlayerEvent::Unavailable { track_id, .. } => match track_id.to_base62() { - Err(e) => warn!("PlayerEvent::Unavailable: Invalid track id: {e}"), - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "unavailable".to_string()); - env_vars.insert("TRACK_ID", id); + PlayerEvent::EndOfTrack { track_id, .. } => { + match track_id.to_base62() { + Err(e) => { + warn!("PlayerEvent::EndOfTrack: Invalid track id: {e}") + } + Ok(id) => { + env_vars.insert("PLAYER_EVENT", "end_of_track".to_string()); + env_vars.insert("TRACK_ID", id); + } + } } - }, - PlayerEvent::VolumeChanged { volume } => { - env_vars.insert("PLAYER_EVENT", "volume_changed".to_string()); - env_vars.insert("VOLUME", volume.to_string()); - } - PlayerEvent::Seeked { - track_id, - position_ms, - .. - } => match track_id.to_base62() { - Err(e) => warn!("PlayerEvent::Seeked: Invalid track id: {e}"), - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "seeked".to_string()); - env_vars.insert("TRACK_ID", id); - env_vars.insert("POSITION_MS", position_ms.to_string()); + PlayerEvent::Unavailable { track_id, .. } => match track_id.to_base62() + { + Err(e) => warn!("PlayerEvent::Unavailable: Invalid track id: {e}"), + Ok(id) => { + env_vars.insert("PLAYER_EVENT", "unavailable".to_string()); + env_vars.insert("TRACK_ID", id); + } + }, + PlayerEvent::VolumeChanged { volume } => { + env_vars.insert("PLAYER_EVENT", "volume_changed".to_string()); + env_vars.insert("VOLUME", volume.to_string()); } - }, - PlayerEvent::PositionCorrection { - track_id, - position_ms, - .. - } => match track_id.to_base62() { - Err(e) => { - warn!("PlayerEvent::PositionCorrection: Invalid track id: {e}") + PlayerEvent::Seeked { + track_id, + position_ms, + .. + } => match track_id.to_base62() { + Err(e) => warn!("PlayerEvent::Seeked: Invalid track id: {e}"), + Ok(id) => { + env_vars.insert("PLAYER_EVENT", "seeked".to_string()); + env_vars.insert("TRACK_ID", id); + env_vars.insert("POSITION_MS", position_ms.to_string()); + } + }, + PlayerEvent::PositionCorrection { + track_id, + position_ms, + .. + } => match track_id.to_base62() { + Err(e) => { + warn!("PlayerEvent::PositionCorrection: Invalid track id: {e}") + } + Ok(id) => { + env_vars + .insert("PLAYER_EVENT", "position_correction".to_string()); + env_vars.insert("TRACK_ID", id); + env_vars.insert("POSITION_MS", position_ms.to_string()); + } + }, + PlayerEvent::SessionConnected { + connection_id, + user_name, + } => { + env_vars.insert("PLAYER_EVENT", "session_connected".to_string()); + env_vars.insert("CONNECTION_ID", connection_id); + env_vars.insert("USER_NAME", user_name); } - Ok(id) => { - env_vars.insert("PLAYER_EVENT", "position_correction".to_string()); - env_vars.insert("TRACK_ID", id); - env_vars.insert("POSITION_MS", position_ms.to_string()); + PlayerEvent::SessionDisconnected { + connection_id, + user_name, + } => { + env_vars.insert("PLAYER_EVENT", "session_disconnected".to_string()); + env_vars.insert("CONNECTION_ID", connection_id); + env_vars.insert("USER_NAME", user_name); } - }, - PlayerEvent::SessionConnected { - connection_id, - user_name, - } => { - env_vars.insert("PLAYER_EVENT", "session_connected".to_string()); - env_vars.insert("CONNECTION_ID", connection_id); - env_vars.insert("USER_NAME", user_name); - } - PlayerEvent::SessionDisconnected { - connection_id, - user_name, - } => { - env_vars.insert("PLAYER_EVENT", "session_disconnected".to_string()); - env_vars.insert("CONNECTION_ID", connection_id); - env_vars.insert("USER_NAME", user_name); - } - PlayerEvent::SessionClientChanged { - client_id, - client_name, - client_brand_name, - client_model_name, - } => { - env_vars.insert("PLAYER_EVENT", "session_client_changed".to_string()); - env_vars.insert("CLIENT_ID", client_id); - env_vars.insert("CLIENT_NAME", client_name); - env_vars.insert("CLIENT_BRAND_NAME", client_brand_name); - env_vars.insert("CLIENT_MODEL_NAME", client_model_name); - } - PlayerEvent::ShuffleChanged { shuffle } => { - env_vars.insert("PLAYER_EVENT", "shuffle_changed".to_string()); - env_vars.insert("SHUFFLE", shuffle.to_string()); - } - PlayerEvent::RepeatChanged { context, track } => { - env_vars.insert("PLAYER_EVENT", "repeat_changed".to_string()); - env_vars.insert("REPEAT", context.to_string()); - env_vars.insert("REPEAT_TRACK", track.to_string()); - } - PlayerEvent::AutoPlayChanged { auto_play } => { - env_vars.insert("PLAYER_EVENT", "auto_play_changed".to_string()); - env_vars.insert("AUTO_PLAY", auto_play.to_string()); + PlayerEvent::SessionClientChanged { + client_id, + client_name, + client_brand_name, + client_model_name, + } => { + env_vars + .insert("PLAYER_EVENT", "session_client_changed".to_string()); + env_vars.insert("CLIENT_ID", client_id); + env_vars.insert("CLIENT_NAME", client_name); + env_vars.insert("CLIENT_BRAND_NAME", client_brand_name); + env_vars.insert("CLIENT_MODEL_NAME", client_model_name); + } + PlayerEvent::ShuffleChanged { shuffle } => { + env_vars.insert("PLAYER_EVENT", "shuffle_changed".to_string()); + env_vars.insert("SHUFFLE", shuffle.to_string()); + } + PlayerEvent::RepeatChanged { context, track } => { + env_vars.insert("PLAYER_EVENT", "repeat_changed".to_string()); + env_vars.insert("REPEAT", context.to_string()); + env_vars.insert("REPEAT_TRACK", track.to_string()); + } + PlayerEvent::AutoPlayChanged { auto_play } => { + env_vars.insert("PLAYER_EVENT", "auto_play_changed".to_string()); + env_vars.insert("AUTO_PLAY", auto_play.to_string()); + } + + PlayerEvent::FilterExplicitContentChanged { filter } => { + env_vars.insert( + "PLAYER_EVENT", + "filter_explicit_content_changed".to_string(), + ); + env_vars.insert("FILTER", filter.to_string()); + } + // Ignore event irrelevant for standalone binary like PositionChanged + _ => {} } - PlayerEvent::FilterExplicitContentChanged { filter } => { - env_vars.insert( - "PLAYER_EVENT", - "filter_explicit_content_changed".to_string(), - ); - env_vars.insert("FILTER", filter.to_string()); + if !env_vars.is_empty() { + run_program(env_vars, &on_event); } - // Ignore event irrelevant for standalone binary like PositionChanged - _ => {} - } - - if !env_vars.is_empty() { - run_program(env_vars, &on_event); } } } @@ -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) { let mut v: Vec<&str> = onevent.split_whitespace().collect(); - debug!( - "Running {onevent} with environment variables:\n{env_vars:#?}" - ); + debug!("Running {onevent} with environment variables:\n{env_vars:#?}"); match Command::new(v.remove(0)) .args(&v)