Connect release groups to musicbrainz id #157
@ -5,6 +5,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::core::collection::{
|
use crate::core::collection::{
|
||||||
merge::{Merge, MergeSorted, WithId},
|
merge::{Merge, MergeSorted, WithId},
|
||||||
|
musicbrainz::MusicBrainz,
|
||||||
track::{Track, TrackFormat},
|
track::{Track, TrackFormat},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ pub struct Album {
|
|||||||
pub id: AlbumId,
|
pub id: AlbumId,
|
||||||
pub date: AlbumDate,
|
pub date: AlbumDate,
|
||||||
pub seq: AlbumSeq,
|
pub seq: AlbumSeq,
|
||||||
|
pub musicbrainz: Option<MusicBrainz>,
|
||||||
pub tracks: Vec<Track>,
|
pub tracks: Vec<Track>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +52,24 @@ impl AlbumDate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<u32> for AlbumDate {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
AlbumDate::new(value, AlbumMonth::default(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Into<AlbumMonth>> From<(u32, M)> for AlbumDate {
|
||||||
|
fn from(value: (u32, M)) -> Self {
|
||||||
|
AlbumDate::new(value.0, value.1, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Into<AlbumMonth>> From<(u32, M, u8)> for AlbumDate {
|
||||||
|
fn from(value: (u32, M, u8)) -> Self {
|
||||||
|
AlbumDate::new(value.0, value.1, value.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||||
pub enum AlbumMonth {
|
pub enum AlbumMonth {
|
||||||
@ -115,6 +135,16 @@ impl AlbumStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Album {
|
impl Album {
|
||||||
|
pub fn new<Id: Into<AlbumId>, Date: Into<AlbumDate>>(id: Id, date: Date) -> Self {
|
||||||
|
Album {
|
||||||
|
id: id.into(),
|
||||||
|
date: date.into(),
|
||||||
|
seq: AlbumSeq::default(),
|
||||||
|
musicbrainz: None,
|
||||||
|
tracks: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) {
|
pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) {
|
||||||
(&self.date, &self.seq, &self.id)
|
(&self.date, &self.seq, &self.id)
|
||||||
}
|
}
|
||||||
@ -202,6 +232,21 @@ mod tests {
|
|||||||
assert_eq!(<u8 as Into<AlbumMonth>>::into(255), AlbumMonth::None);
|
assert_eq!(<u8 as Into<AlbumMonth>>::into(255), AlbumMonth::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn album_date_from() {
|
||||||
|
let date: AlbumDate = 1986.into();
|
||||||
|
assert_eq!(date, AlbumDate::new(1986, AlbumMonth::default(), 0));
|
||||||
|
|
||||||
|
let date: AlbumDate = (1986, 5).into();
|
||||||
|
assert_eq!(date, AlbumDate::new(1986, AlbumMonth::May, 0));
|
||||||
|
|
||||||
|
let date: AlbumDate = (1986, AlbumMonth::June).into();
|
||||||
|
assert_eq!(date, AlbumDate::new(1986, AlbumMonth::June, 0));
|
||||||
|
|
||||||
|
let date: AlbumDate = (1986, 6, 8).into();
|
||||||
|
assert_eq!(date, AlbumDate::new(1986, AlbumMonth::June, 8));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn same_date_seq_cmp() {
|
fn same_date_seq_cmp() {
|
||||||
let date = AlbumDate::new(2024, 3, 2);
|
let date = AlbumDate::new(2024, 3, 2);
|
||||||
@ -209,22 +254,14 @@ mod tests {
|
|||||||
let album_id_1 = AlbumId {
|
let album_id_1 = AlbumId {
|
||||||
title: String::from("album z"),
|
title: String::from("album z"),
|
||||||
};
|
};
|
||||||
let album_1 = Album {
|
let mut album_1 = Album::new(album_id_1, date.clone());
|
||||||
id: album_id_1,
|
album_1.set_seq(AlbumSeq(1));
|
||||||
date: date.clone(),
|
|
||||||
seq: AlbumSeq(1),
|
|
||||||
tracks: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let album_id_2 = AlbumId {
|
let album_id_2 = AlbumId {
|
||||||
title: String::from("album a"),
|
title: String::from("album a"),
|
||||||
};
|
};
|
||||||
let album_2 = Album {
|
let mut album_2 = Album::new(album_id_2, date.clone());
|
||||||
id: album_id_2,
|
album_2.set_seq(AlbumSeq(2));
|
||||||
date: date.clone(),
|
|
||||||
seq: AlbumSeq(2),
|
|
||||||
tracks: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_ne!(album_1, album_2);
|
assert_ne!(album_1, album_2);
|
||||||
assert!(album_1 < album_2);
|
assert!(album_1 < album_2);
|
||||||
@ -232,12 +269,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_seq() {
|
fn set_clear_seq() {
|
||||||
let mut album = Album {
|
let mut album = Album::new("An album", AlbumDate::default());
|
||||||
id: "an album".into(),
|
|
||||||
date: AlbumDate::default(),
|
|
||||||
seq: AlbumSeq::default(),
|
|
||||||
tracks: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(album.seq, AlbumSeq(0));
|
assert_eq!(album.seq, AlbumSeq(0));
|
||||||
|
|
||||||
|
@ -2,16 +2,12 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{self, Debug, Display},
|
fmt::{self, Debug, Display},
|
||||||
mem,
|
mem,
|
||||||
str::FromStr,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use url::Url;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::core::collection::{
|
use crate::core::collection::{
|
||||||
album::Album,
|
album::Album,
|
||||||
merge::{Merge, MergeCollections, WithId},
|
merge::{Merge, MergeCollections, WithId},
|
||||||
Error,
|
musicbrainz::MusicBrainz,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An artist.
|
/// An artist.
|
||||||
@ -40,7 +36,7 @@ pub struct ArtistId {
|
|||||||
|
|
||||||
impl Artist {
|
impl Artist {
|
||||||
/// Create new [`Artist`] with the given [`ArtistId`].
|
/// Create new [`Artist`] with the given [`ArtistId`].
|
||||||
pub fn new<ID: Into<ArtistId>>(id: ID) -> Self {
|
pub fn new<Id: Into<ArtistId>>(id: Id) -> Self {
|
||||||
Artist {
|
Artist {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
sort: None,
|
sort: None,
|
||||||
@ -164,92 +160,6 @@ impl Display for ArtistId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An object with the [`IMbid`] trait contains a [MusicBrainz
|
|
||||||
/// Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID).
|
|
||||||
pub trait IMbid {
|
|
||||||
fn mbid(&self) -> &str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MusicBrainz reference.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct MusicBrainz(Url);
|
|
||||||
|
|
||||||
impl MusicBrainz {
|
|
||||||
/// Validate and wrap a MusicBrainz URL.
|
|
||||||
pub fn new_from_str<S: AsRef<str>>(url: S) -> Result<Self, Error> {
|
|
||||||
let url = Url::parse(url.as_ref())?;
|
|
||||||
Self::new_from_url(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate and wrap a MusicBrainz URL.
|
|
||||||
pub fn new_from_url(url: Url) -> Result<Self, Error> {
|
|
||||||
if !url
|
|
||||||
.domain()
|
|
||||||
.map(|u| u.ends_with("musicbrainz.org"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Err(Self::invalid_url_error(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
match url.path_segments().and_then(|mut ps| ps.nth(1)) {
|
|
||||||
Some(segment) => Uuid::try_parse(segment)?,
|
|
||||||
None => return Err(Self::invalid_url_error(url)),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(MusicBrainz(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid_url_error<U: Display>(url: U) -> Error {
|
|
||||||
Error::UrlError(format!("invalid MusicBrainz URL: {url}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for MusicBrainz {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for MusicBrainz {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
MusicBrainz::new_from_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A blanket TryFrom would be better, but https://stackoverflow.com/a/64407892
|
|
||||||
macro_rules! impl_try_from_for_musicbrainz {
|
|
||||||
($from:ty) => {
|
|
||||||
impl TryFrom<$from> for MusicBrainz {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: $from) -> Result<Self, Self::Error> {
|
|
||||||
MusicBrainz::new_from_str(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_try_from_for_musicbrainz!(&str);
|
|
||||||
impl_try_from_for_musicbrainz!(&String);
|
|
||||||
impl_try_from_for_musicbrainz!(String);
|
|
||||||
|
|
||||||
impl TryFrom<Url> for MusicBrainz {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: Url) -> Result<Self, Self::Error> {
|
|
||||||
MusicBrainz::new_from_url(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IMbid for MusicBrainz {
|
|
||||||
fn mbid(&self) -> &str {
|
|
||||||
// The URL is assumed to have been validated.
|
|
||||||
self.0.path_segments().and_then(|mut ps| ps.nth(1)).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::core::testmod::FULL_COLLECTION;
|
use crate::core::testmod::FULL_COLLECTION;
|
||||||
@ -263,40 +173,6 @@ mod tests {
|
|||||||
static MUSICBUTLER: &str = "https://www.musicbutler.io/artist-page/483340948";
|
static MUSICBUTLER: &str = "https://www.musicbutler.io/artist-page/483340948";
|
||||||
static MUSICBUTLER_2: &str = "https://www.musicbutler.io/artist-page/658903042/";
|
static MUSICBUTLER_2: &str = "https://www.musicbutler.io/artist-page/658903042/";
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn musicbrainz() {
|
|
||||||
let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8";
|
|
||||||
let url_str = format!("https://musicbrainz.org/artist/{uuid}");
|
|
||||||
let url: Url = url_str.as_str().try_into().unwrap();
|
|
||||||
let mb: MusicBrainz = url.try_into().unwrap();
|
|
||||||
assert_eq!(url_str, mb.as_ref());
|
|
||||||
assert_eq!(uuid, mb.mbid());
|
|
||||||
|
|
||||||
let url = "not a url at all".to_string();
|
|
||||||
let expected_error: Error = url::ParseError::RelativeUrlWithoutBase.into();
|
|
||||||
let actual_error = MusicBrainz::from_str(&url).unwrap_err();
|
|
||||||
assert_eq!(actual_error, expected_error);
|
|
||||||
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
|
||||||
|
|
||||||
let url = "https://musicbrainz.org/artist/i-am-not-a-uuid".to_string();
|
|
||||||
let expected_error: Error = Uuid::try_parse("i-am-not-a-uuid").unwrap_err().into();
|
|
||||||
let actual_error = MusicBrainz::from_str(&url).unwrap_err();
|
|
||||||
assert_eq!(actual_error, expected_error);
|
|
||||||
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
|
||||||
|
|
||||||
let url = "https://musicbrainz.org/artist".to_string();
|
|
||||||
let expected_error = Error::UrlError(format!("invalid MusicBrainz URL: {url}"));
|
|
||||||
let actual_error = MusicBrainz::from_str(&url).unwrap_err();
|
|
||||||
assert_eq!(actual_error, expected_error);
|
|
||||||
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn urls() {
|
|
||||||
assert!(MusicBrainz::from_str(MUSICBRAINZ).is_ok());
|
|
||||||
assert!(MusicBrainz::from_str(MUSICBUTLER).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn artist_sort_set_clear() {
|
fn artist_sort_set_clear() {
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist_id = ArtistId::new("an artist");
|
||||||
@ -336,15 +212,6 @@ mod tests {
|
|||||||
assert!(artist < Artist::new(sort_id_2.clone()));
|
assert!(artist < Artist::new(sort_id_2.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn musicbrainz_url() {
|
|
||||||
let result: Result<MusicBrainz, Error> = MUSICBUTLER.try_into();
|
|
||||||
assert!(result.is_err());
|
|
||||||
|
|
||||||
let result: Result<MusicBrainz, Error> = MUSICBRAINZ.try_into();
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_musicbrainz_url() {
|
fn set_clear_musicbrainz_url() {
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut artist = Artist::new(ArtistId::new("an artist"));
|
||||||
@ -353,15 +220,15 @@ mod tests {
|
|||||||
assert_eq!(artist.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Setting a URL on an artist.
|
// Setting a URL on an artist.
|
||||||
artist.set_musicbrainz_url(MUSICBRAINZ.try_into().unwrap());
|
artist.set_musicbrainz_url(MusicBrainz::artist_from_str(MUSICBRAINZ).unwrap());
|
||||||
_ = expected.insert(MUSICBRAINZ.try_into().unwrap());
|
_ = expected.insert(MusicBrainz::artist_from_str(MUSICBRAINZ).unwrap());
|
||||||
assert_eq!(artist.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
artist.set_musicbrainz_url(MUSICBRAINZ.try_into().unwrap());
|
artist.set_musicbrainz_url(MusicBrainz::artist_from_str(MUSICBRAINZ).unwrap());
|
||||||
assert_eq!(artist.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
artist.set_musicbrainz_url(MUSICBRAINZ_2.try_into().unwrap());
|
artist.set_musicbrainz_url(MusicBrainz::artist_from_str(MUSICBRAINZ_2).unwrap());
|
||||||
_ = expected.insert(MUSICBRAINZ_2.try_into().unwrap());
|
_ = expected.insert(MusicBrainz::artist_from_str(MUSICBRAINZ_2).unwrap());
|
||||||
assert_eq!(artist.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Clearing URLs.
|
// Clearing URLs.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
pub mod album;
|
pub mod album;
|
||||||
pub mod artist;
|
pub mod artist;
|
||||||
|
pub mod musicbrainz;
|
||||||
pub mod track;
|
pub mod track;
|
||||||
|
|
||||||
mod merge;
|
mod merge;
|
||||||
|
173
src/core/collection/musicbrainz.rs
Normal file
173
src/core/collection/musicbrainz.rs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
use url::Url;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::core::collection::Error;
|
||||||
|
|
||||||
|
/// An object with the [`IMbid`] trait contains a [MusicBrainz
|
||||||
|
/// Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID).
|
||||||
|
pub trait IMbid {
|
||||||
|
fn mbid(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MusicBrainz reference.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct MusicBrainz(Url);
|
||||||
|
|
||||||
|
impl MusicBrainz {
|
||||||
|
pub fn artist_from_str<S: AsRef<str>>(url: S) -> Result<Self, Error> {
|
||||||
|
Self::artist_from_url(url.as_ref().try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn album_from_str<S: AsRef<str>>(url: S) -> Result<Self, Error> {
|
||||||
|
Self::album_from_url(url.as_ref().try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn artist_from_url(url: Url) -> Result<Self, Error> {
|
||||||
|
Self::new(url, "artist")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn album_from_url(url: Url) -> Result<Self, Error> {
|
||||||
|
Self::new(url, "release-group")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(url: Url, mb_type: &str) -> Result<Self, Error> {
|
||||||
|
if !url
|
||||||
|
.domain()
|
||||||
|
.map(|u| u.ends_with("musicbrainz.org"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Err(Self::invalid_url_error(url, mb_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
// path_segments only returns an empty iterator if the URL cannot-be-a-base. However, if the
|
||||||
|
// URL cannot-be-a-base then it will fail the check above already as it won't have a domain.
|
||||||
|
if url.path_segments().and_then(|mut ps| ps.nth(0)).unwrap() != mb_type {
|
||||||
|
return Err(Self::invalid_url_error(url, mb_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
match url.path_segments().and_then(|mut ps| ps.nth(1)) {
|
||||||
|
Some(segment) => Uuid::try_parse(segment)?,
|
||||||
|
None => return Err(Self::invalid_url_error(url, mb_type)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(MusicBrainz(url))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_url_error<U: Display>(url: U, mb_type: &str) -> Error {
|
||||||
|
Error::UrlError(format!("invalid {mb_type} MusicBrainz URL: {url}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for MusicBrainz {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IMbid for MusicBrainz {
|
||||||
|
fn mbid(&self) -> &str {
|
||||||
|
// The URL is assumed to have been validated.
|
||||||
|
self.0.path_segments().and_then(|mut ps| ps.nth(1)).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn artist() {
|
||||||
|
let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8";
|
||||||
|
let url_str = format!("https://musicbrainz.org/artist/{uuid}");
|
||||||
|
|
||||||
|
let mb = MusicBrainz::artist_from_str(&url_str).unwrap();
|
||||||
|
assert_eq!(url_str, mb.as_ref());
|
||||||
|
assert_eq!(uuid, mb.mbid());
|
||||||
|
|
||||||
|
let url: Url = url_str.as_str().try_into().unwrap();
|
||||||
|
let mb = MusicBrainz::artist_from_url(url).unwrap();
|
||||||
|
assert_eq!(url_str, mb.as_ref());
|
||||||
|
assert_eq!(uuid, mb.mbid());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn album() {
|
||||||
|
let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8";
|
||||||
|
let url_str = format!("https://musicbrainz.org/release-group/{uuid}");
|
||||||
|
|
||||||
|
let mb = MusicBrainz::album_from_str(&url_str).unwrap();
|
||||||
|
assert_eq!(url_str, mb.as_ref());
|
||||||
|
assert_eq!(uuid, mb.mbid());
|
||||||
|
|
||||||
|
let url: Url = url_str.as_str().try_into().unwrap();
|
||||||
|
let mb = MusicBrainz::album_from_url(url).unwrap();
|
||||||
|
assert_eq!(url_str, mb.as_ref());
|
||||||
|
assert_eq!(uuid, mb.mbid());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_a_url() {
|
||||||
|
let url = "not a url at all";
|
||||||
|
let expected_error: Error = url::ParseError::RelativeUrlWithoutBase.into();
|
||||||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||||||
|
assert_eq!(actual_error, expected_error);
|
||||||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_url() {
|
||||||
|
let url = "https://www.musicbutler.io/artist-page/483340948";
|
||||||
|
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
|
||||||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||||||
|
assert_eq!(actual_error, expected_error);
|
||||||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn artist_invalid_type() {
|
||||||
|
let url = "https://musicbrainz.org/release-group/i-am-not-a-uuid";
|
||||||
|
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
|
||||||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||||||
|
assert_eq!(actual_error, expected_error);
|
||||||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn album_invalid_type() {
|
||||||
|
let url = "https://musicbrainz.org/artist/i-am-not-a-uuid";
|
||||||
|
let expected_error =
|
||||||
|
Error::UrlError(format!("invalid release-group MusicBrainz URL: {url}"));
|
||||||
|
let actual_error = MusicBrainz::album_from_str(url).unwrap_err();
|
||||||
|
assert_eq!(actual_error, expected_error);
|
||||||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_uuid() {
|
||||||
|
let url = "https://musicbrainz.org/artist/i-am-not-a-uuid";
|
||||||
|
let expected_error: Error = Uuid::try_parse("i-am-not-a-uuid").unwrap_err().into();
|
||||||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||||||
|
assert_eq!(actual_error, expected_error);
|
||||||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_type() {
|
||||||
|
let url = "https://musicbrainz.org";
|
||||||
|
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}/"));
|
||||||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||||||
|
assert_eq!(actual_error, expected_error);
|
||||||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_uuid() {
|
||||||
|
let url = "https://musicbrainz.org/artist";
|
||||||
|
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
|
||||||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||||||
|
assert_eq!(actual_error, expected_error);
|
||||||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
pub static DATABASE_JSON: &str = "{\
|
pub static DATABASE_JSON: &str = "{\
|
||||||
\"V20240302\":\
|
\"V20240308\":\
|
||||||
[\
|
[\
|
||||||
{\
|
{\
|
||||||
\"name\":\"Album_Artist ‘A’\",\
|
\"name\":\"Album_Artist ‘A’\",\
|
||||||
@ -10,8 +10,11 @@ pub static DATABASE_JSON: &str = "{\
|
|||||||
\"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums\"]\
|
\"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums\"]\
|
||||||
},\
|
},\
|
||||||
\"albums\":[\
|
\"albums\":[\
|
||||||
{\"title\":\"album_title a.a\",\"seq\":1},\
|
{\
|
||||||
{\"title\":\"album_title a.b\",\"seq\":1}\
|
\"title\":\"album_title a.a\",\"seq\":1,\
|
||||||
|
\"musicbrainz\":\"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000\"\
|
||||||
|
},\
|
||||||
|
{\"title\":\"album_title a.b\",\"seq\":1,\"musicbrainz\":null}\
|
||||||
]\
|
]\
|
||||||
},\
|
},\
|
||||||
{\
|
{\
|
||||||
@ -27,10 +30,16 @@ pub static DATABASE_JSON: &str = "{\
|
|||||||
\"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums\"]\
|
\"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums\"]\
|
||||||
},\
|
},\
|
||||||
\"albums\":[\
|
\"albums\":[\
|
||||||
{\"title\":\"album_title b.a\",\"seq\":1},\
|
{\"title\":\"album_title b.a\",\"seq\":1,\"musicbrainz\":null},\
|
||||||
{\"title\":\"album_title b.b\",\"seq\":3},\
|
{\
|
||||||
{\"title\":\"album_title b.c\",\"seq\":2},\
|
\"title\":\"album_title b.b\",\"seq\":3,\
|
||||||
{\"title\":\"album_title b.d\",\"seq\":4}\
|
\"musicbrainz\":\"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111\"\
|
||||||
|
},\
|
||||||
|
{\
|
||||||
|
\"title\":\"album_title b.c\",\"seq\":2,\
|
||||||
|
\"musicbrainz\":\"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112\"\
|
||||||
|
},\
|
||||||
|
{\"title\":\"album_title b.d\",\"seq\":4,\"musicbrainz\":null}\
|
||||||
]\
|
]\
|
||||||
},\
|
},\
|
||||||
{\
|
{\
|
||||||
@ -39,8 +48,8 @@ pub static DATABASE_JSON: &str = "{\
|
|||||||
\"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\
|
\"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\
|
||||||
\"properties\":{},\
|
\"properties\":{},\
|
||||||
\"albums\":[\
|
\"albums\":[\
|
||||||
{\"title\":\"album_title c.a\",\"seq\":0},\
|
{\"title\":\"album_title c.a\",\"seq\":0,\"musicbrainz\":null},\
|
||||||
{\"title\":\"album_title c.b\",\"seq\":0}\
|
{\"title\":\"album_title c.b\",\"seq\":0,\"musicbrainz\":null}\
|
||||||
]\
|
]\
|
||||||
},\
|
},\
|
||||||
{\
|
{\
|
||||||
@ -49,8 +58,8 @@ pub static DATABASE_JSON: &str = "{\
|
|||||||
\"musicbrainz\":null,\
|
\"musicbrainz\":null,\
|
||||||
\"properties\":{},\
|
\"properties\":{},\
|
||||||
\"albums\":[\
|
\"albums\":[\
|
||||||
{\"title\":\"album_title d.a\",\"seq\":0},\
|
{\"title\":\"album_title d.a\",\"seq\":0,\"musicbrainz\":null},\
|
||||||
{\"title\":\"album_title d.b\",\"seq\":0}\
|
{\"title\":\"album_title d.b\",\"seq\":0,\"musicbrainz\":null}\
|
||||||
]\
|
]\
|
||||||
}\
|
}\
|
||||||
]\
|
]\
|
||||||
|
@ -6,6 +6,7 @@ use crate::core::{
|
|||||||
collection::{
|
collection::{
|
||||||
album::{Album, AlbumDate, AlbumId, AlbumSeq},
|
album::{Album, AlbumDate, AlbumId, AlbumSeq},
|
||||||
artist::{Artist, ArtistId},
|
artist::{Artist, ArtistId},
|
||||||
|
musicbrainz::MusicBrainz,
|
||||||
Collection,
|
Collection,
|
||||||
},
|
},
|
||||||
database::LoadError,
|
database::LoadError,
|
||||||
@ -13,7 +14,20 @@ use crate::core::{
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub enum DeserializeDatabase {
|
pub enum DeserializeDatabase {
|
||||||
V20240302(Vec<DeserializeArtist>),
|
V20240308(Vec<DeserializeArtist>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DeserializeDatabase> for Collection {
|
||||||
|
type Error = LoadError;
|
||||||
|
|
||||||
|
fn try_from(database: DeserializeDatabase) -> Result<Self, Self::Error> {
|
||||||
|
match database {
|
||||||
|
DeserializeDatabase::V20240308(collection) => collection
|
||||||
|
.into_iter()
|
||||||
|
.map(|artist| artist.try_into())
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -29,19 +43,7 @@ pub struct DeserializeArtist {
|
|||||||
pub struct DeserializeAlbum {
|
pub struct DeserializeAlbum {
|
||||||
title: String,
|
title: String,
|
||||||
seq: u8,
|
seq: u8,
|
||||||
}
|
musicbrainz: Option<String>,
|
||||||
|
|
||||||
impl TryFrom<DeserializeDatabase> for Collection {
|
|
||||||
type Error = LoadError;
|
|
||||||
|
|
||||||
fn try_from(database: DeserializeDatabase) -> Result<Self, Self::Error> {
|
|
||||||
match database {
|
|
||||||
DeserializeDatabase::V20240302(collection) => collection
|
|
||||||
.into_iter()
|
|
||||||
.map(|artist| artist.try_into())
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<DeserializeArtist> for Artist {
|
impl TryFrom<DeserializeArtist> for Artist {
|
||||||
@ -51,20 +53,33 @@ impl TryFrom<DeserializeArtist> for Artist {
|
|||||||
Ok(Artist {
|
Ok(Artist {
|
||||||
id: ArtistId::new(artist.name),
|
id: ArtistId::new(artist.name),
|
||||||
sort: artist.sort.map(ArtistId::new),
|
sort: artist.sort.map(ArtistId::new),
|
||||||
musicbrainz: artist.musicbrainz.map(TryInto::try_into).transpose()?,
|
musicbrainz: artist
|
||||||
|
.musicbrainz
|
||||||
|
.map(MusicBrainz::artist_from_str)
|
||||||
|
.transpose()?,
|
||||||
properties: artist.properties,
|
properties: artist.properties,
|
||||||
albums: artist.albums.into_iter().map(Into::into).collect(),
|
albums: artist
|
||||||
|
.albums
|
||||||
|
.into_iter()
|
||||||
|
.map(TryInto::try_into)
|
||||||
|
.collect::<Result<Vec<Album>, LoadError>>()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DeserializeAlbum> for Album {
|
impl TryFrom<DeserializeAlbum> for Album {
|
||||||
fn from(album: DeserializeAlbum) -> Self {
|
type Error = LoadError;
|
||||||
Album {
|
|
||||||
|
fn try_from(album: DeserializeAlbum) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Album {
|
||||||
id: AlbumId { title: album.title },
|
id: AlbumId { title: album.title },
|
||||||
date: AlbumDate::default(),
|
date: AlbumDate::default(),
|
||||||
seq: AlbumSeq(album.seq),
|
seq: AlbumSeq(album.seq),
|
||||||
|
musicbrainz: album
|
||||||
|
.musicbrainz
|
||||||
|
.map(MusicBrainz::album_from_str)
|
||||||
|
.transpose()?,
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,13 @@ use crate::core::collection::{album::Album, artist::Artist, Collection};
|
|||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub enum SerializeDatabase<'a> {
|
pub enum SerializeDatabase<'a> {
|
||||||
V20240302(Vec<SerializeArtist<'a>>),
|
V20240308(Vec<SerializeArtist<'a>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a Collection> for SerializeDatabase<'a> {
|
||||||
|
fn from(collection: &'a Collection) -> Self {
|
||||||
|
SerializeDatabase::V20240308(collection.iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@ -22,12 +28,7 @@ pub struct SerializeArtist<'a> {
|
|||||||
pub struct SerializeAlbum<'a> {
|
pub struct SerializeAlbum<'a> {
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
seq: u8,
|
seq: u8,
|
||||||
}
|
musicbrainz: Option<&'a str>,
|
||||||
|
|
||||||
impl<'a> From<&'a Collection> for SerializeDatabase<'a> {
|
|
||||||
fn from(collection: &'a Collection) -> Self {
|
|
||||||
SerializeDatabase::V20240302(collection.iter().map(Into::into).collect())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a Artist> for SerializeArtist<'a> {
|
impl<'a> From<&'a Artist> for SerializeArtist<'a> {
|
||||||
@ -35,7 +36,7 @@ impl<'a> From<&'a Artist> for SerializeArtist<'a> {
|
|||||||
SerializeArtist {
|
SerializeArtist {
|
||||||
name: &artist.id.name,
|
name: &artist.id.name,
|
||||||
sort: artist.sort.as_ref().map(|id| id.name.as_ref()),
|
sort: artist.sort.as_ref().map(|id| id.name.as_ref()),
|
||||||
musicbrainz: artist.musicbrainz.as_ref().map(|mb| mb.as_ref()),
|
musicbrainz: artist.musicbrainz.as_ref().map(AsRef::as_ref),
|
||||||
properties: artist
|
properties: artist
|
||||||
.properties
|
.properties
|
||||||
.iter()
|
.iter()
|
||||||
@ -51,6 +52,7 @@ impl<'a> From<&'a Album> for SerializeAlbum<'a> {
|
|||||||
SerializeAlbum {
|
SerializeAlbum {
|
||||||
title: &album.id.title,
|
title: &album.id.title,
|
||||||
seq: album.seq.0,
|
seq: album.seq.0,
|
||||||
|
musicbrainz: album.musicbrainz.as_ref().map(AsRef::as_ref),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ use std::collections::HashMap;
|
|||||||
use crate::core::{
|
use crate::core::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{Album, AlbumDate, AlbumId, AlbumSeq},
|
album::{Album, AlbumDate, AlbumId, AlbumSeq},
|
||||||
artist::{Artist, ArtistId, MusicBrainz},
|
artist::{Artist, ArtistId},
|
||||||
|
musicbrainz::MusicBrainz,
|
||||||
track::{Track, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackId, TrackNum, TrackQuality},
|
||||||
Collection, MergeCollections,
|
Collection, MergeCollections,
|
||||||
},
|
},
|
||||||
@ -140,12 +141,11 @@ impl<LIB, DB> MusicHoard<LIB, DB> {
|
|||||||
.find(|album| album.id == album_id)
|
.find(|album| album.id == album_id)
|
||||||
{
|
{
|
||||||
Some(album) => album.tracks.push(track),
|
Some(album) => album.tracks.push(track),
|
||||||
None => artist.albums.push(Album {
|
None => {
|
||||||
id: album_id,
|
let mut album = Album::new(album_id, album_date);
|
||||||
date: album_date,
|
album.tracks.push(track);
|
||||||
seq: AlbumSeq(0),
|
artist.albums.push(album);
|
||||||
tracks: vec![track],
|
}
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,15 +353,12 @@ impl<LIB, DB: IDatabase> MusicHoard<LIB, DB> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_artist_musicbrainz<Id: AsRef<ArtistId>, Mb: TryInto<MusicBrainz, Error = E>, E>(
|
pub fn set_artist_musicbrainz<Id: AsRef<ArtistId>, S: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: Id,
|
artist_id: Id,
|
||||||
url: Mb,
|
url: S,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error> {
|
||||||
where
|
let mb = MusicBrainz::artist_from_str(url)?;
|
||||||
Error: From<E>,
|
|
||||||
{
|
|
||||||
let mb = url.try_into()?;
|
|
||||||
self.update_artist(artist_id.as_ref(), |artist| artist.set_musicbrainz_url(mb))
|
self.update_artist(artist_id.as_ref(), |artist| artist.set_musicbrainz_url(mb))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,7 +467,7 @@ mod tests {
|
|||||||
use mockall::{predicate, Sequence};
|
use mockall::{predicate, Sequence};
|
||||||
|
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
collection::artist::{ArtistId, MusicBrainz},
|
collection::{artist::ArtistId, musicbrainz::MusicBrainz},
|
||||||
database::{self, MockIDatabase},
|
database::{self, MockIDatabase},
|
||||||
library::{self, testmod::LIBRARY_ITEMS, MockILibrary},
|
library::{self, testmod::LIBRARY_ITEMS, MockILibrary},
|
||||||
testmod::{FULL_COLLECTION, LIBRARY_COLLECTION},
|
testmod::{FULL_COLLECTION, LIBRARY_COLLECTION},
|
||||||
@ -595,7 +592,7 @@ mod tests {
|
|||||||
.set_artist_musicbrainz(&artist_id, MUSICBUTLER)
|
.set_artist_musicbrainz(&artist_id, MUSICBUTLER)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
let expected_err = Error::CollectionError(format!(
|
let expected_err = Error::CollectionError(format!(
|
||||||
"an error occurred when processing a URL: invalid MusicBrainz URL: {MUSICBUTLER}"
|
"an error occurred when processing a URL: invalid artist MusicBrainz URL: {MUSICBUTLER}"
|
||||||
));
|
));
|
||||||
assert_eq!(actual_err, expected_err);
|
assert_eq!(actual_err, expected_err);
|
||||||
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
||||||
@ -626,7 +623,7 @@ mod tests {
|
|||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_artist_musicbrainz(&artist_id, MUSICBRAINZ)
|
.set_artist_musicbrainz(&artist_id, MUSICBRAINZ)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
_ = expected.insert(MUSICBRAINZ.try_into().unwrap());
|
_ = expected.insert(MusicBrainz::artist_from_str(MUSICBRAINZ).unwrap());
|
||||||
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Clearing URLs on an artist that does not exist is an error.
|
// Clearing URLs on an artist that does not exist is an error.
|
||||||
@ -747,12 +744,9 @@ mod tests {
|
|||||||
let album_id_2 = AlbumId::new("another album");
|
let album_id_2 = AlbumId::new("another album");
|
||||||
|
|
||||||
let mut database_result = vec![Artist::new(artist_id.clone())];
|
let mut database_result = vec![Artist::new(artist_id.clone())];
|
||||||
database_result[0].albums.push(Album {
|
database_result[0]
|
||||||
id: album_id.clone(),
|
.albums
|
||||||
date: AlbumDate::default(),
|
.push(Album::new(album_id.clone(), AlbumDate::default()));
|
||||||
seq: AlbumSeq::default(),
|
|
||||||
tracks: vec![],
|
|
||||||
});
|
|
||||||
|
|
||||||
database
|
database
|
||||||
.expect_load()
|
.expect_load()
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::core::collection::{
|
use crate::core::collection::{
|
||||||
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq},
|
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq},
|
||||||
artist::{Artist, ArtistId, MusicBrainz},
|
artist::{Artist, ArtistId},
|
||||||
|
musicbrainz::MusicBrainz,
|
||||||
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
||||||
};
|
};
|
||||||
use crate::tests::*;
|
use crate::tests::*;
|
||||||
|
28
src/tests.rs
28
src/tests.rs
@ -19,6 +19,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -79,6 +80,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -124,6 +126,7 @@ macro_rules! library_collection {
|
|||||||
day: 6,
|
day: 6,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -162,6 +165,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -200,6 +204,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -238,6 +243,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -288,6 +294,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -326,6 +333,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -374,6 +382,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -412,6 +421,7 @@ macro_rules! library_collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -454,7 +464,7 @@ macro_rules! full_collection {
|
|||||||
let artist_a = iter.next().unwrap();
|
let artist_a = iter.next().unwrap();
|
||||||
assert_eq!(artist_a.id.name, "Album_Artist ‘A’");
|
assert_eq!(artist_a.id.name, "Album_Artist ‘A’");
|
||||||
|
|
||||||
artist_a.musicbrainz = Some(MusicBrainz::from_str(
|
artist_a.musicbrainz = Some(MusicBrainz::artist_from_str(
|
||||||
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000",
|
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000",
|
||||||
).unwrap());
|
).unwrap());
|
||||||
|
|
||||||
@ -472,10 +482,14 @@ macro_rules! full_collection {
|
|||||||
artist_a.albums[0].seq = AlbumSeq(1);
|
artist_a.albums[0].seq = AlbumSeq(1);
|
||||||
artist_a.albums[1].seq = AlbumSeq(1);
|
artist_a.albums[1].seq = AlbumSeq(1);
|
||||||
|
|
||||||
|
artist_a.albums[0].musicbrainz = Some(MusicBrainz::album_from_str(
|
||||||
|
"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000"
|
||||||
|
).unwrap());
|
||||||
|
|
||||||
let artist_b = iter.next().unwrap();
|
let artist_b = iter.next().unwrap();
|
||||||
assert_eq!(artist_b.id.name, "Album_Artist ‘B’");
|
assert_eq!(artist_b.id.name, "Album_Artist ‘B’");
|
||||||
|
|
||||||
artist_b.musicbrainz = Some(MusicBrainz::from_str(
|
artist_b.musicbrainz = Some(MusicBrainz::artist_from_str(
|
||||||
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
|
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
|
||||||
).unwrap());
|
).unwrap());
|
||||||
|
|
||||||
@ -497,10 +511,18 @@ macro_rules! full_collection {
|
|||||||
artist_b.albums[2].seq = AlbumSeq(2);
|
artist_b.albums[2].seq = AlbumSeq(2);
|
||||||
artist_b.albums[3].seq = AlbumSeq(4);
|
artist_b.albums[3].seq = AlbumSeq(4);
|
||||||
|
|
||||||
|
artist_b.albums[1].musicbrainz = Some(MusicBrainz::album_from_str(
|
||||||
|
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111"
|
||||||
|
).unwrap());
|
||||||
|
|
||||||
|
artist_b.albums[2].musicbrainz = Some(MusicBrainz::album_from_str(
|
||||||
|
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112"
|
||||||
|
).unwrap());
|
||||||
|
|
||||||
let artist_c = iter.next().unwrap();
|
let artist_c = iter.next().unwrap();
|
||||||
assert_eq!(artist_c.id.name, "The Album_Artist ‘C’");
|
assert_eq!(artist_c.id.name, "The Album_Artist ‘C’");
|
||||||
|
|
||||||
artist_c.musicbrainz = Some(MusicBrainz::from_str(
|
artist_c.musicbrainz = Some(MusicBrainz::artist_from_str(
|
||||||
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
|
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
|
||||||
).unwrap());
|
).unwrap());
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use std::{collections::HashMap, str::FromStr};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq},
|
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq},
|
||||||
artist::{Artist, ArtistId, MusicBrainz},
|
artist::{Artist, ArtistId},
|
||||||
|
musicbrainz::MusicBrainz,
|
||||||
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
@ -744,7 +744,7 @@ impl IUi for Ui {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use musichoard::collection::{album::AlbumId, artist::ArtistId};
|
use musichoard::collection::artist::ArtistId;
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppPublic, AppPublicInner, Delta},
|
app::{AppPublic, AppPublicInner, Delta},
|
||||||
@ -868,12 +868,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn empty_album() {
|
fn empty_album() {
|
||||||
let mut artists: Vec<Artist> = vec![Artist::new(ArtistId::new("An artist"))];
|
let mut artists: Vec<Artist> = vec![Artist::new(ArtistId::new("An artist"))];
|
||||||
artists[0].albums.push(Album {
|
artists[0]
|
||||||
id: AlbumId::new("An album"),
|
.albums
|
||||||
date: AlbumDate::default(),
|
.push(Album::new("An album", AlbumDate::default()));
|
||||||
seq: AlbumSeq::default(),
|
|
||||||
tracks: vec![],
|
|
||||||
});
|
|
||||||
let mut selection = Selection::new(&artists);
|
let mut selection = Selection::new(&artists);
|
||||||
|
|
||||||
draw_test_suite(&artists, &mut selection);
|
draw_test_suite(&artists, &mut selection);
|
||||||
|
@ -1 +1 @@
|
|||||||
{"V20240302":[{"name":"Аркона","sort":"Arkona","musicbrainz":"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212","properties":{"Bandcamp":["https://arkonamoscow.bandcamp.com/"],"MusicButler":["https://www.musicbutler.io/artist-page/283448581"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums"]},"albums":[{"title":"Slovo","seq":0}]},{"name":"Eluveitie","sort":null,"musicbrainz":"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/269358403"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums"]},"albums":[{"title":"Vên [re‐recorded]","seq":0},{"title":"Slania","seq":0}]},{"name":"Frontside","sort":null,"musicbrainz":"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/826588800"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums"]},"albums":[{"title":"…nasze jest królestwo, potęga i chwała na wieki…","seq":0}]},{"name":"Heaven’s Basement","sort":"Heaven’s Basement","musicbrainz":"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/291158685"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums"]},"albums":[{"title":"Paper Plague","seq":0},{"title":"Unbreakable","seq":0}]},{"name":"Metallica","sort":null,"musicbrainz":"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/3996865"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums"]},"albums":[{"title":"Ride the Lightning","seq":0},{"title":"S&M","seq":0}]}]}
|
{"V20240308":[{"name":"Аркона","sort":"Arkona","musicbrainz":"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212","properties":{"Bandcamp":["https://arkonamoscow.bandcamp.com/"],"MusicButler":["https://www.musicbutler.io/artist-page/283448581"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums"]},"albums":[{"title":"Slovo","seq":0,"musicbrainz":null}]},{"name":"Eluveitie","sort":null,"musicbrainz":"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/269358403"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums"]},"albums":[{"title":"Vên [re‐recorded]","seq":0,"musicbrainz":null},{"title":"Slania","seq":0,"musicbrainz":null}]},{"name":"Frontside","sort":null,"musicbrainz":"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/826588800"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums"]},"albums":[{"title":"…nasze jest królestwo, potęga i chwała na wieki…","seq":0,"musicbrainz":null}]},{"name":"Heaven’s Basement","sort":"Heaven’s Basement","musicbrainz":"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/291158685"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums"]},"albums":[{"title":"Paper Plague","seq":0,"musicbrainz":null},{"title":"Unbreakable","seq":0,"musicbrainz":null}]},{"name":"Metallica","sort":null,"musicbrainz":"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/3996865"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums"]},"albums":[{"title":"Ride the Lightning","seq":0,"musicbrainz":null},{"title":"S&M","seq":0,"musicbrainz":null}]}]}
|
@ -1,9 +1,10 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq},
|
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq},
|
||||||
artist::{Artist, ArtistId, MusicBrainz},
|
artist::{Artist, ArtistId},
|
||||||
|
musicbrainz::MusicBrainz,
|
||||||
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
||||||
Collection,
|
Collection,
|
||||||
};
|
};
|
||||||
@ -17,7 +18,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
sort: Some(ArtistId{
|
sort: Some(ArtistId{
|
||||||
name: String::from("Arkona")
|
name: String::from("Arkona")
|
||||||
}),
|
}),
|
||||||
musicbrainz: Some(MusicBrainz::from_str(
|
musicbrainz: Some(MusicBrainz::artist_from_str(
|
||||||
"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212"
|
"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
@ -41,6 +42,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -204,7 +206,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Eluveitie"),
|
name: String::from("Eluveitie"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
musicbrainz: Some(MusicBrainz::from_str(
|
musicbrainz: Some(MusicBrainz::artist_from_str(
|
||||||
"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38",
|
"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38",
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
@ -226,6 +228,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -305,6 +308,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -447,7 +451,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Frontside"),
|
name: String::from("Frontside"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
musicbrainz: Some(MusicBrainz::from_str(
|
musicbrainz: Some(MusicBrainz::artist_from_str(
|
||||||
"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490",
|
"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490",
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
@ -468,6 +472,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -600,7 +605,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
sort: Some(ArtistId {
|
sort: Some(ArtistId {
|
||||||
name: String::from("Heaven’s Basement"),
|
name: String::from("Heaven’s Basement"),
|
||||||
}),
|
}),
|
||||||
musicbrainz: Some(MusicBrainz::from_str(
|
musicbrainz: Some(MusicBrainz::artist_from_str(
|
||||||
"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc",
|
"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc",
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
@ -621,6 +626,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -644,6 +650,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -730,7 +737,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Metallica"),
|
name: String::from("Metallica"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
musicbrainz: Some(MusicBrainz::from_str(
|
musicbrainz: Some(MusicBrainz::artist_from_str(
|
||||||
"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab",
|
"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab",
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
@ -752,6 +759,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -853,6 +861,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
day: 0,
|
day: 0,
|
||||||
},
|
},
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
musicbrainz: None,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
|
Loading…
Reference in New Issue
Block a user