use std::{ fmt::Display, io::{Read, Write}, path::Path, sync::{Arc, Mutex}, thread::JoinHandle, }; use crate::load::ToFromBytes; use super::{ database::Database, AlbumId, ArtistId, CoverId, DatabaseLocation, GeneralData, SongId, }; #[derive(Clone, Debug)] pub struct Song { pub id: SongId, pub location: DatabaseLocation, pub title: String, pub album: Option, pub artist: Option, pub more_artists: Vec, pub cover: Option, pub general: GeneralData, /// None => No cached data /// Some(Err) => No cached data yet, but a thread is working on loading it. /// Some(Ok(data)) => Cached data is available. pub cached_data: Arc>, JoinHandle>>>>>>>, } impl Song { pub fn new( location: DatabaseLocation, title: String, album: Option, artist: Option, more_artists: Vec, cover: Option, ) -> Self { Self { id: 0, location, title, album, artist, more_artists, cover, general: GeneralData::default(), cached_data: Arc::new(Mutex::new(None)), } } pub fn uncache_data(&self) -> Result<(), ()> { let mut cached = self.cached_data.lock().unwrap(); match cached.as_ref() { Some(Ok(_data)) => { *cached = None; Ok(()) } Some(Err(_thread)) => Err(()), None => Ok(()), } } /// If no data is cached yet and no caching thread is running, starts a thread to cache the data. pub fn cache_data_start_thread(&self, db: &Database) -> bool { let mut cd = self.cached_data.lock().unwrap(); let start_thread = match cd.as_ref() { None => true, Some(Err(_)) | Some(Ok(_)) => false, }; if start_thread { let path = db.get_path(&self.location); *cd = Some(Err(std::thread::spawn(move || { eprintln!("[info] thread started"); let data = Self::load_data(&path)?; eprintln!("[info] thread stopping after loading {path:?}"); Some(Arc::new(data)) }))); true } else { false } } /// Gets the cached data, if available. /// If a thread is running to load the data, it is not awaited. /// This function doesn't block. pub fn cached_data(&self) -> Option>> { if let Some(Ok(v)) = self.cached_data.lock().unwrap().as_ref() { Some(Arc::clone(v)) } else { None } } /// Gets the cached data, if available. /// If a thread is running to load the data, it *is* awaited. /// This function will block until the data is loaded. /// If it still returns none, some error must have occured. pub fn cached_data_now(&self, db: &Database) -> Option>> { let mut cd = self.cached_data.lock().unwrap(); *cd = match cd.take() { None => { if let Some(v) = Self::load_data(db.get_path(&self.location)) { Some(Ok(Arc::new(v))) } else { None } } Some(Err(t)) => match t.join() { Err(_e) => None, Ok(Some(v)) => Some(Ok(v)), Ok(None) => None, }, Some(Ok(v)) => Some(Ok(v)), }; drop(cd); self.cached_data() } fn load_data>(path: P) -> Option> { eprintln!("[info] loading song from {:?}", path.as_ref()); match std::fs::read(&path) { Ok(v) => { eprintln!("[info] loaded song from {:?}", path.as_ref()); Some(v) } Err(e) => { eprintln!("[info] error loading {:?}: {e:?}", path.as_ref()); None } } } } impl Display for Song { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.title)?; match (self.artist, self.album) { (Some(artist), Some(album)) => write!(f, " (by {artist} on {album})")?, (None, Some(album)) => write!(f, " (on {album})")?, (Some(artist), None) => write!(f, " (by {artist})")?, (None, None) => {} } Ok(()) } } impl ToFromBytes for Song { fn to_bytes(&self, s: &mut T) -> Result<(), std::io::Error> where T: Write, { self.id.to_bytes(s)?; self.location.to_bytes(s)?; self.title.to_bytes(s)?; self.album.to_bytes(s)?; self.artist.to_bytes(s)?; self.more_artists.to_bytes(s)?; self.cover.to_bytes(s)?; self.general.to_bytes(s)?; Ok(()) } fn from_bytes(s: &mut T) -> Result where T: Read, { Ok(Self { id: ToFromBytes::from_bytes(s)?, location: ToFromBytes::from_bytes(s)?, title: ToFromBytes::from_bytes(s)?, album: ToFromBytes::from_bytes(s)?, artist: ToFromBytes::from_bytes(s)?, more_artists: ToFromBytes::from_bytes(s)?, cover: ToFromBytes::from_bytes(s)?, general: ToFromBytes::from_bytes(s)?, cached_data: Arc::new(Mutex::new(None)), }) } }