My personal site cherry.computer
htmx tailwind axum askama

feat: handle if last.fm response is offline or malformed

cherry.computer e5591f39 6b51913e

verified
+40 -32
+10 -13
server/src/index.rs
··· 1 - use crate::scrobble_monitor::ScrobbleMonitor; 1 + use crate::{scrobble::ScrobblesTemplate, scrobble_monitor::ScrobbleMonitor}; 2 2 3 3 use askama::Template; 4 4 5 5 #[derive(Template, Debug, Clone)] 6 6 #[template(path = "index.html")] 7 7 pub struct RootTemplate { 8 - intro: String, 9 - now_playing: String, 10 - image: String, 11 - srcset: String, 8 + scrobble: Option<ScrobblesTemplate> 12 9 } 13 10 14 - pub async fn get_index(monitor: &mut ScrobbleMonitor) -> anyhow::Result<RootTemplate> { 15 - let scrobbles_template = monitor.get_scrobble().await?; 11 + pub async fn get_index(monitor: &mut ScrobbleMonitor) -> RootTemplate { 12 + let scrobbles_template = monitor.get_scrobble().await; 13 + if let Err(err) = scrobbles_template.as_ref() { 14 + tracing::warn!(?err, "Failed to get scrobble"); 15 + } 16 16 17 - Ok(RootTemplate { 18 - intro: scrobbles_template.intro, 19 - now_playing: scrobbles_template.now_playing, 20 - image: scrobbles_template.image, 21 - srcset: scrobbles_template.srcset, 22 - }) 17 + RootTemplate { 18 + scrobble: scrobbles_template.ok() 19 + } 23 20 }
+1 -4
server/src/main.rs
··· 52 52 async fn render_index_handler( 53 53 Extension(mut monitor): Extension<ScrobbleMonitor>, 54 54 ) -> impl IntoResponse { 55 - let template = get_index(&mut monitor).await.map_err(|err| { 56 - tracing::error!("failed to get data from last.fm: {err:?}"); 57 - StatusCode::BAD_GATEWAY 58 - })?; 55 + let template = get_index(&mut monitor).await; 59 56 template.render().map(Html).map_err(|err| { 60 57 tracing::error!("failed to render index: {err:?}"); 61 58 StatusCode::INTERNAL_SERVER_ERROR
+18 -13
server/src/scrobble.rs
··· 30 30 31 31 #[derive(Debug, Clone, Deserialize)] 32 32 pub struct ScrobbleRecentTracks { 33 - pub track: Vec<ScrobbleTrack>, 33 + pub track: (ScrobbleTrack,), 34 34 } 35 35 36 36 #[derive(Debug, Clone, Deserialize)] ··· 42 42 #[derive(Template, Debug, Clone)] 43 43 #[template(path = "index.html", block = "scrobbles")] 44 44 pub struct ScrobblesTemplate { 45 - pub intro: String, 45 + pub intro: &'static str, 46 46 pub now_playing: String, 47 - pub image: String, 48 - pub srcset: String, 47 + pub image: Option<String>, 48 + pub srcset: Option<String>, 49 49 } 50 50 51 - pub fn scrobble_partial(scrobble: &Scrobble) -> ScrobblesTemplate { 52 - let latest_track = &scrobble.recent_tracks.track[0]; 53 - let srcset = format!( 54 - "{}, {} 2x, {} 3x", 55 - latest_track.image[0].text, latest_track.image[1].text, latest_track.image[2].text 56 - ); 51 + pub fn scrobble_partial(scrobble: Scrobble) -> ScrobblesTemplate { 52 + let (latest_track,) = scrobble.recent_tracks.track; 53 + let srcset = latest_track.image.get(0..3).map(|images| { 54 + format!( 55 + "{}, {} 2x, {} 3x", 56 + images[0].text, images[1].text, images[2].text 57 + ) 58 + }); 57 59 let text_intro = if latest_track 58 60 .attributes 59 - .as_ref() 60 61 .map_or(false, |attr| attr.now_playing) 61 62 { 62 63 "Now playing: " ··· 66 67 let now_playing = format!("{} - {}", latest_track.name, latest_track.artist.text); 67 68 68 69 ScrobblesTemplate { 69 - intro: text_intro.to_owned(), 70 + intro: text_intro, 70 71 now_playing, 71 - image: latest_track.image[0].text.clone(), 72 + image: latest_track 73 + .image 74 + .into_iter() 75 + .next() 76 + .map(|image| image.text), 72 77 srcset, 73 78 } 74 79 }
+1 -1
server/src/scrobble_monitor.rs
··· 51 51 _ => { 52 52 tracing::debug!("fetching new scrobble data"); 53 53 let scrobble = self.fetch_scrobble().await?; 54 - let scrobble_partial = scrobble_partial(&scrobble); 54 + let scrobble_partial = scrobble_partial(scrobble); 55 55 *last_scrobble = Some(CachedScrobble { 56 56 data: scrobble_partial.clone(), 57 57 fetch_time: Instant::now(),
+10 -1
server/templates/index.html
··· 87 87 </div> 88 88 </div> 89 89 <div class="scrobble-bar" hx-get="/scrobbles" hx-trigger="every 30s"> 90 + {% match scrobble %} {% when Some with (ScrobblesTemplate {intro, now_playing, image, srcset}) %} 90 91 {% block scrobbles %} 91 92 <div class="bar-container"> 93 + {% match image %} {% when Some with (image) %} 92 94 <img 93 95 class="bar-cover" 94 96 src="{{ image }}" 95 97 alt="Cover art" 96 - srcset="{{ srcset }}" 98 + {% match srcset %} {% when Some with (srcset) %} 99 + srcset="{{ srcset }}" 100 + {% else %} 101 + {% endmatch %} 97 102 /> 103 + {% else %} 104 + {% endmatch %} 98 105 <p class="bar-text-intro">{{ intro }}</p> 99 106 <p class="bar-text-music">{{ now_playing }}</p> 100 107 </div> 101 108 {% endblock scrobbles %} 109 + {% else %} 110 + {% endmatch %} 102 111 </div> 103 112 </body> 104 113 </html>