From bc7e13d756a3b4c61eeb6053b56400fbbc33163d Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:38:21 -0600 Subject: [PATCH] Improve track notifications with artwork and quality info --- src-tauri/Cargo.toml | 2 +- src-tauri/src/commands/notification.rs | 116 +++++++++++++++++++++++-- src/lib/services/playbackService.ts | 19 +++- 3 files changed, 123 insertions(+), 14 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 818e9e015..dcafae42a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,7 +22,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" # HTTP Client -reqwest = { version = "0.12", features = ["json", "cookies", "rustls-tls", "stream"], default-features = false } +reqwest = { version = "0.12", features = ["json", "cookies", "rustls-tls", "stream", "blocking"], default-features = false } # Async Runtime tokio = { version = "1", features = ["full"] } diff --git a/src-tauri/src/commands/notification.rs b/src-tauri/src/commands/notification.rs index f94df8803..c9dbf9809 100644 --- a/src-tauri/src/commands/notification.rs +++ b/src-tauri/src/commands/notification.rs @@ -1,28 +1,126 @@ //! System notification commands use notify_rust::Notification; +use md5::{Digest, Md5}; +use std::path::PathBuf; +use std::fs; +use std::io::Write; -/// Show a track change notification +/// Cache directory for notification artwork +fn get_artwork_cache_dir() -> Result { + let cache_dir = dirs::cache_dir() + .ok_or_else(|| "Could not find cache directory".to_string())? + .join("qbz") + .join("artwork"); + + fs::create_dir_all(&cache_dir) + .map_err(|e| format!("Failed to create artwork cache dir: {}", e))?; + + Ok(cache_dir) +} + +/// Download artwork to cache and return the path +fn cache_artwork(url: &str) -> Result { + // Create a hash of the URL for the filename + let mut hasher = Md5::new(); + hasher.update(url.as_bytes()); + let hash = format!("{:x}", hasher.finalize()); + let cache_dir = get_artwork_cache_dir()?; + let cache_path = cache_dir.join(format!("{}.jpg", hash)); + + // Return cached file if exists + if cache_path.exists() { + return Ok(cache_path); + } + + // Download the image + let response = reqwest::blocking::Client::new() + .get(url) + .timeout(std::time::Duration::from_secs(5)) + .send() + .map_err(|e| format!("Failed to download artwork: {}", e))?; + + if !response.status().is_success() { + return Err(format!("Failed to download artwork: HTTP {}", response.status())); + } + + let bytes = response.bytes() + .map_err(|e| format!("Failed to read artwork bytes: {}", e))?; + + // Write to cache + let mut file = fs::File::create(&cache_path) + .map_err(|e| format!("Failed to create artwork cache file: {}", e))?; + file.write_all(&bytes) + .map_err(|e| format!("Failed to write artwork cache: {}", e))?; + + Ok(cache_path) +} + +/// Format quality info for notification +fn format_quality(bit_depth: Option, sample_rate: Option) -> String { + match (bit_depth, sample_rate) { + (Some(bits), Some(rate)) if bits >= 24 || rate > 48 => { + format!("Hi-Res • {}-bit/{}kHz", bits, rate) + } + (Some(bits), Some(rate)) => { + format!("CD Quality • {}-bit/{}kHz", bits, rate) + } + _ => String::new() + } +} + +/// Show a track change notification with artwork #[tauri::command] pub fn show_track_notification( title: String, artist: String, album: String, - _artwork_url: Option, + artwork_url: Option, + bit_depth: Option, + sample_rate: Option, ) -> Result<(), String> { log::info!("Command: show_track_notification - {} by {}", title, artist); - let body = if album.is_empty() { - artist.clone() - } else { - format!("{} • {}", artist, album) - }; + // Build body with artist, album, and quality + let mut body_parts = Vec::new(); - Notification::new() + if !artist.is_empty() { + body_parts.push(artist.clone()); + } + if !album.is_empty() { + body_parts.push(album.clone()); + } + + let quality = format_quality(bit_depth, sample_rate); + if !quality.is_empty() { + body_parts.push(quality); + } + + let body = body_parts.join(" • "); + + let mut notification = Notification::new(); + notification .summary(&title) .body(&body) .appname("QBZ") - .timeout(3000) // 3 seconds + .timeout(4000); // 4 seconds + + // Try to add artwork + if let Some(url) = artwork_url { + match cache_artwork(&url) { + Ok(path) => { + if let Some(path_str) = path.to_str() { + notification.image_path(path_str); + log::debug!("Notification artwork set: {}", path_str); + } + } + Err(e) => { + log::warn!("Could not cache artwork for notification: {}", e); + } + } + } + + notification .show() .map_err(|e| format!("Failed to show notification: {}", e))?; diff --git a/src/lib/services/playbackService.ts b/src/lib/services/playbackService.ts index b2a430b4c..c23eaa4d8 100644 --- a/src/lib/services/playbackService.ts +++ b/src/lib/services/playbackService.ts @@ -98,8 +98,15 @@ export async function playTrack( coverUrl: track.artwork || null }); - // Show system notification - await showTrackNotification(track.title, track.artist, track.album, track.artwork); + // Show system notification with artwork and quality info + await showTrackNotification( + track.title, + track.artist, + track.album, + track.artwork, + track.bitDepth, + track.samplingRate + ); // Update Last.fm await updateLastfmNowPlaying(track.title, track.artist, track.album, track.duration, track.id); @@ -152,14 +159,18 @@ export async function showTrackNotification( title: string, artist: string, album: string, - artworkUrl?: string + artworkUrl?: string, + bitDepth?: number, + sampleRate?: number ): Promise { try { await invoke('show_track_notification', { title, artist, album, - artworkUrl: artworkUrl || null + artworkUrl: artworkUrl || null, + bitDepth: bitDepth || null, + sampleRate: sampleRate || null }); } catch (err) { console.error('Failed to show track notification:', err);