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 + Clone, ) -> Result<(), Box> { println!("Track playlist daemon starting..."); let database = PgPool::connect( CONFIG .get() .unwrap() .get::("database.url")? .as_str(), ) .await .unwrap(); let mut ticker = interval(Duration::from_secs( CONFIG.get().unwrap().get::("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, playlist: TrackedPlaylist, ) -> Result<(), Box> { 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::>(); let normalized_b = b .chars() .filter(|c| c.is_alphanumeric()) .map(|c| c.to_ascii_lowercase()) .collect::>(); normalized_a == normalized_b }