use std::{ fmt::Display, io::{Read, Write}, mem::replace, path::PathBuf, sync::{Arc, Mutex}, thread::JoinHandle, time::Instant, }; use colorize::AnsiColor; use crate::load::ToFromBytes; use super::{ database::{ClientIo, Database}, AlbumId, ArtistId, CoverId, DatabaseLocation, GeneralData, SongId, }; #[derive(Clone, Debug)] pub struct Song { pub id: SongId, pub location: DatabaseLocation, pub file_last_modified_unix_timestamp: Option, pub title: String, pub album: Option, pub artist: ArtistId, pub more_artists: Vec, pub cover: Option, pub file_size: u64, /// song duration in milliseconds pub duration_millis: u64, 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: CachedData, } impl Song { pub fn new( location: DatabaseLocation, file_last_modified_unix_timestamp: Option, title: String, album: Option, artist: ArtistId, more_artists: Vec, cover: Option, file_size: u64, duration_millis: u64, general: GeneralData, ) -> Self { Self { id: 0, location, file_last_modified_unix_timestamp, title, album, artist, more_artists, cover, file_size, duration_millis, general, cached_data: CachedData(Arc::new(Mutex::new((Err(None), None)))), } } pub fn cached_data(&self) -> &CachedData { &self.cached_data } } impl CachedData { pub fn uncache_data(&self) -> Result { let mut cached = self.0.lock().unwrap(); match replace(&mut cached.0, Err(None)) { Ok(Ok(_data)) => Ok(true), Ok(Err(thread)) => { if thread.is_finished() { // get value from thread and drop it _ = thread.join(); Ok(true) } else { // thread is still running... cached.0 = Ok(Err(thread)); Err(()) } } Err(e) => { cached.0 = Err(e); Ok(false) } } } /// 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, song: &Song) -> bool { self.cache_data_start_thread_or_say_already_running(db, song) == Ok(true) } /// Ok(true) => thread started, /// Ok(false) => data was already loaded pub fn cache_data_start_thread_or_say_already_running( &self, db: &Database, song: &Song, ) -> Result { self.get_data_or_start_thread_and_say_already_running(db, |_| false, || true, song) } /// gets the data if available, or, if no thread is running, starts a thread to get the data. /// if a thread is running, was started, or recently encountered an error, `None` is returned, otherwise `Some(data)`. pub fn get_data_or_maybe_start_thread( &self, db: &Database, song: &Song, ) -> Option>> { self.get_data_or_start_thread_and_say_already_running( db, |data| Some(Arc::clone(data)), || None, song, ) .ok() .and_then(|v| v) } /// `Err(true)` if a thread is already running, /// `Ok(get_data(data))` if there is data, /// `Ok(started())` if a thread was started, /// `Err(false)` otherwise (i.e. loading data failed recently, 60 second cooldown between retries is active). pub fn get_data_or_start_thread_and_say_already_running( &self, db: &Database, get_data: impl FnOnce(&Arc>) -> T, started: impl FnOnce() -> T, song: &Song, ) -> Result { let mut cd = self.0.lock().unwrap(); match cd.0.as_mut() { Err(Some(i)) if i.elapsed().as_secs_f32() < 60.0 => return Err(false), Err(_) => (), Ok(Err(t)) => { if t.is_finished() { if let Some(bytes) = replace(&mut cd.0, Err(None)) .unwrap() .unwrap_err() .join() .unwrap() { cd.0 = Ok(Ok(bytes)); return Ok(get_data(cd.0.as_ref().unwrap().as_ref().unwrap())); } else { cd.0 = Err(Some(Instant::now())); return Err(false); } } else { return Err(true); } } Ok(Ok(bytes)) => return Ok(get_data(&*bytes)), }; let src = if let Some(dlcon) = &db.remote_server_as_song_file_source { Err((song.id, Arc::clone(dlcon))) } else { Ok(db.get_path(&song.location)) }; cd.0 = Ok(Err(std::thread::spawn(move || { let data = Self::load_data(src)?; Some(Arc::new(data)) }))); Ok(started()) } /// If the song's data is cached, returns the number of bytes. pub fn has_cached_data(&self) -> Option { if let Ok(Ok(v)) = self.0.lock().unwrap().0.as_ref() { Some(v.len()) } else { None } } /// 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 Ok(Ok(v)) = self.0.lock().unwrap().0.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 doesn't block. pub fn cached_data_await(&self) -> Option>> { let mut cd = self.0.lock().unwrap(); let (out, next) = match replace(&mut cd.0, Err(None)) { Ok(Ok(bytes)) => (Some(Arc::clone(&bytes)), Ok(Ok(bytes))), Ok(Err(t)) => { if let Some(bytes) = t.join().unwrap() { (Some(Arc::clone(&bytes)), Ok(Ok(bytes))) } else { (None, Err(Some(Instant::now()))) } } Err(e) => (None, Err(e)), }; cd.0 = next; out } fn load_data( src: Result< PathBuf, ( SongId, Arc>>>, ), >, ) -> Option> { match src { Ok(path) => { eprintln!("[{}] loading song from {:?}", "INFO".cyan(), path); match std::fs::read(&path) { Ok(v) => { eprintln!("[{}] loaded song from {:?}", "INFO".green(), path); Some(v) } Err(e) => { eprintln!("[{}] error loading {:?}: {e:?}", "ERR!".red(), path); None } } } Err((id, dlcon)) => { eprintln!("[{}] loading song {id}", "INFO".cyan()); match dlcon .lock() .unwrap() .song_file(id) .expect("problem with downloader connection...") { Ok(data) => Some(data), Err(e) => { eprintln!("[{}] error loading song {id}: {e}", "ERR!".red()); None } } } } } } impl Display for Song { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.title)?; match self.album { Some(album) => write!(f, " ({} by {} on {album})", self.id, self.artist)?, None => write!(f, " ({} by {})", self.id, self.artist)?, } 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.file_last_modified_unix_timestamp.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.file_size.to_bytes(s)?; self.duration_millis.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)?, file_last_modified_unix_timestamp: 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)?, file_size: ToFromBytes::from_bytes(s)?, duration_millis: ToFromBytes::from_bytes(s)?, general: ToFromBytes::from_bytes(s)?, cached_data: CachedData(Arc::new(Mutex::new((Err(None), None)))), }) } } #[derive(Debug)] pub struct CachedData( pub Arc< Mutex<( Result>, JoinHandle>>>>, Option>, Option, )>, >, ); impl Clone for CachedData { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } }