mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 22:37:46 +01:00
175 lines
5.5 KiB
Rust
Executable File
175 lines
5.5 KiB
Rust
Executable File
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<AlbumId>,
|
|
pub artist: Option<ArtistId>,
|
|
pub more_artists: Vec<ArtistId>,
|
|
pub cover: Option<CoverId>,
|
|
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<Mutex<Option<Result<Arc<Vec<u8>>, JoinHandle<Option<Arc<Vec<u8>>>>>>>>,
|
|
}
|
|
impl Song {
|
|
pub fn new(
|
|
location: DatabaseLocation,
|
|
title: String,
|
|
album: Option<AlbumId>,
|
|
artist: Option<ArtistId>,
|
|
more_artists: Vec<ArtistId>,
|
|
cover: Option<CoverId>,
|
|
) -> 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<Arc<Vec<u8>>> {
|
|
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<Arc<Vec<u8>>> {
|
|
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<P: AsRef<Path>>(path: P) -> Option<Vec<u8>> {
|
|
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<T>(&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<T>(s: &mut T) -> Result<Self, std::io::Error>
|
|
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)),
|
|
})
|
|
}
|
|
}
|