playlistd/src/daemon/update_playlists.rs
jude 2bd75c1c06 Add playlist building daemon
This creates playlists when the name would change
2024-04-18 22:33:45 +01:00

139 lines
4.0 KiB
Rust

use crate::listenbrainz::StatsRange;
use crate::models::TrackedPlaylist;
use crate::subsonic::Subsonic;
use crate::{listenbrainz, CONFIG};
use log::error;
use sqlx::postgres::PgPool;
use std::time::Duration;
use thiserror::Error;
use tokio::time::{interval, MissedTickBehavior};
#[derive(Debug, Error)]
pub enum UpdatePlaylistError {
#[error("Bad range specified")]
BadRange,
#[error("Operation requires a playlist ID")]
NoPlaylistId,
}
pub async fn update_playlists_daemon(
media_client: impl AsRef<Subsonic> + Clone,
) -> Result<(), Box<dyn std::error::Error>> {
println!("Track playlist daemon starting...");
let database = PgPool::connect(
CONFIG
.get()
.unwrap()
.get::<String>("database.url")?
.as_str(),
)
.await
.unwrap();
let mut ticker = interval(Duration::from_secs(
CONFIG.get().unwrap().get::<u64>("daemon.interval")?,
));
ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
loop {
let records_res = sqlx::query_as!(TrackedPlaylist, "SELECT * FROM \"tracked_playlist\"")
.fetch_all(&database)
.await;
match records_res {
Ok(records) => {
for record in records {
// todo handle me
update_playlist(media_client.clone(), record).await.unwrap();
}
}
Err(e) => {
println!("Could not fetch tracked_playlists: {:?}", e);
}
}
ticker.tick().await;
}
}
async fn update_playlist(
media_client: impl AsRef<Subsonic>,
playlist: TrackedPlaylist,
) -> Result<(), Box<dyn std::error::Error>> {
match playlist.playlist_id {
Some(playlist_id) => {
let range = StatsRange::from_param(&playlist.tracking_type)
.ok_or(UpdatePlaylistError::BadRange)?;
let recordings =
listenbrainz::recordings(playlist.tracking_user, range, playlist.playlist_size)
.await?
.recordings;
let mut tracks = vec![];
for recording in recordings {
let search_results = media_client
.as_ref()
.song_search(format!(
"{} {}",
recording.track_name, recording.artists[0].artist_credit_name
))
.await?;
let filtered = search_results
.iter()
.find(|s| {
alpha_compare(&s.title, &recording.track_name)
&& alpha_compare(&s.album, &recording.release_name)
})
.cloned();
match filtered {
Some(track) => {
tracks.push(track.id);
}
None => {
error!(
"Couldn't find matching track for {} - {}",
recording.track_name, recording.artist_name
)
}
}
}
let playlist = media_client
.as_ref()
.get_playlist(playlist_id.to_string())
.await?;
media_client
.as_ref()
.update_playlist(
playlist_id.to_string(),
tracks,
(0..playlist.song_count).collect(),
)
.await?;
Ok(())
}
None => Err(Box::new(UpdatePlaylistError::NoPlaylistId)),
}
}
fn alpha_compare(a: &str, b: &str) -> bool {
let normalized_a = a
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect::<Vec<char>>();
let normalized_b = b
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect::<Vec<char>>();
normalized_a == normalized_b
}