This commit is contained in:
Mark
2023-08-13 23:58:53 +02:00
commit 922e4fcc00
43 changed files with 6822 additions and 0 deletions

43
musicdb-lib/src/data/album.rs Executable file
View File

@@ -0,0 +1,43 @@
use std::io::{Read, Write};
use crate::load::ToFromBytes;
use super::{AlbumId, ArtistId, CoverId, GeneralData, SongId};
#[derive(Clone, Debug)]
pub struct Album {
pub id: AlbumId,
pub name: String,
pub artist: Option<ArtistId>,
pub cover: Option<CoverId>,
pub songs: Vec<SongId>,
pub general: GeneralData,
}
impl ToFromBytes for Album {
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
where
T: Write,
{
self.id.to_bytes(s)?;
self.name.to_bytes(s)?;
self.artist.to_bytes(s)?;
self.songs.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)?,
name: ToFromBytes::from_bytes(s)?,
artist: ToFromBytes::from_bytes(s)?,
songs: ToFromBytes::from_bytes(s)?,
cover: ToFromBytes::from_bytes(s)?,
general: ToFromBytes::from_bytes(s)?,
})
}
}

43
musicdb-lib/src/data/artist.rs Executable file
View File

@@ -0,0 +1,43 @@
use std::io::{Read, Write};
use crate::load::ToFromBytes;
use super::{AlbumId, ArtistId, CoverId, GeneralData, SongId};
#[derive(Clone, Debug)]
pub struct Artist {
pub id: ArtistId,
pub name: String,
pub cover: Option<CoverId>,
pub albums: Vec<AlbumId>,
pub singles: Vec<SongId>,
pub general: GeneralData,
}
impl ToFromBytes for Artist {
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
where
T: Write,
{
self.id.to_bytes(s)?;
self.name.to_bytes(s)?;
self.albums.to_bytes(s)?;
self.singles.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)?,
name: ToFromBytes::from_bytes(s)?,
albums: ToFromBytes::from_bytes(s)?,
singles: ToFromBytes::from_bytes(s)?,
cover: ToFromBytes::from_bytes(s)?,
general: ToFromBytes::from_bytes(s)?,
})
}
}

377
musicdb-lib/src/data/database.rs Executable file
View File

@@ -0,0 +1,377 @@
use std::{
collections::HashMap,
fs::{self, File},
io::{BufReader, Write},
path::PathBuf,
sync::{mpsc, Arc},
time::Instant,
};
use crate::{load::ToFromBytes, server::Command};
use super::{
album::Album,
artist::Artist,
queue::{Queue, QueueContent},
song::Song,
AlbumId, ArtistId, CoverId, DatabaseLocation, SongId,
};
pub struct Database {
db_file: PathBuf,
pub lib_directory: PathBuf,
artists: HashMap<ArtistId, Artist>,
albums: HashMap<AlbumId, Album>,
songs: HashMap<SongId, Song>,
covers: HashMap<CoverId, DatabaseLocation>,
// TODO! make sure this works out for the server AND clients
// cover_cache: HashMap<CoverId, Vec<u8>>,
db_data_file_change_first: Option<Instant>,
db_data_file_change_last: Option<Instant>,
pub queue: Queue,
pub update_endpoints: Vec<UpdateEndpoint>,
pub playing: bool,
pub command_sender: Option<mpsc::Sender<Command>>,
}
pub enum UpdateEndpoint {
Bytes(Box<dyn Write + Sync + Send>),
CmdChannel(mpsc::Sender<Arc<Command>>),
CmdChannelTokio(tokio::sync::mpsc::UnboundedSender<Arc<Command>>),
Custom(Box<dyn FnMut(&Command) + Send>),
}
impl Database {
fn panic(&self, msg: &str) -> ! {
// custom panic handler
// make a backup
// exit
panic!("DatabasePanic: {msg}");
}
pub fn get_path(&self, location: &DatabaseLocation) -> PathBuf {
self.lib_directory.join(&location.rel_path)
}
pub fn get_song(&self, song: &SongId) -> Option<&Song> {
self.songs.get(song)
}
pub fn get_song_mut(&mut self, song: &SongId) -> Option<&mut Song> {
self.songs.get_mut(song)
}
/// adds a song to the database.
/// ignores song.id and just assigns a new id, which it then returns.
/// this function also adds a reference to the new song to the album (or artist.singles, if no album)
pub fn add_song_new(&mut self, song: Song) -> SongId {
let album = song.album.clone();
let artist = song.artist.clone();
let id = self.add_song_new_nomagic(song);
if let Some(Some(album)) = album.map(|v| self.albums.get_mut(&v)) {
album.songs.push(id);
} else {
if let Some(Some(artist)) = artist.map(|v| self.artists.get_mut(&v)) {
artist.singles.push(id);
}
}
id
}
pub fn add_song_new_nomagic(&mut self, mut song: Song) -> SongId {
for key in 0.. {
if !self.songs.contains_key(&key) {
song.id = key;
self.songs.insert(key, song);
return key;
}
}
self.panic("database.songs all keys used - no more capacity for new songs!");
}
/// adds an artist to the database.
/// ignores artist.id and just assigns a new id, which it then returns.
/// this function does nothing special.
pub fn add_artist_new(&mut self, artist: Artist) -> ArtistId {
let id = self.add_artist_new_nomagic(artist);
id
}
fn add_artist_new_nomagic(&mut self, mut artist: Artist) -> ArtistId {
for key in 0.. {
if !self.artists.contains_key(&key) {
artist.id = key;
self.artists.insert(key, artist);
return key;
}
}
self.panic("database.artists all keys used - no more capacity for new artists!");
}
/// adds an album to the database.
/// ignores album.id and just assigns a new id, which it then returns.
/// this function also adds a reference to the new album to the artist
pub fn add_album_new(&mut self, album: Album) -> AlbumId {
let artist = album.artist.clone();
let id = self.add_album_new_nomagic(album);
if let Some(Some(artist)) = artist.map(|v| self.artists.get_mut(&v)) {
artist.albums.push(id);
}
id
}
fn add_album_new_nomagic(&mut self, mut album: Album) -> AlbumId {
for key in 0.. {
if !self.albums.contains_key(&key) {
album.id = key;
self.albums.insert(key, album);
return key;
}
}
self.panic("database.artists all keys used - no more capacity for new artists!");
}
/// updates an existing song in the database with the new value.
/// uses song.id to find the correct song.
/// if the id doesn't exist in the db, Err(()) is returned.
/// Otherwise Some(old_data) is returned.
pub fn update_song(&mut self, song: Song) -> Result<Song, ()> {
if let Some(prev_song) = self.songs.get_mut(&song.id) {
Ok(std::mem::replace(prev_song, song))
} else {
Err(())
}
}
pub fn update_album(&mut self, album: Album) -> Result<Album, ()> {
if let Some(prev_album) = self.albums.get_mut(&album.id) {
Ok(std::mem::replace(prev_album, album))
} else {
Err(())
}
}
pub fn update_artist(&mut self, artist: Artist) -> Result<Artist, ()> {
if let Some(prev_artist) = self.artists.get_mut(&artist.id) {
Ok(std::mem::replace(prev_artist, artist))
} else {
Err(())
}
}
/// [NOT RECOMMENDED - use add_song_new or update_song instead!] inserts the song into the database.
/// uses song.id. If another song with that ID exists, it is replaced and Some(other_song) is returned.
/// If no other song exists, the song will be added to the database with the given ID and None is returned.
pub fn update_or_add_song(&mut self, song: Song) -> Option<Song> {
self.songs.insert(song.id, song)
}
pub fn init_connection<T: Write>(&self, con: &mut T) -> Result<(), std::io::Error> {
// TODO! this is slow because it clones everything - there has to be a better way...
Command::SyncDatabase(
self.artists().iter().map(|v| v.1.clone()).collect(),
self.albums().iter().map(|v| v.1.clone()).collect(),
self.songs().iter().map(|v| v.1.clone()).collect(),
)
.to_bytes(con)?;
Command::QueueUpdate(vec![], self.queue.clone()).to_bytes(con)?;
if self.playing {
Command::Resume.to_bytes(con)?;
}
// since this is so easy to check for, it comes last.
// this allows clients to find out when init_connection is done.
Command::SetLibraryDirectory(self.lib_directory.clone()).to_bytes(con)?;
// is initialized now - client can receive updates after this point.
// NOTE: Don't write to connection anymore - the db will dispatch updates on its own.
// we just need to handle commands (receive from the connection).
Ok(())
}
pub fn apply_command(&mut self, command: Command) {
// since db.update_endpoints is empty for clients, this won't cause unwanted back and forth
self.broadcast_update(&command);
match command {
Command::Resume => self.playing = true,
Command::Pause => self.playing = false,
Command::Stop => self.playing = false,
Command::NextSong => {
self.queue.advance_index();
}
Command::Save => {
if let Err(e) = self.save_database(None) {
eprintln!("Couldn't save: {e}");
}
}
Command::SyncDatabase(a, b, c) => self.sync(a, b, c),
Command::QueueUpdate(index, new_data) => {
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
*v = new_data;
}
}
Command::QueueAdd(mut index, new_data) => {
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
v.add_to_end(new_data);
}
}
Command::QueueInsert(mut index, pos, new_data) => {
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
v.insert(new_data, pos);
}
}
Command::QueueRemove(index) => {
self.queue.remove_by_index(&index, 0);
}
Command::QueueGoto(index) => self.queue.set_index(&index, 0),
Command::AddSong(song) => {
self.add_song_new(song);
}
Command::AddAlbum(album) => {
self.add_album_new(album);
}
Command::AddArtist(artist) => {
self.add_artist_new(artist);
}
Command::ModifySong(song) => {
_ = self.update_song(song);
}
Command::ModifyAlbum(album) => {
_ = self.update_album(album);
}
Command::ModifyArtist(artist) => {
_ = self.update_artist(artist);
}
Command::SetLibraryDirectory(new_dir) => {
self.lib_directory = new_dir;
}
}
}
}
// file saving/loading
impl Database {
/// Database is also used for clients, to keep things consistent.
/// A client database doesn't need any storage paths and won't perform autosaves.
pub fn new_clientside() -> Self {
Self {
db_file: PathBuf::new(),
lib_directory: PathBuf::new(),
artists: HashMap::new(),
albums: HashMap::new(),
songs: HashMap::new(),
covers: HashMap::new(),
db_data_file_change_first: None,
db_data_file_change_last: None,
queue: QueueContent::Folder(0, vec![], String::new()).into(),
update_endpoints: vec![],
playing: false,
command_sender: None,
}
}
pub fn new_empty(path: PathBuf, lib_dir: PathBuf) -> Self {
Self {
db_file: path,
lib_directory: lib_dir,
artists: HashMap::new(),
albums: HashMap::new(),
songs: HashMap::new(),
covers: HashMap::new(),
db_data_file_change_first: None,
db_data_file_change_last: None,
queue: QueueContent::Folder(0, vec![], String::new()).into(),
update_endpoints: vec![],
playing: false,
command_sender: None,
}
}
pub fn load_database(path: PathBuf) -> Result<Self, std::io::Error> {
let mut file = BufReader::new(File::open(&path)?);
eprintln!("[info] loading library from {file:?}");
let lib_directory = ToFromBytes::from_bytes(&mut file)?;
eprintln!("[info] library directory is {lib_directory:?}");
Ok(Self {
db_file: path,
lib_directory,
artists: ToFromBytes::from_bytes(&mut file)?,
albums: ToFromBytes::from_bytes(&mut file)?,
songs: ToFromBytes::from_bytes(&mut file)?,
covers: ToFromBytes::from_bytes(&mut file)?,
db_data_file_change_first: None,
db_data_file_change_last: None,
queue: QueueContent::Folder(0, vec![], String::new()).into(),
update_endpoints: vec![],
playing: false,
command_sender: None,
})
}
pub fn save_database(&self, path: Option<PathBuf>) -> Result<PathBuf, std::io::Error> {
let path = if let Some(p) = path {
p
} else {
self.db_file.clone()
};
// if no path is set (client mode), do nothing
if path.as_os_str().is_empty() {
return Ok(path);
}
eprintln!("[info] saving db to {path:?}.");
let mut file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(&path)?;
self.lib_directory.to_bytes(&mut file)?;
self.artists.to_bytes(&mut file)?;
self.albums.to_bytes(&mut file)?;
self.songs.to_bytes(&mut file)?;
self.covers.to_bytes(&mut file)?;
Ok(path)
}
pub fn broadcast_update(&mut self, update: &Command) {
let mut remove = vec![];
let mut bytes = None;
let mut arc = None;
for (i, udep) in self.update_endpoints.iter_mut().enumerate() {
match udep {
UpdateEndpoint::Bytes(writer) => {
if bytes.is_none() {
bytes = Some(update.to_bytes_vec());
}
if writer.write_all(bytes.as_ref().unwrap()).is_err() {
remove.push(i);
}
}
UpdateEndpoint::CmdChannel(sender) => {
if arc.is_none() {
arc = Some(Arc::new(update.clone()));
}
if sender.send(arc.clone().unwrap()).is_err() {
remove.push(i);
}
}
UpdateEndpoint::CmdChannelTokio(sender) => {
if arc.is_none() {
arc = Some(Arc::new(update.clone()));
}
if sender.send(arc.clone().unwrap()).is_err() {
remove.push(i);
}
}
UpdateEndpoint::Custom(func) => func(update),
}
}
if !remove.is_empty() {
eprintln!(
"[info] closing {} connections, {} are still active",
remove.len(),
self.update_endpoints.len() - remove.len()
);
for i in remove.into_iter().rev() {
self.update_endpoints.remove(i);
}
}
}
pub fn sync(&mut self, artists: Vec<Artist>, albums: Vec<Album>, songs: Vec<Song>) {
self.artists = artists.iter().map(|v| (v.id, v.clone())).collect();
self.albums = albums.iter().map(|v| (v.id, v.clone())).collect();
self.songs = songs.iter().map(|v| (v.id, v.clone())).collect();
}
}
impl Database {
pub fn songs(&self) -> &HashMap<SongId, Song> {
&self.songs
}
pub fn albums(&self) -> &HashMap<AlbumId, Album> {
&self.albums
}
pub fn artists(&self) -> &HashMap<ArtistId, Artist> {
&self.artists
}
}

73
musicdb-lib/src/data/mod.rs Executable file
View File

@@ -0,0 +1,73 @@
use std::{
io::{Read, Write},
path::PathBuf,
};
use crate::load::ToFromBytes;
pub mod album;
pub mod artist;
pub mod database;
pub mod queue;
pub mod song;
pub type SongId = u64;
pub type AlbumId = u64;
pub type ArtistId = u64;
pub type CoverId = u64;
#[derive(Clone, Default, Debug)]
pub struct GeneralData {
pub tags: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct DatabaseLocation {
pub rel_path: PathBuf,
}
impl ToFromBytes for DatabaseLocation {
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
where
T: Write,
{
self.rel_path.to_bytes(s)
}
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
where
T: Read,
{
Ok(Self {
rel_path: ToFromBytes::from_bytes(s)?,
})
}
}
impl<P> From<P> for DatabaseLocation
where
P: Into<PathBuf>,
{
fn from(value: P) -> Self {
Self {
rel_path: value.into(),
}
}
}
impl ToFromBytes for GeneralData {
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
where
T: Write,
{
self.tags.to_bytes(s)?;
Ok(())
}
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
where
T: Read,
{
Ok(Self {
tags: ToFromBytes::from_bytes(s)?,
})
}
}

286
musicdb-lib/src/data/queue.rs Executable file
View File

@@ -0,0 +1,286 @@
use crate::load::ToFromBytes;
use super::SongId;
#[derive(Clone, Debug)]
pub struct Queue {
enabled: bool,
content: QueueContent,
}
#[derive(Clone, Debug)]
pub enum QueueContent {
Song(SongId),
Folder(usize, Vec<Queue>, String),
}
impl Queue {
pub fn enabled(&self) -> bool {
self.enabled
}
pub fn content(&self) -> &QueueContent {
&self.content
}
pub fn add_to_end(&mut self, v: Self) -> bool {
match &mut self.content {
QueueContent::Song(_) => false,
QueueContent::Folder(_, vec, _) => {
vec.push(v);
true
}
}
}
pub fn insert(&mut self, v: Self, index: usize) -> bool {
match &mut self.content {
QueueContent::Song(_) => false,
QueueContent::Folder(_, vec, _) => {
if index <= vec.len() {
vec.insert(index, v);
true
} else {
false
}
}
}
}
pub fn len(&self) -> usize {
if !self.enabled {
return 0;
}
match &self.content {
QueueContent::Song(_) => 1,
QueueContent::Folder(_, v, _) => v.iter().map(|v| v.len()).sum(),
}
}
/// recursively descends the queue until the current active element is found, then returns it.
pub fn get_current(&self) -> Option<&Self> {
match &self.content {
QueueContent::Folder(i, v, _) => {
let i = *i;
if let Some(v) = v.get(i) {
v.get_current()
} else {
None
}
}
QueueContent::Song(_) => Some(self),
}
}
pub fn get_current_song(&self) -> Option<&SongId> {
if let QueueContent::Song(id) = self.get_current()?.content() {
Some(id)
} else {
None
}
}
pub fn get_next_song(&self) -> Option<&SongId> {
if let QueueContent::Song(id) = self.get_next()?.content() {
Some(id)
} else {
None
}
}
pub fn get_next(&self) -> Option<&Self> {
match &self.content {
QueueContent::Folder(i, vec, _) => {
let i = *i;
if let Some(v) = vec.get(i) {
if let Some(v) = v.get_next() {
Some(v)
} else {
if let Some(v) = vec.get(i + 1) {
v.get_current()
} else {
None
}
}
} else {
None
}
}
QueueContent::Song(_) => None,
}
}
pub fn advance_index(&mut self) -> bool {
match &mut self.content {
QueueContent::Song(_) => false,
QueueContent::Folder(index, contents, _) => {
if let Some(c) = contents.get_mut(*index) {
// inner value could advance index, do nothing.
if c.advance_index() {
true
} else {
loop {
if *index + 1 < contents.len() {
// can advance
*index += 1;
if contents[*index].enabled {
break true;
}
} else {
// can't advance: index would be out of bounds
*index = 0;
break false;
}
}
}
} else {
*index = 0;
false
}
}
}
}
pub fn set_index(&mut self, index: &Vec<usize>, depth: usize) {
let i = index.get(depth).map(|v| *v).unwrap_or(0);
match &mut self.content {
QueueContent::Song(_) => {}
QueueContent::Folder(idx, contents, _) => {
*idx = i;
for (i2, c) in contents.iter_mut().enumerate() {
if i2 != i {
c.set_index(&vec![], 0)
}
}
if let Some(c) = contents.get_mut(i) {
c.set_index(index, depth + 1);
}
}
}
}
pub fn get_item_at_index(&self, index: &Vec<usize>, depth: usize) -> Option<&Self> {
if let Some(i) = index.get(depth) {
match &self.content {
QueueContent::Song(_) => None,
QueueContent::Folder(_, v, _) => {
if let Some(v) = v.get(*i) {
v.get_item_at_index(index, depth + 1)
} else {
None
}
}
}
} else {
Some(self)
}
}
pub fn get_item_at_index_mut(&mut self, index: &Vec<usize>, depth: usize) -> Option<&mut Self> {
if let Some(i) = index.get(depth) {
match &mut self.content {
QueueContent::Song(_) => None,
QueueContent::Folder(_, v, _) => {
if let Some(v) = v.get_mut(*i) {
v.get_item_at_index_mut(index, depth + 1)
} else {
None
}
}
}
} else {
Some(self)
}
}
pub fn remove_by_index(&mut self, index: &Vec<usize>, depth: usize) -> Option<Self> {
if let Some(i) = index.get(depth) {
match &mut self.content {
QueueContent::Song(_) => None,
QueueContent::Folder(ci, v, _) => {
if depth + 1 < index.len() {
if let Some(v) = v.get_mut(*i) {
v.remove_by_index(index, depth + 1)
} else {
None
}
} else {
if *i < v.len() {
// if current playback is past this point,
// reduce the index by 1 so that it still points to the same element
if *ci > *i {
*ci -= 1;
}
Some(v.remove(*i))
} else {
None
}
}
}
}
} else {
None
}
}
}
impl From<QueueContent> for Queue {
fn from(value: QueueContent) -> Self {
Self {
enabled: true,
content: value,
}
}
}
impl ToFromBytes for Queue {
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
where
T: std::io::Write,
{
s.write_all(&[if self.enabled { 0b11111111 } else { 0b00000000 }])?;
self.content.to_bytes(s)?;
Ok(())
}
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
where
T: std::io::Read,
{
let mut enabled = [0];
s.read_exact(&mut enabled)?;
Ok(Self {
enabled: enabled[0].count_ones() >= 4,
content: ToFromBytes::from_bytes(s)?,
})
}
}
impl ToFromBytes for QueueContent {
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
where
T: std::io::Write,
{
match self {
Self::Song(id) => {
s.write_all(&[0b11111111])?;
id.to_bytes(s)?;
}
Self::Folder(index, contents, name) => {
s.write_all(&[0b00000000])?;
index.to_bytes(s)?;
contents.to_bytes(s)?;
name.to_bytes(s)?;
}
}
Ok(())
}
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
where
T: std::io::Read,
{
let mut switch_on = [0];
s.read_exact(&mut switch_on)?;
Ok(if switch_on[0].count_ones() > 4 {
Self::Song(ToFromBytes::from_bytes(s)?)
} else {
Self::Folder(
ToFromBytes::from_bytes(s)?,
ToFromBytes::from_bytes(s)?,
ToFromBytes::from_bytes(s)?,
)
})
}
}

166
musicdb-lib/src/data/song.rs Executable file
View File

@@ -0,0 +1,166 @@
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) {
*self.cached_data.lock().unwrap() = None;
}
/// 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)),
})
}
}