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 std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::thread::{sleep, spawn};
use std::time::Duration;
use std::thread::spawn;
use thiserror::Error;
use crossbeam_channel::unbounded;
@ -24,8 +23,8 @@ use crate::{
config::Config, music_storage::library::MusicLibrary,
};
pub struct Controller<'a, P: Player + Send + Sync> {
pub queue: Arc<RwLock<Queue<Song, Album<'a>, PlayerLocation>>>,
pub struct Controller<P: Player + Send + Sync> {
pub queue: Arc<RwLock<Queue<Song, Album, PlayerLocation>>>,
pub config: Arc<RwLock<Config>>,
pub library: MusicLibrary,
pub player: Arc<Mutex<P>>,
@ -86,7 +85,7 @@ impl<T: Send, U: Send> MailMan<T, U> {
}
#[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>>
where
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;
// Various std things
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use std::error::Error;
use std::ops::ControlFlow::{Break, Continue};
@ -320,7 +320,6 @@ impl Song {
}
/// creates a `Vec<Song>` from a cue file
pub fn from_cue(cuesheet: &Path) -> Result<Vec<(Self, PathBuf)>, Box<dyn Error>> {
let mut tracks = Vec::new();
@ -574,43 +573,43 @@ pub enum Service {
}
#[derive(Clone, Debug, PartialEq)]
pub struct Album<'a> {
title: &'a String,
artist: Option<&'a String>,
cover: Option<&'a AlbumArt>,
discs: BTreeMap<usize, Vec<&'a Song>>,
pub struct Album {
title: String,
artist: Option<String>,
cover: Option<AlbumArt>,
discs: BTreeMap<u16, Vec<Uuid>>,
}
#[allow(clippy::len_without_is_empty)]
impl Album<'_> {
impl Album {
//returns the Album title
pub fn title(&self) -> &String {
self.title
&self.title
}
/// Returns the album cover as an AlbumArt struct, if it exists
fn cover(&self) -> Option<&AlbumArt> {
self.cover
fn cover(&self) -> &Option<AlbumArt> {
&self.cover
}
/// Returns the Album Artist, if they exist
pub fn artist(&self) -> Option<&String> {
self.artist
pub fn artist(&self) -> &Option<String> {
&self.artist
}
pub fn discs(&self) -> &BTreeMap<usize, Vec<&Song>> {
pub fn discs(&self) -> &BTreeMap<u16, Vec<Uuid>> {
&self.discs
}
/// Returns the specified track at `index` from the album, returning
/// an error if the track index is out of range
pub fn track(&self, disc: usize, index: usize) -> Option<&Song> {
Some(self.discs.get(&disc)?[index])
pub fn track(&self, disc: u16, index: usize) -> Option<&Uuid> {
self.discs.get(&disc)?.get(index)
}
fn tracks(&self) -> Vec<&Song> {
fn tracks(&self) -> Vec<Uuid> {
let mut songs = Vec::new();
for disc in &self.discs {
songs.append(&mut disc.1.clone())
for disc in self.discs.values() {
songs.extend_from_slice(&disc)
}
songs
}
@ -618,15 +617,14 @@ impl Album<'_> {
/// Returns the number of songs in the album
pub fn len(&self) -> usize {
let mut total = 0;
for disc in &self.discs {
total += disc.1.len();
for disc in self.discs.values() {
total += disc.len();
}
total
}
}
impl TrackGroup for Album<'_> {}
impl TrackGroup for Album {}
#[derive(Debug, Serialize, Deserialize)]
pub struct MusicLibrary {
@ -688,6 +686,17 @@ impl MusicLibrary {
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
pub fn save(&self, config: Arc<RwLock<Config>>) -> Result<(), Box<dyn Error>> {
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`
/// with the `URI` that matches along with its position in the library
#[inline(always)]
pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
let result = self
.library
@ -759,9 +769,10 @@ impl MusicLibrary {
self.library.par_iter().for_each(|track| {
if path == track.primary_uri().unwrap().0.path() {
//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 {
Some(Arc::try_unwrap(result).unwrap().into_inner().unwrap())
} else {
@ -786,19 +797,11 @@ impl MusicLibrary {
continue;
}
/* TODO: figure out how to increase the speed of this maybe
// Check if the file path is already in the db
if self.query_uri(&URI::Local(path.to_path_buf())).is_some() {
continue;
}
// Save periodically while scanning
i += 1;
if i % 500 == 0 {
self.save(config).unwrap();
}
*/
let format = FileFormat::from_file(path)?;
let extension = match path.extension() {
Some(ext) => ext.to_string_lossy().to_ascii_lowercase(),
@ -834,6 +837,22 @@ impl MusicLibrary {
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>> {
let new_song = Song::from_file(target_file)?;
match self.add_song(new_song) {
@ -1038,65 +1057,42 @@ impl MusicLibrary {
/// Generates all albums from the track list
pub fn albums(&self) -> BTreeMap<String, Album> {
let mut albums: BTreeMap<String, Album> = BTreeMap::new();
for result in &self.library {
let title = match result.get_tag(&Tag::Album) {
Some(title) => title,
for song in &self.library {
let album_title = match song.get_tag(&Tag::Album) {
Some(title) => title.clone(),
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)
.unwrap_or(&"".to_string())
.parse::<usize>()
.parse::<u16>()
.unwrap_or(1);
match albums.get_mut(&norm_title) {
// If the album is in the list, add the track to the appropriate disc in it
match albums.get_mut(&album_title) {
// 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(disc) => disc.push(result),
Some(disc) => disc.push(song.uuid),
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 => {
let album_art = result.album_art.first();
let album_art = song.album_art.first();
let new_album = Album {
title,
artist: result.get_tag(&Tag::AlbumArtist),
discs: BTreeMap::from([(disc_num, vec![result])]),
cover: album_art,
title: album_title.clone(),
artist: song.get_tag(&Tag::AlbumArtist).cloned(),
discs: BTreeMap::from([(disc_num, vec![song.uuid])]),
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!
albums
}

View file

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