use std::num::NonZeroUsize; use iced::{futures::stream, subscription, widget::image, Subscription}; use lru::LruCache; use once_cell::sync::Lazy; use parking_lot::Mutex; use reqwest::IntoUrl; use url::Url; use crate::config::FANART_PROJECT_KEY; #[derive(Debug)] pub enum MaybePendingImage { Downloaded(image::Handle), Loading(Url), } impl MaybePendingImage { pub fn handle(&self) -> Option { match self { Self::Downloaded(h) => Some(h.clone()), Self::Loading(_) => None, } } } pub fn download_image( url: Url, post_process: fn(::image::RgbaImage) -> ::image::RgbaImage, resp: impl FnOnce(image::Handle) -> M + Send + 'static, ) -> Subscription { subscription::run_with_id( url.to_string(), stream::once(async move { eprintln!("{url} dl"); (resp)(load_image(url.clone(), post_process).await) }), ) } pub async fn load_image( url: T, post_process: fn(::image::RgbaImage) -> ::image::RgbaImage, ) -> image::Handle { static CACHE: Lazy>> = Lazy::new(|| Mutex::new(LruCache::new(NonZeroUsize::new(50).unwrap()))); static CLIENT: Lazy = Lazy::new(reqwest::Client::new); let url = url.into_url().unwrap(); if let Some(handle) = CACHE.lock().get(&url) { return handle.clone(); } let bytes = CLIENT .get(url.clone()) .send() .await .unwrap() .bytes() .await .unwrap(); let handle = tokio::task::spawn_blocking(move || { eprintln!("parsing image"); let img = ::image::load_from_memory(&bytes).unwrap(); eprintln!("post processing"); let data = post_process(img.into_rgba8()); let (h, w) = data.dimensions(); image::Handle::from_pixels(h, w, data.into_raw()) }) .await .unwrap(); CACHE.lock().push(url.clone(), handle.clone()); handle } pub fn find_musicbrainz_artist( artist: String, to_msg: fn(String) -> M, ) -> Subscription { static CACHE: Lazy>> = Lazy::new(|| Mutex::new(LruCache::new(NonZeroUsize::new(10).unwrap()))); subscription::run_with_id( format!("musicbrainz-{artist}"), stream::once(async move { eprintln!("musicbrainz req"); if let Some(handle) = CACHE.lock().get(&artist) { return (to_msg)(handle.to_string()); } // TODO let client = reqwest::Client::builder() .user_agent(format!( "{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION") )) .build() .unwrap(); let resp: serde_json::Value = client .get(format!( "https://musicbrainz.org/ws/2/artist/?query={artist}&fmt=json", )) .send() .await .unwrap() .json() .await .unwrap(); let id = resp .get("artists") .unwrap() .get(0) .unwrap() .get("id") .unwrap() .as_str() .unwrap() .to_string(); CACHE.lock().push(artist, id.clone()); // TODO: typing (to_msg)(id) }), ) } pub fn find_fanart_urls( musicbrainz_id: String, to_msg: fn(Option, Option) -> M, ) -> Subscription { subscription::run_with_id( format!("fanart-{musicbrainz_id}"), stream::once(async move { eprintln!("fanart req"); let resp: serde_json::Value = reqwest::get(format!("http://webservice.fanart.tv/v3/music/{musicbrainz_id}?api_key={FANART_PROJECT_KEY}")) .await .unwrap() .json() .await .unwrap(); // TODO: typing let logo = resp .get("hdmusiclogo") .and_then(|v| v.get(0)) .and_then(|v| v.get("url")) .and_then(|v| v.as_str()) .map(Url::parse) .transpose() .unwrap(); let background = resp .get("artistbackground") .and_then(|v| v.get(0)) .and_then(|v| v.get("url")) .and_then(|v| v.as_str()) .map(Url::parse) .transpose() .unwrap(); (to_msg)(logo, background) }), ) }