diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index da49982..66c45e6 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -26,9 +26,8 @@ pub struct AlbumMeta { } /// Album non-identifier metadata. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct AlbumInfo { - pub musicbrainz: MbRefOption, pub primary_type: Option, pub secondary_types: Vec, } @@ -38,15 +37,7 @@ pub struct AlbumInfo { pub struct AlbumId { pub title: String, pub lib_id: AlbumLibId, -} - -impl AlbumId { - pub fn compatible(&self, other: &AlbumId) -> bool { - let titles_compatible = self.title == other.title; - let lib_id_compatible = - self.lib_id.is_none() || other.lib_id.is_none() || (self.lib_id == other.lib_id); - titles_compatible && lib_id_compatible - } + pub db_id: AlbumDbId, } /// Unique library identifier. @@ -63,6 +54,9 @@ impl AlbumLibId { } } +/// Unique database identifier. Use MBID for this purpose. +pub type AlbumDbId = MbRefOption; + // There are crates for handling dates, but we don't need much complexity beyond year-month-day. /// The album's release date. #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] @@ -221,6 +215,14 @@ impl AlbumMeta { (&self.date, &self.seq, &self.id) } + pub fn set_db_id(&mut self, db_id: AlbumDbId) { + self.id.set_db_id(db_id); + } + + pub fn clear_db_id(&mut self) { + self.id.clear_db_id(); + } + pub fn set_seq(&mut self, seq: AlbumSeq) { self.seq = seq; } @@ -230,24 +232,12 @@ impl AlbumMeta { } } -impl Default for AlbumInfo { - fn default() -> Self { - AlbumInfo { - musicbrainz: MbRefOption::None, - primary_type: None, - secondary_types: Vec::new(), - } - } -} - impl AlbumInfo { pub fn new( - musicbrainz: MbRefOption, primary_type: Option, secondary_types: Vec, ) -> Self { AlbumInfo { - musicbrainz, primary_type, secondary_types, } @@ -269,6 +259,7 @@ impl Ord for AlbumMeta { impl Merge for AlbumMeta { fn merge_in_place(&mut self, other: Self) { assert!(self.id.compatible(&other.id)); + self.id.db_id = self.id.db_id.take().or(other.id.db_id); self.seq = std::cmp::max(self.seq, other.seq); self.info.merge_in_place(other.info); } @@ -276,7 +267,6 @@ impl Merge for AlbumMeta { impl Merge for AlbumInfo { fn merge_in_place(&mut self, other: Self) { - self.musicbrainz = self.musicbrainz.take().or(other.musicbrainz); self.primary_type = self.primary_type.take().or(other.primary_type); if self.secondary_types.is_empty() { self.secondary_types = other.secondary_types; @@ -301,8 +291,31 @@ impl AlbumId { AlbumId { title: name.into(), lib_id: AlbumLibId::None, + db_id: AlbumDbId::None, } } + + pub fn with_db_id(mut self, db_id: AlbumDbId) -> Self { + self.db_id = db_id; + self + } + + pub fn set_db_id(&mut self, db_id: AlbumDbId) { + self.db_id = db_id; + } + + pub fn clear_db_id(&mut self) { + self.db_id = AlbumDbId::None; + } + + pub fn compatible(&self, other: &AlbumId) -> bool { + let titles_compatible = self.title == other.title; + let lib_id_compatible = + self.lib_id.is_none() || other.lib_id.is_none() || (self.lib_id == other.lib_id); + let db_id_compatible = + self.db_id.is_none() || other.db_id.is_none() || (self.db_id == other.db_id); + titles_compatible && lib_id_compatible && db_id_compatible + } } impl Display for AlbumId { diff --git a/src/core/collection/musicbrainz.rs b/src/core/collection/musicbrainz.rs index 3b24af5..092144f 100644 --- a/src/core/collection/musicbrainz.rs +++ b/src/core/collection/musicbrainz.rs @@ -7,7 +7,7 @@ use crate::core::collection::Error; const MB_DOMAIN: &str = "musicbrainz.org"; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Mbid(Uuid); impl Mbid { @@ -38,7 +38,7 @@ try_from_impl_for_mbid!(&str); try_from_impl_for_mbid!(&String); try_from_impl_for_mbid!(String); -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum MbRefOption { Some(T), CannotHaveMbid, @@ -70,7 +70,7 @@ impl MbRefOption { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] struct MusicBrainzRef { mbid: Mbid, url: Url, @@ -82,10 +82,10 @@ pub trait IMusicBrainzRef { fn entity() -> &'static str; } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MbArtistRef(MusicBrainzRef); -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MbAlbumRef(MusicBrainzRef); macro_rules! impl_imusicbrainzref { diff --git a/src/core/musichoard/database.rs b/src/core/musichoard/database.rs index 21837d1..6f7c563 100644 --- a/src/core/musichoard/database.rs +++ b/src/core/musichoard/database.rs @@ -2,7 +2,7 @@ use std::mem; use crate::{ collection::{ - album::{AlbumInfo, AlbumMeta}, + album::{AlbumDbId, AlbumInfo, AlbumMeta}, artist::ArtistInfo, merge::Merge, }, @@ -72,6 +72,17 @@ pub trait IMusicHoardDatabase { album_id: AlbumIdRef, ) -> Result<(), Error>; + fn set_album_db_id, AlbumIdRef: AsRef>( + &mut self, + artist_id: ArtistIdRef, + album_id: AlbumIdRef, + db_id: AlbumDbId, + ) -> Result<(), Error>; + fn clear_album_db_id, AlbumIdRef: AsRef>( + &mut self, + artist_id: ArtistIdRef, + album_id: AlbumIdRef, + ) -> Result<(), Error>; fn set_album_seq, AlbumIdRef: AsRef>( &mut self, artist_id: ArtistIdRef, @@ -242,6 +253,33 @@ impl IMusicHoardDatabase for MusicHoard, AlbumIdRef: AsRef>( + &mut self, + artist_id: ArtistIdRef, + album_id: AlbumIdRef, + db_id: AlbumDbId, + ) -> Result<(), Error> { + self.update_album_and( + artist_id.as_ref(), + album_id.as_ref(), + |album| album.meta.set_db_id(db_id), + |artist| artist.albums.sort_unstable(), + ) + } + + fn clear_album_db_id, AlbumIdRef: AsRef>( + &mut self, + artist_id: ArtistIdRef, + album_id: AlbumIdRef, + ) -> Result<(), Error> { + self.update_album_and( + artist_id.as_ref(), + album_id.as_ref(), + |album| album.meta.clear_db_id(), + |artist| artist.albums.sort_unstable(), + ) + } + fn set_album_seq, AlbumIdRef: AsRef>( &mut self, artist_id: ArtistIdRef, @@ -708,6 +746,61 @@ mod tests { assert_eq!(music_hoard.collection, collection); } + #[test] + fn set_clear_album_db_id() { + let mut database = MockIDatabase::new(); + + let artist_id = ArtistId::new("an artist"); + let mut album_id = AlbumId::new("an album"); + let album_id_2 = AlbumId::new("another album"); + + let mut database_result = vec![Artist::new(artist_id.clone())]; + database_result[0].albums.push(Album::new(album_id.clone())); + + database + .expect_load() + .times(1) + .return_once(|| Ok(database_result)); + database.expect_save().times(2).returning(|_| Ok(())); + + let mut music_hoard = MusicHoard::database(database).unwrap(); + let album = &music_hoard.collection[0].albums[0]; + assert_eq!(album.meta.id.db_id, AlbumDbId::None); + + // Seting db_id on an album not belonging to the artist is an error. + assert!(music_hoard + .set_album_db_id(&artist_id, &album_id_2, AlbumDbId::CannotHaveMbid) + .is_err()); + let album = &music_hoard.collection[0].albums[0]; + assert_eq!(album.meta.id.db_id, AlbumDbId::None); + + // Set db_id. + assert!(music_hoard + .set_album_db_id(&artist_id, &album_id, AlbumDbId::CannotHaveMbid) + .is_ok()); + let album = &music_hoard.collection[0].albums[0]; + assert_eq!(album.meta.id.db_id, AlbumDbId::CannotHaveMbid); + + // Clearing db_id on an album that does not exist is an error. + assert!(music_hoard + .clear_album_db_id(&artist_id, &album_id_2) + .is_err()); + + // Clearing db_id from album without the db_id set is an error. Effectively the album does + // not exist. + assert!(music_hoard + .clear_album_db_id(&artist_id, &album_id) + .is_err()); + let album = &music_hoard.collection[0].albums[0]; + assert_eq!(album.meta.id.db_id, AlbumDbId::CannotHaveMbid); + + // To clear the db_id we need the album_id to have the db_id to identify the correct album. + album_id.set_db_id(AlbumDbId::CannotHaveMbid); + assert!(music_hoard.clear_album_db_id(&artist_id, &album_id).is_ok()); + let album = &music_hoard.collection[0].albums[0]; + assert_eq!(album.meta.id.db_id, AlbumDbId::None); + } + #[test] fn set_clear_album_seq() { let mut database = MockIDatabase::new(); @@ -767,12 +860,10 @@ mod tests { let mut music_hoard = MusicHoard::database(database).unwrap(); let meta = &music_hoard.collection[0].albums[0].meta; - assert_eq!(meta.info.musicbrainz, MbRefOption::None); assert_eq!(meta.info.primary_type, None); assert_eq!(meta.info.secondary_types, Vec::new()); let info = AlbumInfo::new( - MbRefOption::CannotHaveMbid, Some(AlbumPrimaryType::Album), vec![AlbumSecondaryType::Live], ); diff --git a/src/core/musichoard/library.rs b/src/core/musichoard/library.rs index 09fa249..fb7eed7 100644 --- a/src/core/musichoard/library.rs +++ b/src/core/musichoard/library.rs @@ -1,19 +1,22 @@ use std::collections::HashMap; -use crate::core::{ - collection::{ - album::{Album, AlbumDate, AlbumId}, - artist::{Artist, ArtistId}, - track::{Track, TrackId, TrackNum, TrackQuality}, - Collection, - }, - interface::{ - database::IDatabase, - library::{ILibrary, Item, Query}, - }, - musichoard::{ - base::IMusicHoardBasePrivate, database::IMusicHoardDatabasePrivate, Error, MusicHoard, - NoDatabase, +use crate::{ + collection::album::AlbumDbId, + core::{ + collection::{ + album::{Album, AlbumDate, AlbumId}, + artist::{Artist, ArtistId}, + track::{Track, TrackId, TrackNum, TrackQuality}, + Collection, + }, + interface::{ + database::IDatabase, + library::{ILibrary, Item, Query}, + }, + musichoard::{ + base::IMusicHoardBasePrivate, database::IMusicHoardDatabasePrivate, Error, MusicHoard, + NoDatabase, + }, }, }; @@ -57,6 +60,7 @@ impl MusicHoard { let album_id = AlbumId { title: item.album_title, lib_id: item.album_lib_id, + db_id: AlbumDbId::None, }; let album_date = AlbumDate { diff --git a/src/core/testmod.rs b/src/core/testmod.rs index 2ef6236..c44c7c1 100644 --- a/src/core/testmod.rs +++ b/src/core/testmod.rs @@ -2,7 +2,9 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use crate::core::collection::{ - album::{Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMeta, AlbumPrimaryType, AlbumSeq}, + album::{ + Album, AlbumDbId, AlbumId, AlbumInfo, AlbumLibId, AlbumMeta, AlbumPrimaryType, AlbumSeq, + }, artist::{Artist, ArtistId, ArtistInfo, ArtistMeta}, musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption}, track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, diff --git a/src/external/database/serde/deserialize.rs b/src/external/database/serde/deserialize.rs index 5cddf35..a57970b 100644 --- a/src/external/database/serde/deserialize.rs +++ b/src/external/database/serde/deserialize.rs @@ -142,11 +142,11 @@ impl From for Album { id: AlbumId { title: album.title, lib_id: album.lib_id.into(), + db_id: album.musicbrainz.into(), }, date: AlbumDate::default(), seq: AlbumSeq(album.seq), info: AlbumInfo { - musicbrainz: album.musicbrainz.into(), primary_type: album.primary_type.map(Into::into), secondary_types: album.secondary_types.into_iter().map(Into::into).collect(), }, diff --git a/src/external/database/serde/serialize.rs b/src/external/database/serde/serialize.rs index 4ffd96b..59275d2 100644 --- a/src/external/database/serde/serialize.rs +++ b/src/external/database/serde/serialize.rs @@ -105,7 +105,7 @@ impl<'a> From<&'a Album> for SerializeAlbum<'a> { title: &album.meta.id.title, lib_id: album.meta.id.lib_id.into(), seq: album.meta.seq.0, - musicbrainz: (&album.meta.info.musicbrainz).into(), + musicbrainz: (&album.meta.id.db_id).into(), primary_type: album.meta.info.primary_type.map(Into::into), secondary_types: album .meta diff --git a/src/testmod/full.rs b/src/testmod/full.rs index 87e1cbc..cbdbec3 100644 --- a/src/testmod/full.rs +++ b/src/testmod/full.rs @@ -29,13 +29,13 @@ macro_rules! full_collection { id: AlbumId { title: "album_title a.a".to_string(), lib_id: AlbumLibId::Value(1), + db_id: AlbumDbId::Some(MbAlbumRef::from_url_str( + "https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000" + ).unwrap()), }, date: 1998.into(), seq: AlbumSeq(1), info: AlbumInfo { - musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str( - "https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000" - ).unwrap()), primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -95,11 +95,11 @@ macro_rules! full_collection { id: AlbumId { title: "album_title a.b".to_string(), lib_id: AlbumLibId::Value(2), + db_id: AlbumDbId::None, }, date: (2015, 4).into(), seq: AlbumSeq(1), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -163,11 +163,11 @@ macro_rules! full_collection { id: AlbumId { title: "album_title b.a".to_string(), lib_id: AlbumLibId::Value(3), + db_id: AlbumDbId::None, }, date: (2003, 6, 6).into(), seq: AlbumSeq(1), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -205,13 +205,13 @@ macro_rules! full_collection { id: AlbumId { title: "album_title b.b".to_string(), lib_id: AlbumLibId::Value(4), + db_id: AlbumDbId::Some(MbAlbumRef::from_url_str( + "https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111" + ).unwrap()), }, date: 2008.into(), seq: AlbumSeq(3), info: AlbumInfo { - musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str( - "https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111" - ).unwrap()), primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -249,13 +249,13 @@ macro_rules! full_collection { id: AlbumId { title: "album_title b.c".to_string(), lib_id: AlbumLibId::Value(5), + db_id: AlbumDbId::Some(MbAlbumRef::from_url_str( + "https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112" + ).unwrap()), }, date: 2009.into(), seq: AlbumSeq(2), info: AlbumInfo { - musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str( - "https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112" - ).unwrap()), primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -293,11 +293,11 @@ macro_rules! full_collection { id: AlbumId { title: "album_title b.d".to_string(), lib_id: AlbumLibId::Value(6), + db_id: AlbumDbId::None, }, date: 2015.into(), seq: AlbumSeq(4), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -349,11 +349,11 @@ macro_rules! full_collection { id: AlbumId { title: "album_title c.a".to_string(), lib_id: AlbumLibId::Value(7), + db_id: AlbumDbId::None, }, date: 1985.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -391,11 +391,11 @@ macro_rules! full_collection { id: AlbumId { title: "album_title c.b".to_string(), lib_id: AlbumLibId::Value(8), + db_id: AlbumDbId::None, }, date: 2018.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -447,11 +447,11 @@ macro_rules! full_collection { id: AlbumId { title: "album_title d.a".to_string(), lib_id: AlbumLibId::Value(9), + db_id: AlbumDbId::None, }, date: 1995.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -489,11 +489,11 @@ macro_rules! full_collection { id: AlbumId { title: "album_title d.b".to_string(), lib_id: AlbumLibId::Value(10), + db_id: AlbumDbId::None, }, date: 2028.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, diff --git a/src/testmod/library.rs b/src/testmod/library.rs index a50b16c..cc326ce 100644 --- a/src/testmod/library.rs +++ b/src/testmod/library.rs @@ -19,6 +19,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title a.a".to_string(), lib_id: AlbumLibId::Value(1), + db_id: AlbumDbId::None, }, date: 1998.into(), seq: AlbumSeq(0), @@ -79,6 +80,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title a.b".to_string(), lib_id: AlbumLibId::Value(2), + db_id: AlbumDbId::None, }, date: (2015, 4).into(), seq: AlbumSeq(0), @@ -128,6 +130,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title b.a".to_string(), lib_id: AlbumLibId::Value(3), + db_id: AlbumDbId::None, }, date: (2003, 6, 6).into(), seq: AlbumSeq(0), @@ -166,6 +169,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title b.b".to_string(), lib_id: AlbumLibId::Value(4), + db_id: AlbumDbId::None, }, date: 2008.into(), seq: AlbumSeq(0), @@ -204,6 +208,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title b.c".to_string(), lib_id: AlbumLibId::Value(5), + db_id: AlbumDbId::None, }, date: 2009.into(), seq: AlbumSeq(0), @@ -242,6 +247,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title b.d".to_string(), lib_id: AlbumLibId::Value(6), + db_id: AlbumDbId::None, }, date: 2015.into(), seq: AlbumSeq(0), @@ -294,6 +300,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title c.a".to_string(), lib_id: AlbumLibId::Value(7), + db_id: AlbumDbId::None, }, date: 1985.into(), seq: AlbumSeq(0), @@ -332,6 +339,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title c.b".to_string(), lib_id: AlbumLibId::Value(8), + db_id: AlbumDbId::None, }, date: 2018.into(), seq: AlbumSeq(0), @@ -384,6 +392,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title d.a".to_string(), lib_id: AlbumLibId::Value(9), + db_id: AlbumDbId::None, }, date: 1995.into(), seq: AlbumSeq(0), @@ -422,6 +431,7 @@ macro_rules! library_collection { id: AlbumId { title: "album_title d.b".to_string(), lib_id: AlbumLibId::Value(10), + db_id: AlbumDbId::None, }, date: 2028.into(), seq: AlbumSeq(0), diff --git a/src/tui/app/machine/fetch_state.rs b/src/tui/app/machine/fetch_state.rs index 2029fd5..7443511 100644 --- a/src/tui/app/machine/fetch_state.rs +++ b/src/tui/app/machine/fetch_state.rs @@ -213,7 +213,7 @@ impl AppMachine { } fn album_match(old: &AlbumMeta, new: &AlbumMeta) -> bool { - old.info.musicbrainz.is_some() && (old.info.musicbrainz == new.info.musicbrainz) + old.id.db_id.is_some() && (old.id.db_id == new.id.db_id) } pub fn app_lookup_artist( @@ -287,7 +287,7 @@ impl AppMachine { let arid = arid.mbid(); albums .iter() - .filter(|album| album.meta.info.musicbrainz.is_none()) + .filter(|album| album.meta.id.db_id.is_none()) .map(|album| { MbParams::search_release_group(artist.clone(), arid.clone(), album.meta.clone()) }) diff --git a/src/tui/app/machine/match_state.rs b/src/tui/app/machine/match_state.rs index f339f60..efb51bd 100644 --- a/src/tui/app/machine/match_state.rs +++ b/src/tui/app/machine/match_state.rs @@ -1,7 +1,7 @@ use std::cmp; use musichoard::collection::{ - album::{AlbumInfo, AlbumMeta}, + album::{AlbumDbId, AlbumInfo, AlbumMeta}, artist::{ArtistInfo, ArtistMeta}, musicbrainz::{MbRefOption, Mbid}, }; @@ -12,6 +12,11 @@ use crate::tui::app::{ MatchOption, MatchStatePublic, WidgetState, }; +struct AlbumInfoTuple { + db_id: AlbumDbId, + info: AlbumInfo, +} + trait GetInfoMeta { type InfoType; } @@ -19,7 +24,7 @@ impl GetInfoMeta for ArtistMeta { type InfoType = ArtistInfo; } impl GetInfoMeta for AlbumMeta { - type InfoType = AlbumInfo; + type InfoType = AlbumInfoTuple; } trait GetInfo { @@ -47,16 +52,20 @@ impl GetInfo for MatchOption { } impl GetInfo for MatchOption { - type InfoType = AlbumInfo; + type InfoType = AlbumInfoTuple; fn get_info(&self) -> InfoOption { + let db_id; let mut info = AlbumInfo::default(); match self { - MatchOption::Some(option) => info = option.entity.info.clone(), - MatchOption::CannotHaveMbid => info.musicbrainz = MbRefOption::CannotHaveMbid, + MatchOption::Some(option) => { + db_id = option.entity.id.db_id.clone(); + info = option.entity.info.clone(); + } + MatchOption::CannotHaveMbid => db_id = AlbumDbId::CannotHaveMbid, MatchOption::ManualInputMbid => return InfoOption::NeedInput, } - InfoOption::Info(info) + InfoOption::Info(AlbumInfoTuple { db_id, info }) } } @@ -232,9 +241,11 @@ impl IAppInteractMatch for AppMachine { InfoOption::NeedInput => return self.get_input(), }, EntityMatches::Album(ref mut matches) => match matches.list.extract_info(index) { - InfoOption::Info(info) => { - mh.merge_album_info(&matches.artist, &matches.matching, info) - } + InfoOption::Info(tuple) => mh + .merge_album_info(&matches.artist, &matches.matching, tuple.info) + .and_then(|()| { + mh.set_album_db_id(&matches.artist, &matches.matching, tuple.db_id) + }), InfoOption::NeedInput => return self.get_input(), }, }; @@ -255,7 +266,10 @@ impl IAppInteractMatch for AppMachine { mod tests { use std::{collections::VecDeque, sync::mpsc}; - use mockall::predicate::{self, eq}; + use mockall::{ + predicate::{self, eq}, + Sequence, + }; use musichoard::collection::{ album::{AlbumDate, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType}, artist::{ArtistId, ArtistMeta}, @@ -315,14 +329,13 @@ mod tests { } fn album_id() -> AlbumId { - AlbumId::new("Album") + AlbumId::new("Album").with_db_id(MbRefOption::Some(mbid().into())) } fn album_meta(id: AlbumId) -> AlbumMeta { AlbumMeta::new(id) .with_date(AlbumDate::new(Some(1990), Some(5), None)) .with_info(AlbumInfo::new( - MbRefOption::Some(mbid().into()), Some(AlbumPrimaryType::Album), vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], )) @@ -330,7 +343,7 @@ mod tests { fn album_match() -> EntityMatches { let artist_id = ArtistId::new("Artist"); - let album_id = album_id(); + let mut album_id = album_id(); let album_meta = album_meta(album_id.clone()); let album_1 = album_meta.clone(); @@ -341,14 +354,17 @@ mod tests { album_2.info.secondary_types.pop(); let album_match_2 = Entity::with_score(album_2, 100); + album_id.clear_db_id(); let list = vec![album_match_1.clone(), album_match_2.clone()]; EntityMatches::album_search(artist_id, album_id, list) } fn album_lookup() -> EntityMatches { let artist_id = ArtistId::new("Artist"); - let album_id = album_id(); + let mut album_id = album_id(); let album_meta = album_meta(album_id.clone()); + + album_id.clear_db_id(); let lookup = Entity::new(album_meta.clone()); EntityMatches::album_lookup(artist_id, album_id, lookup) } @@ -393,14 +409,21 @@ mod tests { match matches_info { EntityMatches::Album(_) => { let album_id = AlbumId::new("Album"); - let info = AlbumInfo { - musicbrainz: MbRefOption::CannotHaveMbid, - ..Default::default() - }; + let db_id = MbRefOption::CannotHaveMbid; + let info = AlbumInfo::default(); + + let mut seq = Sequence::new(); music_hoard .expect_merge_album_info() .with(eq(artist_id.clone()), eq(album_id.clone()), eq(info)) .times(1) + .in_sequence(&mut seq) + .return_once(|_, _, _| Ok(())); + music_hoard + .expect_set_album_db_id() + .with(eq(artist_id.clone()), eq(album_id.clone()), eq(db_id)) + .times(1) + .in_sequence(&mut seq) .return_once(|_, _, _| Ok(())); } EntityMatches::Artist(_) => { @@ -515,11 +538,24 @@ mod tests { match matches_info { EntityMatches::Artist(_) => panic!(), EntityMatches::Album(matches) => { - let meta = album_meta(album_id()); + let mut album_id = album_id(); + let meta = album_meta(album_id.clone()); + let db_id = album_id.db_id.clone(); + album_id.clear_db_id(); + let artist = matches.artist.clone(); + + let mut seq = Sequence::new(); music_hoard .expect_merge_album_info() - .with(eq(matches.artist), eq(meta.id), eq(meta.info)) + .with(eq(artist.clone()), eq(album_id.clone()), eq(meta.info)) .times(1) + .in_sequence(&mut seq) + .return_once(|_, _, _| Ok(())); + music_hoard + .expect_set_album_db_id() + .with(eq(artist.clone()), eq(album_id.clone()), eq(db_id)) + .times(1) + .in_sequence(&mut seq) .return_once(|_, _, _| Ok(())); } } diff --git a/src/tui/lib/external/musicbrainz/api/mod.rs b/src/tui/lib/external/musicbrainz/api/mod.rs index 6c2c7c1..ada894d 100644 --- a/src/tui/lib/external/musicbrainz/api/mod.rs +++ b/src/tui/lib/external/musicbrainz/api/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use musichoard::{ collection::{ - album::{AlbumDate, AlbumInfo, AlbumMeta, AlbumSeq}, + album::{AlbumDate, AlbumDbId, AlbumId, AlbumInfo, AlbumLibId, AlbumMeta, AlbumSeq}, artist::{ArtistInfo, ArtistMeta}, musicbrainz::{MbRefOption, Mbid}, }, @@ -128,11 +128,14 @@ fn from_mb_artist_meta(meta: MbArtistMeta) -> (ArtistMeta, Option) { fn from_mb_release_group_meta(meta: MbReleaseGroupMeta) -> AlbumMeta { AlbumMeta { - id: meta.title.into(), + id: AlbumId { + title: meta.title, + lib_id: AlbumLibId::None, + db_id: AlbumDbId::Some(meta.id.into()), + }, date: meta.first_release_date, seq: AlbumSeq::default(), info: AlbumInfo { - musicbrainz: MbRefOption::Some(meta.id.into()), primary_type: meta.primary_type, secondary_types: meta.secondary_types.unwrap_or_default(), }, diff --git a/src/tui/lib/mod.rs b/src/tui/lib/mod.rs index 7470a3a..d8272b5 100644 --- a/src/tui/lib/mod.rs +++ b/src/tui/lib/mod.rs @@ -3,7 +3,7 @@ pub mod interface; use musichoard::{ collection::{ - album::{AlbumId, AlbumInfo, AlbumMeta}, + album::{AlbumDbId, AlbumId, AlbumInfo, AlbumMeta}, artist::{ArtistId, ArtistInfo}, Collection, }, @@ -31,6 +31,12 @@ pub trait IMusicHoard { id: &ArtistId, info: ArtistInfo, ) -> Result<(), musichoard::Error>; + fn set_album_db_id( + &mut self, + artist_id: &ArtistId, + album_id: &AlbumId, + db_id: AlbumDbId, + ) -> Result<(), musichoard::Error>; fn merge_album_info( &mut self, artist_id: &ArtistId, @@ -69,6 +75,15 @@ impl IMusicHoard for MusicHoard::merge_artist_info(self, id, info) } + fn set_album_db_id( + &mut self, + artist_id: &ArtistId, + album_id: &AlbumId, + db_id: AlbumDbId, + ) -> Result<(), musichoard::Error> { + ::set_album_db_id(self, artist_id, album_id, db_id) + } + fn merge_album_info( &mut self, artist_id: &ArtistId, diff --git a/src/tui/testmod.rs b/src/tui/testmod.rs index 6624bac..fa7e664 100644 --- a/src/tui/testmod.rs +++ b/src/tui/testmod.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; use musichoard::collection::{ - album::{Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMeta, AlbumPrimaryType, AlbumSeq}, + album::{ + Album, AlbumDbId, AlbumId, AlbumInfo, AlbumLibId, AlbumMeta, AlbumPrimaryType, AlbumSeq, + }, artist::{Artist, ArtistId, ArtistInfo, ArtistMeta}, musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption}, track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, diff --git a/src/tui/ui/info_state.rs b/src/tui/ui/info_state.rs index 4a99c0a..be11b12 100644 --- a/src/tui/ui/info_state.rs +++ b/src/tui/ui/info_state.rs @@ -108,7 +108,7 @@ impl<'a> AlbumOverlay<'a> { .map(|a| UiDisplay::display_album_lib_id(&a.meta.id.lib_id)) .unwrap_or_default(), album - .map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.info.musicbrainz)) + .map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.id.db_id)) .unwrap_or_default(), )); diff --git a/src/tui/ui/mod.rs b/src/tui/ui/mod.rs index 3258e86..c3e3ee3 100644 --- a/src/tui/ui/mod.rs +++ b/src/tui/ui/mod.rs @@ -201,7 +201,6 @@ mod tests { use musichoard::collection::{ album::{AlbumDate, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType}, artist::{Artist, ArtistId, ArtistMeta}, - musicbrainz::MbRefOption, }; use crate::tui::{ @@ -362,7 +361,6 @@ mod tests { AlbumMeta::new(id) .with_date(AlbumDate::new(Some(1990), Some(5), None)) .with_info(AlbumInfo::new( - MbRefOption::None, Some(AlbumPrimaryType::Album), vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], )) diff --git a/tests/testlib.rs b/tests/testlib.rs index 072b325..9bdab89 100644 --- a/tests/testlib.rs +++ b/tests/testlib.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use musichoard::collection::{ album::{ - Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType, - AlbumSeq, + Album, AlbumDbId, AlbumId, AlbumInfo, AlbumLibId, AlbumMeta, AlbumPrimaryType, + AlbumSecondaryType, AlbumSeq, }, artist::{Artist, ArtistId, ArtistInfo, ArtistMeta}, musicbrainz::{MbArtistRef, MbRefOption}, @@ -42,11 +42,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: AlbumId { title: String::from("Slovo"), lib_id: AlbumLibId::Value(7), + db_id: AlbumDbId::None, }, date: 2011.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -235,11 +235,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: AlbumId { title: String::from("Vên [re‐recorded]"), lib_id: AlbumLibId::Value(1), + db_id: AlbumDbId::None, }, date: 2004.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Ep), secondary_types: vec![], }, @@ -318,11 +318,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: AlbumId { title: String::from("Slania"), lib_id: AlbumLibId::Value(2), + db_id: AlbumDbId::None, }, date: 2008.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -489,11 +489,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: AlbumId { title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), lib_id: AlbumLibId::Value(3), + db_id: AlbumDbId::None, }, date: 2001.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -648,6 +648,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: AlbumId { title: String::from("Paper Plague"), lib_id: AlbumLibId::Singleton, + db_id: AlbumDbId::None, }, date: 2011.into(), seq: AlbumSeq(0), @@ -671,11 +672,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: AlbumId { title: String::from("Unbreakable"), lib_id: AlbumLibId::Value(4), + db_id: AlbumDbId::None, }, date: 2011.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -787,11 +788,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: AlbumId { title: String::from("Ride the Lightning"), lib_id: AlbumLibId::Value(5), + db_id: AlbumDbId::None, }, date: 1984.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![], }, @@ -892,11 +893,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: AlbumId { title: String::from("S&M"), lib_id: AlbumLibId::Value(6), + db_id: AlbumDbId::None, }, date: 1999.into(), seq: AlbumSeq(0), info: AlbumInfo { - musicbrainz: MbRefOption::None, primary_type: Some(AlbumPrimaryType::Album), secondary_types: vec![AlbumSecondaryType::Live], },