Made albums own their contents

This commit is contained in:
G2-Games 2024-06-28 22:20:13 -05:00
parent 81ccab01f1
commit 47483127ed
3 changed files with 78 additions and 83 deletions

View file

@ -9,8 +9,7 @@ use kushi::traits::Location;
use kushi::{Queue, QueueItemType}; use kushi::{Queue, QueueItemType};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use std::thread::{sleep, spawn}; use std::thread::spawn;
use std::time::Duration;
use thiserror::Error; use thiserror::Error;
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
@ -24,8 +23,8 @@ use crate::{
config::Config, music_storage::library::MusicLibrary, config::Config, music_storage::library::MusicLibrary,
}; };
pub struct Controller<'a, P: Player + Send + Sync> { pub struct Controller<P: Player + Send + Sync> {
pub queue: Arc<RwLock<Queue<Song, Album<'a>, PlayerLocation>>>, pub queue: Arc<RwLock<Queue<Song, Album, PlayerLocation>>>,
pub config: Arc<RwLock<Config>>, pub config: Arc<RwLock<Config>>,
pub library: MusicLibrary, pub library: MusicLibrary,
pub player: Arc<Mutex<P>>, pub player: Arc<Mutex<P>>,
@ -86,7 +85,7 @@ impl<T: Send, U: Send> MailMan<T, U> {
} }
#[allow(unused_variables)] #[allow(unused_variables)]
impl<P: Player + Send + Sync + Sized + 'static> Controller<'static, P> { impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
pub fn start<T>(config_path: T) -> Result <Self, Box<dyn Error>> pub fn start<T>(config_path: T) -> Result <Self, Box<dyn Error>>
where where
std::path::PathBuf: std::convert::From<T>, std::path::PathBuf: std::convert::From<T>,

View file

@ -4,7 +4,7 @@ use super::utils::{find_images, normalize, read_file, write_file};
use crate::config::Config; use crate::config::Config;
// Various std things // Various std things
use std::collections::BTreeMap; use std::collections::{BTreeMap, HashMap};
use std::error::Error; use std::error::Error;
use std::ops::ControlFlow::{Break, Continue}; use std::ops::ControlFlow::{Break, Continue};
@ -320,7 +320,6 @@ impl Song {
} }
/// creates a `Vec<Song>` from a cue file /// creates a `Vec<Song>` from a cue file
pub fn from_cue(cuesheet: &Path) -> Result<Vec<(Self, PathBuf)>, Box<dyn Error>> { pub fn from_cue(cuesheet: &Path) -> Result<Vec<(Self, PathBuf)>, Box<dyn Error>> {
let mut tracks = Vec::new(); let mut tracks = Vec::new();
@ -574,43 +573,43 @@ pub enum Service {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Album<'a> { pub struct Album {
title: &'a String, title: String,
artist: Option<&'a String>, artist: Option<String>,
cover: Option<&'a AlbumArt>, cover: Option<AlbumArt>,
discs: BTreeMap<usize, Vec<&'a Song>>, discs: BTreeMap<u16, Vec<Uuid>>,
} }
#[allow(clippy::len_without_is_empty)] #[allow(clippy::len_without_is_empty)]
impl Album<'_> { impl Album {
//returns the Album title //returns the Album title
pub fn title(&self) -> &String { pub fn title(&self) -> &String {
self.title &self.title
} }
/// Returns the album cover as an AlbumArt struct, if it exists /// Returns the album cover as an AlbumArt struct, if it exists
fn cover(&self) -> Option<&AlbumArt> { fn cover(&self) -> &Option<AlbumArt> {
self.cover &self.cover
} }
/// Returns the Album Artist, if they exist /// Returns the Album Artist, if they exist
pub fn artist(&self) -> Option<&String> { pub fn artist(&self) -> &Option<String> {
self.artist &self.artist
} }
pub fn discs(&self) -> &BTreeMap<usize, Vec<&Song>> { pub fn discs(&self) -> &BTreeMap<u16, Vec<Uuid>> {
&self.discs &self.discs
} }
/// Returns the specified track at `index` from the album, returning /// Returns the specified track at `index` from the album, returning
/// an error if the track index is out of range /// an error if the track index is out of range
pub fn track(&self, disc: usize, index: usize) -> Option<&Song> { pub fn track(&self, disc: u16, index: usize) -> Option<&Uuid> {
Some(self.discs.get(&disc)?[index]) self.discs.get(&disc)?.get(index)
} }
fn tracks(&self) -> Vec<&Song> { fn tracks(&self) -> Vec<Uuid> {
let mut songs = Vec::new(); let mut songs = Vec::new();
for disc in &self.discs { for disc in self.discs.values() {
songs.append(&mut disc.1.clone()) songs.extend_from_slice(&disc)
} }
songs songs
} }
@ -618,15 +617,14 @@ impl Album<'_> {
/// Returns the number of songs in the album /// Returns the number of songs in the album
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
let mut total = 0; let mut total = 0;
for disc in &self.discs { for disc in self.discs.values() {
total += disc.1.len(); total += disc.len();
} }
total total
} }
} }
impl TrackGroup for Album<'_> {} impl TrackGroup for Album {}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct MusicLibrary { pub struct MusicLibrary {
@ -688,6 +686,17 @@ impl MusicLibrary {
Ok(library) Ok(library)
} }
/// Serializes the database out to the file specified in the config
pub fn save_path<P: ?Sized + AsRef<Path>>(&self, path: &P) -> Result<(), Box<dyn Error>> {
let path = path.as_ref();
match path.try_exists() {
Ok(_) => write_file(self, path)?,
Err(error) => return Err(error.into()),
}
Ok(())
}
/// Serializes the database out to the file specified in the config /// Serializes the database out to the file specified in the config
pub fn save(&self, config: Arc<RwLock<Config>>) -> Result<(), Box<dyn Error>> { pub fn save(&self, config: Arc<RwLock<Config>>) -> Result<(), Box<dyn Error>> {
let path = config.read().unwrap().libraries.get_library(&self.uuid)?.path.clone(); let path = config.read().unwrap().libraries.get_library(&self.uuid)?.path.clone();
@ -711,6 +720,7 @@ impl MusicLibrary {
/// Queries for a [Song] by its [URI], returning a single `Song` /// Queries for a [Song] by its [URI], returning a single `Song`
/// with the `URI` that matches along with its position in the library /// with the `URI` that matches along with its position in the library
#[inline(always)]
pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> { pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
let result = self let result = self
.library .library
@ -759,9 +769,10 @@ impl MusicLibrary {
self.library.par_iter().for_each(|track| { self.library.par_iter().for_each(|track| {
if path == track.primary_uri().unwrap().0.path() { if path == track.primary_uri().unwrap().0.path() {
//TODO: make this also not unwrap //TODO: make this also not unwrap
result.clone().lock().unwrap().push(track); Arc::clone(&result).lock().unwrap().push(track);
} }
}); });
if result.lock().unwrap().len() > 0 { if result.lock().unwrap().len() > 0 {
Some(Arc::try_unwrap(result).unwrap().into_inner().unwrap()) Some(Arc::try_unwrap(result).unwrap().into_inner().unwrap())
} else { } else {
@ -786,19 +797,11 @@ impl MusicLibrary {
continue; continue;
} }
/* TODO: figure out how to increase the speed of this maybe
// Check if the file path is already in the db // Check if the file path is already in the db
if self.query_uri(&URI::Local(path.to_path_buf())).is_some() { if self.query_uri(&URI::Local(path.to_path_buf())).is_some() {
continue; continue;
} }
// Save periodically while scanning
i += 1;
if i % 500 == 0 {
self.save(config).unwrap();
}
*/
let format = FileFormat::from_file(path)?; let format = FileFormat::from_file(path)?;
let extension = match path.extension() { let extension = match path.extension() {
Some(ext) => ext.to_string_lossy().to_ascii_lowercase(), Some(ext) => ext.to_string_lossy().to_ascii_lowercase(),
@ -834,6 +837,22 @@ impl MusicLibrary {
Ok(total) Ok(total)
} }
pub fn remove_missing(&mut self) {
let target_removals = Arc::new(Mutex::new(Vec::new()));
self.library.par_iter().for_each(|t|{
for location in &t.location {
if !location.exists().unwrap() {
Arc::clone(&target_removals).lock().unwrap().push(location.clone());
}
}
});
let target_removals = Arc::try_unwrap(target_removals).unwrap().into_inner().unwrap();
for location in target_removals {
self.remove_uri(&location).unwrap();
}
}
pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box<dyn Error>> { pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box<dyn Error>> {
let new_song = Song::from_file(target_file)?; let new_song = Song::from_file(target_file)?;
match self.add_song(new_song) { match self.add_song(new_song) {
@ -1038,65 +1057,42 @@ impl MusicLibrary {
/// Generates all albums from the track list /// Generates all albums from the track list
pub fn albums(&self) -> BTreeMap<String, Album> { pub fn albums(&self) -> BTreeMap<String, Album> {
let mut albums: BTreeMap<String, Album> = BTreeMap::new(); let mut albums: BTreeMap<String, Album> = BTreeMap::new();
for result in &self.library { for song in &self.library {
let title = match result.get_tag(&Tag::Album) { let album_title = match song.get_tag(&Tag::Album) {
Some(title) => title, Some(title) => title.clone(),
None => continue, None => continue,
}; };
let norm_title = normalize(title); //let norm_title = normalize(&album_title);
let disc_num = result let disc_num = song
.get_tag(&Tag::Disk) .get_tag(&Tag::Disk)
.unwrap_or(&"".to_string()) .unwrap_or(&"".to_string())
.parse::<usize>() .parse::<u16>()
.unwrap_or(1); .unwrap_or(1);
match albums.get_mut(&norm_title) { match albums.get_mut(&album_title) {
// If the album is in the list, add the track to the appropriate disc in it // If the album is in the list, add the track to the appropriate disc within the album
Some(album) => match album.discs.get_mut(&disc_num) { Some(album) => match album.discs.get_mut(&disc_num) {
Some(disc) => disc.push(result), Some(disc) => disc.push(song.uuid),
None => { None => {
album.discs.insert(disc_num, vec![result]); album.discs.insert(disc_num, vec![song.uuid]);
} }
}, },
// If the album is not in the list, make a new one and add it // If the album is not in the list, make it new one and add it
None => { None => {
let album_art = result.album_art.first(); let album_art = song.album_art.first();
let new_album = Album { let new_album = Album {
title, title: album_title.clone(),
artist: result.get_tag(&Tag::AlbumArtist), artist: song.get_tag(&Tag::AlbumArtist).cloned(),
discs: BTreeMap::from([(disc_num, vec![result])]), discs: BTreeMap::from([(disc_num, vec![song.uuid])]),
cover: album_art, cover: album_art.cloned(),
}; };
albums.insert(norm_title, new_album); albums.insert(album_title, new_album);
} }
} }
} }
// Sort the tracks in each disk in each album
let blank = String::from("");
albums.par_iter_mut().for_each(|album| {
for disc in &mut album.1.discs {
disc.1.par_sort_by(|a, b| {
let a_track = a.get_tag(&Tag::Track).unwrap_or(&blank);
let b_track = b.get_tag(&Tag::Track).unwrap_or(&blank);
if let (Ok(num_a), Ok(num_b)) = (a_track.parse::<i32>(), b_track.parse::<i32>())
{
// If parsing the track numbers succeeds, compare as numbers
num_a.cmp(&num_b)
} else {
// If parsing doesn't succeed, compare the locations
let path_a = PathBuf::from(a.get_field("location").unwrap().to_string());
let path_b = PathBuf::from(b.get_field("location").unwrap().to_string());
path_a.file_name().cmp(&path_b.file_name())
}
});
}
});
// Return the albums! // Return the albums!
albums albums
} }

View file

@ -37,13 +37,13 @@ pub(super) fn write_file<
writer_name.set_extension("tmp"); writer_name.set_extension("tmp");
// Create a new BufWriter on the file and a snap frame encoder // Create a new BufWriter on the file and a snap frame encoder
let writer = BufWriter::new(File::create(&writer_name)?); let mut writer = BufWriter::new(File::create(&writer_name)?);
let mut e = snap::write::FrameEncoder::new(writer); //let mut e = snap::write::FrameEncoder::new(writer);
// Write out the data // Write out the data
bincode::serde::encode_into_std_write( bincode::serde::encode_into_std_write(
library, library,
&mut e, &mut writer,
bincode::config::standard() bincode::config::standard()
.with_little_endian() .with_little_endian()
.with_variable_int_encoding(), .with_variable_int_encoding(),
@ -59,12 +59,12 @@ pub(super) fn read_file<T: for<'de> serde::Deserialize<'de>>(
path: PathBuf, path: PathBuf,
) -> Result<T, Box<dyn Error>> { ) -> Result<T, Box<dyn Error>> {
// Create a new snap reader over the file // Create a new snap reader over the file
let file_reader = BufReader::new(File::open(path)?); let mut file_reader = BufReader::new(File::open(path)?);
let mut d = snap::read::FrameDecoder::new(file_reader); //let mut d = snap::read::FrameDecoder::new(file_reader);
// Decode the library from the serialized data into the vec // Decode the library from the serialized data into the vec
let library: T = bincode::serde::decode_from_std_read( let library: T = bincode::serde::decode_from_std_read(
&mut d, &mut file_reader,
bincode::config::standard() bincode::config::standard()
.with_little_endian() .with_little_endian()
.with_variable_int_encoding(), .with_variable_int_encoding(),