Implement cannot have MBID in core #220

Merged
wojtek merged 2 commits from 189---enable-fetch-to-apply-modifications-to-the-database into main 2024-09-24 22:38:40 +02:00
20 changed files with 246 additions and 125 deletions

View File

@ -5,7 +5,7 @@ use std::{
use crate::core::collection::{
merge::{Merge, MergeSorted, WithId},
musicbrainz::MbAlbumRef,
musicbrainz::{MbAlbumRef, MbRefOption},
track::{Track, TrackFormat},
};
@ -22,7 +22,7 @@ pub struct AlbumMeta {
pub id: AlbumId,
pub date: AlbumDate,
pub seq: AlbumSeq,
pub musicbrainz: Option<MbAlbumRef>,
pub musicbrainz: MbRefOption<MbAlbumRef>,
pub primary_type: Option<AlbumPrimaryType>,
pub secondary_types: Vec<AlbumSecondaryType>,
}
@ -186,7 +186,7 @@ impl AlbumMeta {
id: id.into(),
date: date.into(),
seq: AlbumSeq::default(),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type,
secondary_types,
}
@ -364,7 +364,7 @@ mod tests {
let mut album = Album::new(AlbumId::new("an album"), AlbumDate::default(), None, vec![]);
let mut expected: Option<MbAlbumRef> = None;
let mut expected: MbRefOption<MbAlbumRef> = MbRefOption::None;
assert_eq!(album.meta.musicbrainz, expected);
// Setting a URL on an album.

View File

@ -7,7 +7,7 @@ use std::{
use crate::core::collection::{
album::Album,
merge::{Merge, MergeCollections, WithId},
musicbrainz::MbArtistRef,
musicbrainz::{MbArtistRef, MbRefOption},
};
/// An artist.
@ -22,7 +22,7 @@ pub struct Artist {
pub struct ArtistMeta {
pub id: ArtistId,
pub sort: Option<ArtistId>,
pub musicbrainz: Option<MbArtistRef>,
pub musicbrainz: MbRefOption<MbArtistRef>,
pub properties: HashMap<String, Vec<String>>,
}
@ -75,7 +75,7 @@ impl ArtistMeta {
ArtistMeta {
id: id.into(),
sort: None,
musicbrainz: None,
musicbrainz: MbRefOption::None,
properties: HashMap::new(),
}
}
@ -255,7 +255,7 @@ mod tests {
fn set_clear_musicbrainz_url() {
let mut artist = Artist::new(ArtistId::new("an artist"));
let mut expected: Option<MbArtistRef> = None;
let mut expected: MbRefOption<MbArtistRef> = MbRefOption::None;
assert_eq!(artist.meta.musicbrainz, expected);
// Setting a URL on an artist.
@ -417,7 +417,7 @@ mod tests {
let left = FULL_COLLECTION[0].to_owned();
let mut right = FULL_COLLECTION[1].to_owned();
right.meta.id = left.meta.id.clone();
right.meta.musicbrainz = None;
right.meta.musicbrainz = MbRefOption::None;
right.meta.properties = HashMap::new();
let mut expected = left.clone();

View File

@ -1,4 +1,4 @@
use std::fmt::{Debug, Display};
use std::{fmt, mem};
use url::Url;
use uuid::Uuid;
@ -38,6 +38,30 @@ try_from_impl_for_mbid!(&str);
try_from_impl_for_mbid!(&String);
try_from_impl_for_mbid!(String);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MbRefOption<T> {
Some(T),
CannotHaveMbid,
None,
}
impl<T> MbRefOption<T> {
pub fn or(self, optb: MbRefOption<T>) -> MbRefOption<T> {
match self {
x @ MbRefOption::Some(_) => x,
MbRefOption::CannotHaveMbid | MbRefOption::None => optb,
}
}
pub fn replace(&mut self, value: T) -> MbRefOption<T> {
mem::replace(self, MbRefOption::Some(value))
}
pub fn take(&mut self) -> MbRefOption<T> {
mem::replace(self, MbRefOption::None)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct MusicBrainzRef {
mbid: Mbid,
@ -142,7 +166,7 @@ impl MusicBrainzRef {
MusicBrainzRef { mbid, url }
}
fn invalid_url_error<U: Display>(url: U, entity: &'static str) -> Error {
fn invalid_url_error<U: fmt::Display>(url: U, entity: &'static str) -> Error {
Error::UrlError(format!("invalid {entity} MusicBrainz URL: {url}"))
}
}

View File

@ -297,11 +297,14 @@ impl<Database: IDatabase, Library> MusicHoard<Database, Library> {
mod tests {
use mockall::{predicate, Sequence};
use crate::core::{
use crate::{
collection::musicbrainz::MbRefOption,
core::{
collection::{album::AlbumDate, artist::ArtistId},
interface::database::{self, MockIDatabase},
musichoard::{base::IMusicHoardBase, NoLibrary},
testmod::FULL_COLLECTION,
},
};
use super::*;
@ -441,7 +444,7 @@ mod tests {
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
let mut expected: Option<MbArtistRef> = None;
let mut expected: MbRefOption<MbArtistRef> = MbRefOption::None;
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
// Setting a URL on an artist not in the collection is an error.

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use crate::core::collection::{
album::{Album, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSeq},
artist::{Artist, ArtistId, ArtistMeta},
musicbrainz::{MbAlbumRef, MbArtistRef},
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption},
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
};
use crate::testmod::*;

View File

@ -1,10 +1,10 @@
pub static DATABASE_JSON: &str = "{\
\"V20240828\":\
\"V20240924\":\
[\
{\
\"name\":\"Album_Artist A\",\
\"sort\":null,\
\"musicbrainz\":\"00000000-0000-0000-0000-000000000000\",\
\"musicbrainz\":{\"Some\":\"00000000-0000-0000-0000-000000000000\"},\
\"properties\":{\
\"MusicButler\":[\"https://www.musicbutler.io/artist-page/000000000\"],\
\"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums\"]\
@ -12,11 +12,11 @@ pub static DATABASE_JSON: &str = "{\
\"albums\":[\
{\
\"title\":\"album_title a.a\",\"seq\":1,\
\"musicbrainz\":\"00000000-0000-0000-0000-000000000000\",\
\"musicbrainz\":{\"Some\":\"00000000-0000-0000-0000-000000000000\"},\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\
\"title\":\"album_title a.b\",\"seq\":1,\"musicbrainz\":null,\
\"title\":\"album_title a.b\",\"seq\":1,\"musicbrainz\":\"None\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
}\
]\
@ -24,7 +24,7 @@ pub static DATABASE_JSON: &str = "{\
{\
\"name\":\"Album_Artist B\",\
\"sort\":null,\
\"musicbrainz\":\"11111111-1111-1111-1111-111111111111\",\
\"musicbrainz\":{\"Some\":\"11111111-1111-1111-1111-111111111111\"},\
\"properties\":{\
\"Bandcamp\":[\"https://artist-b.bandcamp.com/\"],\
\"MusicButler\":[\
@ -35,21 +35,21 @@ pub static DATABASE_JSON: &str = "{\
},\
\"albums\":[\
{\
\"title\":\"album_title b.a\",\"seq\":1,\"musicbrainz\":null,\
\"title\":\"album_title b.a\",\"seq\":1,\"musicbrainz\":\"None\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\
\"title\":\"album_title b.b\",\"seq\":3,\
\"musicbrainz\":\"11111111-1111-1111-1111-111111111111\",\
\"musicbrainz\":{\"Some\":\"11111111-1111-1111-1111-111111111111\"},\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\
\"title\":\"album_title b.c\",\"seq\":2,\
\"musicbrainz\":\"11111111-1111-1111-1111-111111111112\",\
\"musicbrainz\":{\"Some\":\"11111111-1111-1111-1111-111111111112\"},\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\
\"title\":\"album_title b.d\",\"seq\":4,\"musicbrainz\":null,\
\"title\":\"album_title b.d\",\"seq\":4,\"musicbrainz\":\"None\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
}\
]\
@ -57,15 +57,15 @@ pub static DATABASE_JSON: &str = "{\
{\
\"name\":\"The Album_Artist C\",\
\"sort\":\"Album_Artist C, The\",\
\"musicbrainz\":\"11111111-1111-1111-1111-111111111111\",\
\"musicbrainz\":\"CannotHaveMbid\",\
\"properties\":{},\
\"albums\":[\
{\
\"title\":\"album_title c.a\",\"seq\":0,\"musicbrainz\":null,\
\"title\":\"album_title c.a\",\"seq\":0,\"musicbrainz\":\"None\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\
\"title\":\"album_title c.b\",\"seq\":0,\"musicbrainz\":null,\
\"title\":\"album_title c.b\",\"seq\":0,\"musicbrainz\":\"None\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
}\
]\
@ -73,15 +73,15 @@ pub static DATABASE_JSON: &str = "{\
{\
\"name\":\"Album_Artist D\",\
\"sort\":null,\
\"musicbrainz\":null,\
\"musicbrainz\":\"None\",\
\"properties\":{},\
\"albums\":[\
{\
\"title\":\"album_title d.a\",\"seq\":0,\"musicbrainz\":null,\
\"title\":\"album_title d.a\",\"seq\":0,\"musicbrainz\":\"None\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\
\"title\":\"album_title d.b\",\"seq\":0,\"musicbrainz\":null,\
\"title\":\"album_title d.b\",\"seq\":0,\"musicbrainz\":\"None\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
}\
]\

View File

@ -1,6 +1,17 @@
use serde::{Deserialize, Serialize};
use crate::core::collection::album::{AlbumPrimaryType, AlbumSecondaryType};
use crate::{
collection::musicbrainz::MbRefOption,
core::collection::album::{AlbumPrimaryType, AlbumSecondaryType},
};
#[derive(Debug, Deserialize, Serialize)]
#[serde(remote = "MbRefOption")]
pub enum MbRefOptionDef<T> {
Some(T),
CannotHaveMbid,
None,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(remote = "AlbumPrimaryType")]

View File

@ -3,7 +3,11 @@ use std::{collections::HashMap, fmt};
use serde::{de::Visitor, Deserialize, Deserializer};
use crate::{
collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid},
collection::{
album::AlbumMeta,
artist::ArtistMeta,
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption, Mbid},
},
core::collection::{
album::{Album, AlbumDate, AlbumId, AlbumSeq},
artist::{Artist, ArtistId},
@ -12,15 +16,17 @@ use crate::{
external::database::serde::common::{SerdeAlbumPrimaryType, SerdeAlbumSecondaryType},
};
use super::common::MbRefOptionDef;
#[derive(Debug, Deserialize)]
pub enum DeserializeDatabase {
V20240828(Vec<DeserializeArtist>),
V20240924(Vec<DeserializeArtist>),
}
impl From<DeserializeDatabase> for Collection {
fn from(database: DeserializeDatabase) -> Self {
match database {
DeserializeDatabase::V20240828(collection) => {
DeserializeDatabase::V20240924(collection) => {
collection.into_iter().map(Into::into).collect()
}
}
@ -31,7 +37,7 @@ impl From<DeserializeDatabase> for Collection {
pub struct DeserializeArtist {
name: String,
sort: Option<String>,
musicbrainz: Option<DeserializeMbid>,
musicbrainz: DeserializeMbRefOption,
properties: HashMap<String, Vec<String>>,
albums: Vec<DeserializeAlbum>,
}
@ -40,14 +46,34 @@ pub struct DeserializeArtist {
pub struct DeserializeAlbum {
title: String,
seq: u8,
musicbrainz: Option<DeserializeMbid>,
musicbrainz: DeserializeMbRefOption,
primary_type: Option<SerdeAlbumPrimaryType>,
secondary_types: Vec<SerdeAlbumSecondaryType>,
}
#[derive(Debug, Deserialize)]
pub struct DeserializeMbRefOption(#[serde(with = "MbRefOptionDef")] MbRefOption<DeserializeMbid>);
#[derive(Clone, Debug)]
pub struct DeserializeMbid(Mbid);
macro_rules! impl_from_for_mb_ref_option {
($ref:ty) => {
impl From<DeserializeMbRefOption> for MbRefOption<$ref> {
fn from(value: DeserializeMbRefOption) -> Self {
match value.0 {
MbRefOption::Some(val) => MbRefOption::Some(val.0.into()),
MbRefOption::CannotHaveMbid => MbRefOption::CannotHaveMbid,
MbRefOption::None => MbRefOption::None,
}
}
}
};
}
impl_from_for_mb_ref_option!(MbArtistRef);
impl_from_for_mb_ref_option!(MbAlbumRef);
impl From<DeserializeMbid> for Mbid {
fn from(value: DeserializeMbid) -> Self {
value.0
@ -89,7 +115,7 @@ impl From<DeserializeArtist> for Artist {
meta: ArtistMeta {
id: ArtistId::new(artist.name),
sort: artist.sort.map(ArtistId::new),
musicbrainz: artist.musicbrainz.map(Into::<Mbid>::into).map(Into::into),
musicbrainz: artist.musicbrainz.into(),
properties: artist.properties,
},
albums: artist.albums.into_iter().map(Into::into).collect(),
@ -104,7 +130,7 @@ impl From<DeserializeAlbum> for Album {
id: AlbumId { title: album.title },
date: AlbumDate::default(),
seq: AlbumSeq(album.seq),
musicbrainz: album.musicbrainz.map(Into::<Mbid>::into).map(Into::into),
musicbrainz: album.musicbrainz.into(),
primary_type: album.primary_type.map(Into::into),
secondary_types: album.secondary_types.into_iter().map(Into::into).collect(),
},

View File

@ -3,19 +3,21 @@ use std::collections::BTreeMap;
use serde::Serialize;
use crate::{
collection::musicbrainz::Mbid,
collection::musicbrainz::{MbRefOption, Mbid},
core::collection::{album::Album, artist::Artist, musicbrainz::IMusicBrainzRef, Collection},
external::database::serde::common::{SerdeAlbumPrimaryType, SerdeAlbumSecondaryType},
external::database::serde::common::{
MbRefOptionDef, SerdeAlbumPrimaryType, SerdeAlbumSecondaryType,
},
};
#[derive(Debug, Serialize)]
pub enum SerializeDatabase<'a> {
V20240828(Vec<SerializeArtist<'a>>),
V20240924(Vec<SerializeArtist<'a>>),
}
impl<'a> From<&'a Collection> for SerializeDatabase<'a> {
fn from(collection: &'a Collection) -> Self {
SerializeDatabase::V20240828(collection.iter().map(Into::into).collect())
SerializeDatabase::V20240924(collection.iter().map(Into::into).collect())
}
}
@ -23,7 +25,7 @@ impl<'a> From<&'a Collection> for SerializeDatabase<'a> {
pub struct SerializeArtist<'a> {
name: &'a str,
sort: Option<&'a str>,
musicbrainz: Option<SerializeMbid<'a>>,
musicbrainz: SerializeMbRefOption<'a>,
properties: BTreeMap<&'a str, &'a Vec<String>>,
albums: Vec<SerializeAlbum<'a>>,
}
@ -32,14 +34,31 @@ pub struct SerializeArtist<'a> {
pub struct SerializeAlbum<'a> {
title: &'a str,
seq: u8,
musicbrainz: Option<SerializeMbid<'a>>,
musicbrainz: SerializeMbRefOption<'a>,
primary_type: Option<SerdeAlbumPrimaryType>,
secondary_types: Vec<SerdeAlbumSecondaryType>,
}
#[derive(Debug, Serialize)]
pub struct SerializeMbRefOption<'a>(
#[serde(with = "MbRefOptionDef")] MbRefOption<SerializeMbid<'a>>,
);
#[derive(Clone, Debug)]
pub struct SerializeMbid<'a>(&'a Mbid);
impl<'a, T: IMusicBrainzRef> From<&'a MbRefOption<T>> for SerializeMbRefOption<'a> {
fn from(value: &'a MbRefOption<T>) -> Self {
match value {
MbRefOption::Some(val) => {
SerializeMbRefOption(MbRefOption::Some(SerializeMbid(val.mbid())))
}
MbRefOption::CannotHaveMbid => SerializeMbRefOption(MbRefOption::CannotHaveMbid),
MbRefOption::None => SerializeMbRefOption(MbRefOption::None),
}
}
}
impl<'a> Serialize for SerializeMbid<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -54,11 +73,7 @@ impl<'a> From<&'a Artist> for SerializeArtist<'a> {
SerializeArtist {
name: &artist.meta.id.name,
sort: artist.meta.sort.as_ref().map(|id| id.name.as_ref()),
musicbrainz: artist
.meta
.musicbrainz
.as_ref()
.map(|mbref| SerializeMbid(mbref.mbid())),
musicbrainz: (&artist.meta.musicbrainz).into(),
properties: artist
.meta
.properties
@ -75,11 +90,7 @@ impl<'a> From<&'a Album> for SerializeAlbum<'a> {
SerializeAlbum {
title: &album.meta.id.title,
seq: album.meta.seq.0,
musicbrainz: album
.meta
.musicbrainz
.as_ref()
.map(|mbref| SerializeMbid(mbref.mbid())),
musicbrainz: (&album.meta.musicbrainz).into(),
primary_type: album.meta.primary_type.map(Into::into),
secondary_types: album
.meta

View File

@ -7,7 +7,7 @@ macro_rules! full_collection {
name: "Album_Artist A".to_string(),
},
sort: None,
musicbrainz: Some(MbArtistRef::from_url_str(
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000"
).unwrap()),
properties: HashMap::from([
@ -29,7 +29,7 @@ macro_rules! full_collection {
},
date: 1998.into(),
seq: AlbumSeq(1),
musicbrainz: Some(MbAlbumRef::from_url_str(
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000"
).unwrap()),
primary_type: Some(AlbumPrimaryType::Album),
@ -92,7 +92,7 @@ macro_rules! full_collection {
},
date: (2015, 4).into(),
seq: AlbumSeq(1),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -129,7 +129,7 @@ macro_rules! full_collection {
name: "Album_Artist B".to_string(),
},
sort: None,
musicbrainz: Some(MbArtistRef::from_url_str(
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111"
).unwrap()),
properties: HashMap::from([
@ -155,7 +155,7 @@ macro_rules! full_collection {
},
date: (2003, 6, 6).into(),
seq: AlbumSeq(1),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -194,7 +194,7 @@ macro_rules! full_collection {
},
date: 2008.into(),
seq: AlbumSeq(3),
musicbrainz: Some(MbAlbumRef::from_url_str(
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111"
).unwrap()),
primary_type: Some(AlbumPrimaryType::Album),
@ -235,7 +235,7 @@ macro_rules! full_collection {
},
date: 2009.into(),
seq: AlbumSeq(2),
musicbrainz: Some(MbAlbumRef::from_url_str(
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112"
).unwrap()),
primary_type: Some(AlbumPrimaryType::Album),
@ -276,7 +276,7 @@ macro_rules! full_collection {
},
date: 2015.into(),
seq: AlbumSeq(4),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -318,9 +318,7 @@ macro_rules! full_collection {
sort: Some(ArtistId {
name: "Album_Artist C, The".to_string(),
}),
musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111"
).unwrap()),
musicbrainz: MbRefOption::CannotHaveMbid,
properties: HashMap::new(),
},
albums: vec![
@ -331,7 +329,7 @@ macro_rules! full_collection {
},
date: 1985.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -370,7 +368,7 @@ macro_rules! full_collection {
},
date: 2018.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -410,7 +408,7 @@ macro_rules! full_collection {
name: "Album_Artist D".to_string(),
},
sort: None,
musicbrainz: None,
musicbrainz: MbRefOption::None,
properties: HashMap::new(),
},
albums: vec![
@ -421,7 +419,7 @@ macro_rules! full_collection {
},
date: 1995.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -460,7 +458,7 @@ macro_rules! full_collection {
},
date: 2028.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},

View File

@ -8,7 +8,7 @@ macro_rules! library_collection {
name: "Album_Artist A".to_string(),
},
sort: None,
musicbrainz: None,
musicbrainz: MbRefOption::None,
properties: HashMap::new(),
},
albums: vec![
@ -19,7 +19,7 @@ macro_rules! library_collection {
},
date: 1998.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -80,7 +80,7 @@ macro_rules! library_collection {
},
date: (2015, 4).into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -117,7 +117,7 @@ macro_rules! library_collection {
name: "Album_Artist B".to_string(),
},
sort: None,
musicbrainz: None,
musicbrainz: MbRefOption::None,
properties: HashMap::new(),
},
albums: vec![
@ -128,7 +128,7 @@ macro_rules! library_collection {
},
date: (2003, 6, 6).into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -167,7 +167,7 @@ macro_rules! library_collection {
},
date: 2008.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -206,7 +206,7 @@ macro_rules! library_collection {
},
date: 2009.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -245,7 +245,7 @@ macro_rules! library_collection {
},
date: 2015.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -287,7 +287,7 @@ macro_rules! library_collection {
sort: Some(ArtistId {
name: "Album_Artist C, The".to_string(),
}),
musicbrainz: None,
musicbrainz: MbRefOption::None,
properties: HashMap::new(),
},
albums: vec![
@ -298,7 +298,7 @@ macro_rules! library_collection {
},
date: 1985.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -337,7 +337,7 @@ macro_rules! library_collection {
},
date: 2018.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -377,7 +377,7 @@ macro_rules! library_collection {
name: "Album_Artist D".to_string(),
},
sort: None,
musicbrainz: None,
musicbrainz: MbRefOption::None,
properties: HashMap::new(),
},
albums: vec![
@ -388,7 +388,7 @@ macro_rules! library_collection {
},
date: 1995.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -427,7 +427,7 @@ macro_rules! library_collection {
},
date: 2028.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},

View File

@ -6,7 +6,7 @@ use std::{
use musichoard::collection::{
album::AlbumMeta,
artist::{Artist, ArtistMeta},
musicbrainz::{IMusicBrainzRef, Mbid},
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
};
use crate::tui::{
@ -35,9 +35,8 @@ impl FetchState {
fn try_recv(&mut self) -> Result<MbApiResult, TryRecvError> {
if let Some(lookup_rx) = &self.lookup_rx {
let result = lookup_rx.try_recv();
match result {
Ok(_) | Err(TryRecvError::Empty) => return result,
match lookup_rx.try_recv() {
x @ Ok(_) | x @ Err(TryRecvError::Empty) => return x,
Err(TryRecvError::Disconnected) => {
self.lookup_rx.take();
}
@ -142,15 +141,16 @@ impl AppMachine<FetchState> {
artist: &Artist,
) -> Result<(), DaemonError> {
let requests = match artist.meta.musicbrainz {
Some(ref arid) => {
MbRefOption::Some(ref arid) => {
let arid = arid.mbid();
let albums = artist.albums.iter();
albums
.filter(|album| album.meta.musicbrainz.is_none())
.filter(|album| matches!(album.meta.musicbrainz, MbRefOption::None))
.map(|album| MbParams::search_release_group(arid.clone(), album.meta.clone()))
.collect()
}
None => VecDeque::from([MbParams::search_artist(artist.meta.clone())]),
MbRefOption::CannotHaveMbid => return Ok(()),
MbRefOption::None => VecDeque::from([MbParams::search_artist(artist.meta.clone())]),
};
musicbrainz.submit_background_job(result_sender, requests)
}
@ -379,6 +379,20 @@ mod tests {
AppMachine::app_lookup_artist(inner, fetch, &artist, mbid());
}
#[test]
fn fetch_artist_cannot_have_mbid() {
let music_hoard = music_hoard(COLLECTION.to_owned());
let inner = inner(music_hoard);
// Use third artist to match the expectation.
let browse = AppMachine::browse_state(inner);
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
let app = browse.increment_selection(Delta::Line);
let app = app.unwrap_browse().fetch_musicbrainz();
assert!(matches!(app, AppState::Match(_)));
}
#[test]
fn fetch_artist_job_sender_err() {
let mut mb_job_sender = MockIMbJobSender::new();

View File

@ -6,7 +6,7 @@ use musichoard::{
collection::{
album::{AlbumDate, AlbumMeta, AlbumSeq},
artist::{ArtistId, ArtistMeta},
musicbrainz::Mbid,
musicbrainz::{MbRefOption, Mbid},
},
external::musicbrainz::{
api::{
@ -100,7 +100,7 @@ fn from_lookup_artist_response(entity: LookupArtistResponse) -> Lookup<ArtistMet
item: ArtistMeta {
id: entity.meta.name,
sort,
musicbrainz: Some(entity.meta.id.into()),
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
properties: HashMap::new(),
},
disambiguation: entity.meta.disambiguation,
@ -113,7 +113,7 @@ fn from_lookup_release_group_response(entity: LookupReleaseGroupResponse) -> Loo
id: entity.meta.title,
date: entity.meta.first_release_date,
seq: AlbumSeq::default(),
musicbrainz: Some(entity.meta.id.into()),
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
primary_type: Some(entity.meta.primary_type),
secondary_types: entity.meta.secondary_types.unwrap_or_default(),
},
@ -130,7 +130,7 @@ fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Mat
item: ArtistMeta {
id: entity.meta.name,
sort,
musicbrainz: Some(entity.meta.id.into()),
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
properties: HashMap::new(),
},
disambiguation: entity.meta.disambiguation,
@ -146,7 +146,7 @@ fn from_search_release_group_response_release_group(
id: entity.meta.title,
date: entity.meta.first_release_date,
seq: AlbumSeq::default(),
musicbrainz: Some(entity.meta.id.into()),
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
primary_type: Some(entity.meta.primary_type),
secondary_types: entity.meta.secondary_types.unwrap_or_default(),
},

View File

@ -316,7 +316,7 @@ mod tests {
use musichoard::collection::{
album::AlbumMeta,
artist::ArtistMeta,
musicbrainz::{IMusicBrainzRef, Mbid},
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
};
use crate::tui::{
@ -327,6 +327,21 @@ mod tests {
use super::*;
fn mb_ref_opt_unwrap<T>(opt: MbRefOption<T>) -> T {
match opt {
MbRefOption::Some(val) => val,
_ => panic!(),
}
}
fn mb_ref_opt_as_ref<T>(opt: &MbRefOption<T>) -> MbRefOption<&T> {
match *opt {
MbRefOption::Some(ref x) => MbRefOption::Some(x),
MbRefOption::CannotHaveMbid => MbRefOption::CannotHaveMbid,
MbRefOption::None => MbRefOption::None,
}
}
fn musicbrainz() -> MockIMusicBrainz {
MockIMusicBrainz::new()
}
@ -403,8 +418,8 @@ mod tests {
}
fn search_albums_requests() -> VecDeque<MbParams> {
let mbref = COLLECTION[1].meta.musicbrainz.as_ref();
let arid = mbref.unwrap().mbid().clone();
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.musicbrainz);
let arid = mb_ref_opt_unwrap(mbref).mbid().clone();
let album_1 = COLLECTION[1].albums[0].meta.clone();
let album_4 = COLLECTION[1].albums[3].meta.clone();
@ -416,8 +431,8 @@ mod tests {
}
fn album_arid_expectation() -> Mbid {
let mbref = COLLECTION[1].meta.musicbrainz.as_ref();
mbref.unwrap().mbid().clone()
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.musicbrainz);
mb_ref_opt_unwrap(mbref).mbid().clone()
}
fn search_album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use musichoard::collection::{
album::{Album, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSeq},
artist::{Artist, ArtistId, ArtistMeta},
musicbrainz::{MbAlbumRef, MbArtistRef},
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption},
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
};
use once_cell::sync::Lazy;

View File

@ -1,6 +1,7 @@
use musichoard::collection::{
album::{AlbumDate, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq, AlbumStatus},
artist::ArtistMeta,
musicbrainz::{IMusicBrainzRef, MbRefOption},
track::{TrackFormat, TrackQuality},
};
@ -30,6 +31,14 @@ impl UiDisplay {
}
}
pub fn display_mb_ref_option_as_url<T: IMusicBrainzRef>(option: &MbRefOption<T>) -> &str {
match option {
MbRefOption::Some(val) => val.url().as_str(),
MbRefOption::CannotHaveMbid => "cannot have a MusicBrainz identifier",
MbRefOption::None => "",
}
}
pub fn display_type(
primary: &Option<AlbumPrimaryType>,
secondary: &Vec<AlbumSecondaryType>,

View File

@ -1,8 +1,10 @@
use std::collections::HashMap;
use musichoard::collection::{album::Album, artist::Artist, musicbrainz::IMusicBrainzRef};
use musichoard::collection::{album::Album, artist::Artist};
use ratatui::widgets::{ListState, Paragraph};
use super::display::UiDisplay;
struct InfoOverlay;
impl InfoOverlay {
@ -74,8 +76,8 @@ impl<'a> ArtistOverlay<'a> {
Properties: {}",
artist.map(|a| a.meta.id.name.as_str()).unwrap_or(""),
artist
.and_then(|a| a.meta.musicbrainz.as_ref().map(|mb| mb.url().as_str()))
.unwrap_or(""),
.map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.musicbrainz))
.unwrap_or_default(),
Self::opt_hashmap_to_string(
artist.map(|a| &a.meta.properties),
&double_item_indent,
@ -102,8 +104,8 @@ impl<'a> AlbumOverlay<'a> {
MusicBrainz: {}",
album.map(|a| a.meta.id.title.as_str()).unwrap_or(""),
album
.and_then(|a| a.meta.musicbrainz.as_ref().map(|mb| mb.url().as_str()))
.unwrap_or(""),
.map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.musicbrainz))
.unwrap_or_default(),
));
AlbumOverlay { properties }

View File

@ -305,7 +305,15 @@ mod tests {
draw_test_suite(artists, &mut selection);
// Change the artist (which cannot have a MBID).
selection.increment_selection(artists, Delta::Line);
selection.increment_selection(artists, Delta::Line);
draw_test_suite(artists, &mut selection);
// Change the track (which has a different track format).
selection.decrement_selection(artists, Delta::Line);
selection.decrement_selection(artists, Delta::Line);
selection.increment_category();
selection.increment_category();
selection.increment_selection(artists, Delta::Line);

View File

@ -1 +1 @@
{"V20240828":[{"name":"Аркона","sort":"Arkona","musicbrainz":"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,"primary_type":"Album","secondary_types":[]}]},{"name":"Eluveitie","sort":null,"musicbrainz":"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 [rerecorded]","seq":0,"musicbrainz":null,"primary_type":"Ep","secondary_types":[]},{"title":"Slania","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":[]}]},{"name":"Frontside","sort":null,"musicbrainz":"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,"primary_type":"Album","secondary_types":[]}]},{"name":"Heavens Basement","sort":"Heavens Basement","musicbrainz":"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,"primary_type":null,"secondary_types":[]},{"title":"Unbreakable","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":[]}]},{"name":"Metallica","sort":null,"musicbrainz":"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,"primary_type":"Album","secondary_types":[]},{"title":"S&M","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":["Live"]}]}]}
{"V20240924":[{"name":"Аркона","sort":"Arkona","musicbrainz":{"Some":"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":"None","primary_type":"Album","secondary_types":[]}]},{"name":"Eluveitie","sort":null,"musicbrainz":{"Some":"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 [rerecorded]","seq":0,"musicbrainz":"None","primary_type":"Ep","secondary_types":[]},{"title":"Slania","seq":0,"musicbrainz":"None","primary_type":"Album","secondary_types":[]}]},{"name":"Frontside","sort":null,"musicbrainz":{"Some":"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":"None","primary_type":"Album","secondary_types":[]}]},{"name":"Heavens Basement","sort":"Heavens Basement","musicbrainz":{"Some":"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":"None","primary_type":null,"secondary_types":[]},{"title":"Unbreakable","seq":0,"musicbrainz":"None","primary_type":"Album","secondary_types":[]}]},{"name":"Metallica","sort":null,"musicbrainz":{"Some":"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":"None","primary_type":"Album","secondary_types":[]},{"title":"S&M","seq":0,"musicbrainz":"None","primary_type":"Album","secondary_types":["Live"]}]}]}

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use musichoard::collection::{
album::{Album, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq},
artist::{Artist, ArtistId, ArtistMeta},
musicbrainz::MbArtistRef,
musicbrainz::{MbArtistRef, MbRefOption},
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
Collection,
};
@ -19,7 +19,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
sort: Some(ArtistId{
name: String::from("Arkona")
}),
musicbrainz: Some(MbArtistRef::from_url_str(
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212"
).unwrap()),
properties: HashMap::from([
@ -41,7 +41,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
},
date: 2011.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -209,7 +209,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
name: String::from("Eluveitie"),
},
sort: None,
musicbrainz: Some(MbArtistRef::from_url_str(
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38"
).unwrap()),
properties: HashMap::from([
@ -229,7 +229,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
},
date: 2004.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Ep),
secondary_types: vec![],
},
@ -309,7 +309,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
},
date: 2008.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -456,7 +456,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
name: String::from("Frontside"),
},
sort: None,
musicbrainz: Some(MbArtistRef::from_url_str(
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490"
).unwrap()),
properties: HashMap::from([
@ -475,7 +475,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
},
date: 2001.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -612,7 +612,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
sort: Some(ArtistId {
name: String::from("Heavens Basement"),
}),
musicbrainz: Some(MbArtistRef::from_url_str(
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc"
).unwrap()),
properties: HashMap::from([
@ -631,7 +631,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
},
date: 2011.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: None,
secondary_types: vec![],
},
@ -655,7 +655,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
},
date: 2011.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -746,7 +746,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
name: String::from("Metallica"),
},
sort: None,
musicbrainz: Some(MbArtistRef::from_url_str(
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab"
).unwrap()),
properties: HashMap::from([
@ -766,7 +766,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
},
date: 1984.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
},
@ -868,7 +868,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
},
date: 1999.into(),
seq: AlbumSeq(0),
musicbrainz: None,
musicbrainz: MbRefOption::None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![AlbumSecondaryType::Live],
},