tangled
alpha
login
or
join now
cherry.computer
/
website
My personal site
cherry.computer
htmx
tailwind
axum
askama
0
fork
atom
overview
issues
pulls
pipelines
refactor: fix pedantic Clippy lints
cherry.computer
1 year ago
45e23b96
5802e018
verified
This commit was signed with the committer's
known signature
.
cherry.computer
SSH Key Fingerprint:
SHA256:SIA77Ll0IpMb8Xd3RtaGT+PBIGIePhJJg5W2r6Td7cc=
+56
-52
4 changed files
expand all
collapse all
unified
split
server
src
index.rs
main.rs
scrobble.rs
scrobble_monitor.rs
+10
-8
server/src/index.rs
···
8
scrobble: Option<ScrobblesTemplate>,
9
}
10
11
-
pub async fn get_index(monitor: ScrobbleMonitor) -> RootTemplate {
12
-
let scrobbles_template = monitor.try_get_scrobble();
13
-
if scrobbles_template.is_none() {
14
-
// start fetching scrobble so we can send a fresh response to the client ASAP
15
-
tokio::spawn(async move { monitor.get_scrobble().await });
16
-
}
0
17
18
-
RootTemplate {
19
-
scrobble: scrobbles_template,
0
20
}
21
}
···
8
scrobble: Option<ScrobblesTemplate>,
9
}
10
11
+
impl RootTemplate {
12
+
pub fn new(monitor: ScrobbleMonitor) -> RootTemplate {
13
+
let scrobbles_template = monitor.try_get_scrobble();
14
+
if scrobbles_template.is_none() {
15
+
// start fetching scrobble so we can send a fresh response to the client ASAP
16
+
tokio::spawn(async move { monitor.get_scrobble().await });
17
+
}
18
19
+
RootTemplate {
20
+
scrobble: scrobbles_template,
21
+
}
22
}
23
}
+2
-2
server/src/main.rs
···
4
5
use std::{convert::Infallible, env, net::SocketAddr, time::Duration};
6
7
-
use crate::index::get_index;
8
use crate::scrobble_monitor::ScrobbleMonitor;
9
10
use askama::Template;
···
55
}
56
57
async fn render_index_handler(State(monitor): State<ScrobbleMonitor>) -> impl IntoResponse {
58
-
let template = get_index(monitor).await;
59
template.render().map(Html).map_err(|err| {
60
tracing::error!("failed to render index: {err:?}");
61
StatusCode::INTERNAL_SERVER_ERROR
···
4
5
use std::{convert::Infallible, env, net::SocketAddr, time::Duration};
6
7
+
use crate::index::RootTemplate;
8
use crate::scrobble_monitor::ScrobbleMonitor;
9
10
use askama::Template;
···
55
}
56
57
async fn render_index_handler(State(monitor): State<ScrobbleMonitor>) -> impl IntoResponse {
58
+
let template = RootTemplate::new(monitor);
59
template.render().map(Html).map_err(|err| {
60
tracing::error!("failed to render index: {err:?}");
61
StatusCode::INTERNAL_SERVER_ERROR
+42
-40
server/src/scrobble.rs
···
2
use serde::Deserialize;
3
4
#[derive(Debug, Clone, Deserialize)]
5
-
pub struct ScrobbleArtist {
6
#[serde(rename = "#text")]
7
pub text: String,
8
}
9
10
#[derive(Debug, Clone, Deserialize)]
11
-
pub struct ScrobbleImage {
12
#[serde(rename = "#text")]
13
pub text: String,
14
}
15
16
#[derive(Debug, Clone, Deserialize)]
17
-
pub struct ScrobbleAttributes {
18
#[serde(rename = "nowplaying")]
19
pub now_playing: String,
20
}
21
22
#[derive(Debug, Clone, Deserialize)]
23
-
pub struct ScrobbleTrack {
24
-
pub artist: ScrobbleArtist,
25
-
pub image: Vec<ScrobbleImage>,
26
pub name: String,
27
#[serde(rename = "@attr")]
28
-
pub attributes: Option<ScrobbleAttributes>,
29
}
30
31
#[derive(Debug, Clone, Deserialize)]
32
-
pub struct ScrobbleRecentTracks {
33
-
pub track: Vec<ScrobbleTrack>,
34
}
35
36
#[derive(Debug, Clone, Deserialize)]
37
pub struct Scrobble {
38
#[serde(rename = "recenttracks")]
39
-
pub recent_tracks: ScrobbleRecentTracks,
40
}
41
42
#[derive(Template, Debug, Clone, PartialEq)]
···
48
pub srcset: Option<String>,
49
}
50
51
-
pub fn scrobble_partial(scrobble: Scrobble) -> ScrobblesTemplate {
52
-
let latest_track = scrobble
53
-
.recent_tracks
54
-
.track
55
-
.into_iter()
56
-
.next()
57
-
.expect("no tracks were returned");
58
-
let srcset = latest_track.image.get(0..3).map(|images| {
59
-
format!(
60
-
"{}, {} 2x, {} 3x",
61
-
images[0].text, images[1].text, images[2].text
62
-
)
63
-
});
64
-
let text_intro = if latest_track
65
-
.attributes
66
-
.is_some_and(|attr| attr.now_playing == "true")
67
-
{
68
-
"Now playing: "
69
-
} else {
70
-
"Last played: "
71
-
};
72
-
let now_playing = format!("{} - {}", latest_track.name, latest_track.artist.text);
73
-
74
-
ScrobblesTemplate {
75
-
intro: text_intro,
76
-
now_playing,
77
-
image: latest_track
78
-
.image
79
.into_iter()
80
.next()
81
-
.map(|image| image.text),
82
-
srcset,
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
83
}
84
}
···
2
use serde::Deserialize;
3
4
#[derive(Debug, Clone, Deserialize)]
5
+
pub struct Artist {
6
#[serde(rename = "#text")]
7
pub text: String,
8
}
9
10
#[derive(Debug, Clone, Deserialize)]
11
+
pub struct Image {
12
#[serde(rename = "#text")]
13
pub text: String,
14
}
15
16
#[derive(Debug, Clone, Deserialize)]
17
+
pub struct Attributes {
18
#[serde(rename = "nowplaying")]
19
pub now_playing: String,
20
}
21
22
#[derive(Debug, Clone, Deserialize)]
23
+
pub struct Track {
24
+
pub artist: Artist,
25
+
pub image: Vec<Image>,
26
pub name: String,
27
#[serde(rename = "@attr")]
28
+
pub attributes: Option<Attributes>,
29
}
30
31
#[derive(Debug, Clone, Deserialize)]
32
+
pub struct RecentTracks {
33
+
pub track: Vec<Track>,
34
}
35
36
#[derive(Debug, Clone, Deserialize)]
37
pub struct Scrobble {
38
#[serde(rename = "recenttracks")]
39
+
pub recent_tracks: RecentTracks,
40
}
41
42
#[derive(Template, Debug, Clone, PartialEq)]
···
48
pub srcset: Option<String>,
49
}
50
51
+
impl ScrobblesTemplate {
52
+
pub fn new(scrobble: Scrobble) -> ScrobblesTemplate {
53
+
let latest_track = scrobble
54
+
.recent_tracks
55
+
.track
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
56
.into_iter()
57
.next()
58
+
.expect("no tracks were returned");
59
+
let srcset = latest_track.image.get(0..3).map(|images| {
60
+
format!(
61
+
"{}, {} 2x, {} 3x",
62
+
images[0].text, images[1].text, images[2].text
63
+
)
64
+
});
65
+
let text_intro = if latest_track
66
+
.attributes
67
+
.is_some_and(|attr| attr.now_playing == "true")
68
+
{
69
+
"Now playing: "
70
+
} else {
71
+
"Last played: "
72
+
};
73
+
let now_playing = format!("{} - {}", latest_track.name, latest_track.artist.text);
74
+
75
+
ScrobblesTemplate {
76
+
intro: text_intro,
77
+
now_playing,
78
+
image: latest_track
79
+
.image
80
+
.into_iter()
81
+
.next()
82
+
.map(|image| image.text),
83
+
srcset,
84
+
}
85
}
86
}
+2
-2
server/src/scrobble_monitor.rs
···
6
use reqwest::Client;
7
use tokio::sync::RwLock;
8
9
-
use crate::scrobble::{scrobble_partial, Scrobble, ScrobblesTemplate};
10
11
#[derive(Debug, Clone)]
12
struct CachedScrobble {
···
63
_ => {
64
tracing::debug!("fetching new scrobble data");
65
let scrobble = self.fetch_scrobble().await?;
66
-
let scrobble_partial = scrobble_partial(scrobble);
67
*last_scrobble = Some(CachedScrobble {
68
data: scrobble_partial.clone(),
69
fetch_time: Instant::now(),
···
6
use reqwest::Client;
7
use tokio::sync::RwLock;
8
9
+
use crate::scrobble::{Scrobble, ScrobblesTemplate};
10
11
#[derive(Debug, Clone)]
12
struct CachedScrobble {
···
63
_ => {
64
tracing::debug!("fetching new scrobble data");
65
let scrobble = self.fetch_scrobble().await?;
66
+
let scrobble_partial = ScrobblesTemplate::new(scrobble);
67
*last_scrobble = Some(CachedScrobble {
68
data: scrobble_partial.clone(),
69
fetch_time: Instant::now(),