mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-19 17:52:55 -05:00
Made albums own their contents
This commit is contained in:
parent
81ccab01f1
commit
47483127ed
3 changed files with 78 additions and 83 deletions
|
@ -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>,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
Loading…
Reference in a new issue