139 lines
4.0 KiB
Rust
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
|
|
}
|