Enable fetch to apply modifications to the database #221

Merged
wojtek merged 14 commits from 189---enable-fetch-to-apply-modifications-to-the-database into main 2024-09-29 10:44:38 +02:00
12 changed files with 616 additions and 131 deletions
Showing only changes of commit a26ae891ff - Show all commits

View File

@ -206,7 +206,7 @@ impl MusicBrainzCommand {
fn handle(self, music_hoard: &mut MH, artist_name: &str) { fn handle(self, music_hoard: &mut MH, artist_name: &str) {
match self { match self {
MusicBrainzCommand::Set(musicbrainz_value) => music_hoard MusicBrainzCommand::Set(musicbrainz_value) => music_hoard
.set_artist_musicbrainz(ArtistId::new(artist_name), musicbrainz_value.url) .set_artist_musicbrainz_from_url(ArtistId::new(artist_name), musicbrainz_value.url)
.expect("failed to set MusicBrainz URL"), .expect("failed to set MusicBrainz URL"),
MusicBrainzCommand::Clear => music_hoard MusicBrainzCommand::Clear => music_hoard
.clear_artist_musicbrainz(ArtistId::new(artist_name)) .clear_artist_musicbrainz(ArtistId::new(artist_name))

View File

@ -204,13 +204,29 @@ impl AlbumMeta {
self.seq = AlbumSeq::default(); self.seq = AlbumSeq::default();
} }
pub fn set_musicbrainz_ref(&mut self, mbref: MbAlbumRef) { pub fn set_musicbrainz_ref(&mut self, mbref: MbRefOption<MbAlbumRef>) {
self.musicbrainz.replace(mbref); _ = mem::replace(&mut self.musicbrainz, mbref);
} }
pub fn clear_musicbrainz_ref(&mut self) { pub fn clear_musicbrainz_ref(&mut self) {
self.musicbrainz.take(); self.musicbrainz.take();
} }
pub fn set_primary_type(&mut self, primary_type: Option<AlbumPrimaryType>) {
_ = mem::replace(&mut self.primary_type, primary_type);
}
pub fn clear_primary_type(&mut self) {
self.primary_type.take();
}
pub fn set_secondary_types(&mut self, secondary_types: Vec<AlbumSecondaryType>) {
self.secondary_types = secondary_types;
}
pub fn clear_secondary_types(&mut self) {
self.secondary_types.clear();
}
} }
impl PartialOrd for AlbumMeta { impl PartialOrd for AlbumMeta {
@ -368,20 +384,20 @@ mod tests {
assert_eq!(album.meta.musicbrainz, expected); assert_eq!(album.meta.musicbrainz, expected);
// Setting a URL on an album. // Setting a URL on an album.
album album.meta.set_musicbrainz_ref(MbRefOption::Some(
.meta MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap(),
.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap()); ));
expected.replace(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap()); expected.replace(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(album.meta.musicbrainz, expected); assert_eq!(album.meta.musicbrainz, expected);
album album.meta.set_musicbrainz_ref(MbRefOption::Some(
.meta MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap(),
.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap()); ));
assert_eq!(album.meta.musicbrainz, expected); assert_eq!(album.meta.musicbrainz, expected);
album album.meta.set_musicbrainz_ref(MbRefOption::Some(
.meta MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap(),
.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap()); ));
expected.replace(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap()); expected.replace(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap());
assert_eq!(album.meta.musicbrainz, expected); assert_eq!(album.meta.musicbrainz, expected);

View File

@ -92,8 +92,8 @@ impl ArtistMeta {
self.sort.take(); self.sort.take();
} }
pub fn set_musicbrainz_ref(&mut self, mbref: MbArtistRef) { pub fn set_musicbrainz_ref(&mut self, mbref: MbRefOption<MbArtistRef>) {
self.musicbrainz.replace(mbref); self.musicbrainz = mbref
} }
pub fn clear_musicbrainz_ref(&mut self) { pub fn clear_musicbrainz_ref(&mut self) {
@ -259,20 +259,20 @@ mod tests {
assert_eq!(artist.meta.musicbrainz, expected); assert_eq!(artist.meta.musicbrainz, expected);
// Setting a URL on an artist. // Setting a URL on an artist.
artist artist.meta.set_musicbrainz_ref(MbRefOption::Some(
.meta MbArtistRef::from_url_str(MUSICBRAINZ).unwrap(),
.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap()); ));
expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap()); expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(artist.meta.musicbrainz, expected); assert_eq!(artist.meta.musicbrainz, expected);
artist artist.meta.set_musicbrainz_ref(MbRefOption::Some(
.meta MbArtistRef::from_url_str(MUSICBRAINZ).unwrap(),
.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap()); ));
assert_eq!(artist.meta.musicbrainz, expected); assert_eq!(artist.meta.musicbrainz, expected);
artist artist.meta.set_musicbrainz_ref(MbRefOption::Some(
.meta MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap(),
.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap()); ));
expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap()); expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap());
assert_eq!(artist.meta.musicbrainz, expected); assert_eq!(artist.meta.musicbrainz, expected);

View File

@ -1,12 +1,18 @@
use crate::core::{ use crate::{
collection::{ collection::{
album::{Album, AlbumId, AlbumSeq}, album::{AlbumPrimaryType, AlbumSecondaryType},
artist::{Artist, ArtistId}, musicbrainz::{MbAlbumRef, MbRefOption},
musicbrainz::MbArtistRef, },
Collection, core::{
collection::{
album::{Album, AlbumId, AlbumSeq},
artist::{Artist, ArtistId},
musicbrainz::MbArtistRef,
Collection,
},
interface::database::IDatabase,
musichoard::{base::IMusicHoardBasePrivate, Error, MusicHoard, NoDatabase},
}, },
interface::database::IDatabase,
musichoard::{base::IMusicHoardBasePrivate, Error, MusicHoard, NoDatabase},
}; };
pub trait IMusicHoardDatabase { pub trait IMusicHoardDatabase {
@ -22,7 +28,12 @@ pub trait IMusicHoardDatabase {
) -> Result<(), Error>; ) -> Result<(), Error>;
fn clear_artist_sort<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>; fn clear_artist_sort<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
fn set_artist_musicbrainz<Id: AsRef<ArtistId>, S: AsRef<str>>( fn set_artist_musicbrainz<Id: AsRef<ArtistId>>(
&mut self,
artist_id: Id,
mbid: MbRefOption<MbArtistRef>,
) -> Result<(), Error>;
fn set_artist_musicbrainz_from_url<Id: AsRef<ArtistId>, S: AsRef<str>>(
&mut self, &mut self,
artist_id: Id, artist_id: Id,
url: S, url: S,
@ -54,6 +65,17 @@ pub trait IMusicHoardDatabase {
property: S, property: S,
) -> Result<(), Error>; ) -> Result<(), Error>;
fn set_album_musicbrainz<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
mbid: MbRefOption<MbAlbumRef>,
) -> Result<(), Error>;
fn clear_album_musicbrainz<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
) -> Result<(), Error>;
fn set_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>( fn set_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self, &mut self,
artist_id: ArtistIdRef, artist_id: ArtistIdRef,
@ -65,6 +87,28 @@ pub trait IMusicHoardDatabase {
artist_id: ArtistIdRef, artist_id: ArtistIdRef,
album_id: AlbumIdRef, album_id: AlbumIdRef,
) -> Result<(), Error>; ) -> Result<(), Error>;
fn set_album_primary_type<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
primary_type: Option<AlbumPrimaryType>,
) -> Result<(), Error>;
fn clear_album_primary_type<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
) -> Result<(), Error>;
fn set_album_secondary_types<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
secondary_types: Vec<AlbumSecondaryType>,
) -> Result<(), Error>;
fn clear_album_secondary_types<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
) -> Result<(), Error>;
} }
impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database, Library> { impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database, Library> {
@ -120,14 +164,24 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
) )
} }
fn set_artist_musicbrainz<Id: AsRef<ArtistId>, S: AsRef<str>>( fn set_artist_musicbrainz<Id: AsRef<ArtistId>>(
&mut self,
artist_id: Id,
mbid: MbRefOption<MbArtistRef>,
) -> Result<(), Error> {
self.update_artist(artist_id.as_ref(), |artist| {
artist.meta.set_musicbrainz_ref(mbid)
})
}
fn set_artist_musicbrainz_from_url<Id: AsRef<ArtistId>, S: AsRef<str>>(
&mut self, &mut self,
artist_id: Id, artist_id: Id,
url: S, url: S,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mb = MbArtistRef::from_url_str(url)?; let mb = MbArtistRef::from_url_str(url)?;
self.update_artist(artist_id.as_ref(), |artist| { self.update_artist(artist_id.as_ref(), |artist| {
artist.meta.set_musicbrainz_ref(mb) artist.meta.set_musicbrainz_ref(MbRefOption::Some(mb))
}) })
} }
@ -183,6 +237,27 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
}) })
} }
fn set_album_musicbrainz<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
mbid: MbRefOption<MbAlbumRef>,
) -> Result<(), Error> {
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
album.meta.set_musicbrainz_ref(mbid)
})
}
fn clear_album_musicbrainz<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
) -> Result<(), Error> {
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
album.meta.clear_musicbrainz_ref()
})
}
fn set_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>( fn set_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self, &mut self,
artist_id: ArtistIdRef, artist_id: ArtistIdRef,
@ -194,7 +269,6 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
album_id.as_ref(), album_id.as_ref(),
|album| album.meta.set_seq(AlbumSeq(seq)), |album| album.meta.set_seq(AlbumSeq(seq)),
|artist| artist.albums.sort_unstable(), |artist| artist.albums.sort_unstable(),
|_| {},
) )
} }
@ -208,9 +282,50 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
album_id.as_ref(), album_id.as_ref(),
|album| album.meta.clear_seq(), |album| album.meta.clear_seq(),
|artist| artist.albums.sort_unstable(), |artist| artist.albums.sort_unstable(),
|_| {},
) )
} }
fn set_album_primary_type<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
primary_type: Option<AlbumPrimaryType>,
) -> Result<(), Error> {
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
album.meta.set_primary_type(primary_type)
})
}
fn clear_album_primary_type<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
) -> Result<(), Error> {
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
album.meta.clear_primary_type()
})
}
fn set_album_secondary_types<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
secondary_types: Vec<AlbumSecondaryType>,
) -> Result<(), Error> {
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
album.meta.set_secondary_types(secondary_types)
})
}
fn clear_album_secondary_types<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
&mut self,
artist_id: Id,
album_id: AlbumIdRef,
) -> Result<(), Error> {
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
album.meta.clear_secondary_types()
})
}
} }
pub trait IMusicHoardDatabasePrivate { pub trait IMusicHoardDatabasePrivate {
@ -272,24 +387,34 @@ impl<Database: IDatabase, Library> MusicHoard<Database, Library> {
self.update_artist_and(artist_id, fn_artist, |_| {}) self.update_artist_and(artist_id, fn_artist, |_| {})
} }
fn update_album_and<FnAlbum, FnArtist, FnColl>( fn update_album_and<FnAlbum, FnArtist>(
&mut self, &mut self,
artist_id: &ArtistId, artist_id: &ArtistId,
album_id: &AlbumId, album_id: &AlbumId,
fn_album: FnAlbum, fn_album: FnAlbum,
fn_artist: FnArtist, fn_artist: FnArtist,
fn_coll: FnColl,
) -> Result<(), Error> ) -> Result<(), Error>
where where
FnAlbum: FnOnce(&mut Album), FnAlbum: FnOnce(&mut Album),
FnArtist: FnOnce(&mut Artist), FnArtist: FnOnce(&mut Artist),
FnColl: FnOnce(&mut Collection),
{ {
let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id)?; let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id)?;
let album = Self::get_album_mut_or_err(artist, album_id)?; let album = Self::get_album_mut_or_err(artist, album_id)?;
fn_album(album); fn_album(album);
fn_artist(artist); fn_artist(artist);
self.update_collection(fn_coll) self.update_collection(|_| {})
}
fn update_album<FnAlbum>(
&mut self,
artist_id: &ArtistId,
album_id: &AlbumId,
fn_album: FnAlbum,
) -> Result<(), Error>
where
FnAlbum: FnOnce(&mut Album),
{
self.update_album_and(artist_id, album_id, fn_album, |_| {})
} }
} }
@ -423,7 +548,7 @@ mod tests {
assert!(music_hoard.add_artist(artist_id.clone()).is_ok()); assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
let actual_err = music_hoard let actual_err = music_hoard
.set_artist_musicbrainz(&artist_id, MUSICBUTLER) .set_artist_musicbrainz_from_url(&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 artist MusicBrainz URL: {MUSICBUTLER}" "an error occurred when processing a URL: invalid artist MusicBrainz URL: {MUSICBUTLER}"
@ -449,13 +574,13 @@ mod tests {
// Setting a URL on an artist not in the collection is an error. // Setting a URL on an artist not in the collection is an error.
assert!(music_hoard assert!(music_hoard
.set_artist_musicbrainz(&artist_id_2, MUSICBRAINZ) .set_artist_musicbrainz_from_url(&artist_id_2, MUSICBRAINZ)
.is_err()); .is_err());
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected); assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
// Setting a URL on an artist. // Setting a URL on an artist.
assert!(music_hoard assert!(music_hoard
.set_artist_musicbrainz(&artist_id, MUSICBRAINZ) .set_artist_musicbrainz_from_url(&artist_id, MUSICBRAINZ)
.is_ok()); .is_ok());
expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap()); expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected); assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);

View File

@ -73,7 +73,7 @@ impl IAppInteractBrowse for AppMachine<BrowseState> {
} }
fn fetch_musicbrainz(self) -> Self::APP { fn fetch_musicbrainz(self) -> Self::APP {
AppMachine::app_fetch_new(self.inner) AppMachine::app_fetch_first(self.inner)
} }
} }

View File

@ -5,7 +5,7 @@ use std::{
use musichoard::collection::{ use musichoard::collection::{
album::AlbumMeta, album::AlbumMeta,
artist::{Artist, ArtistMeta}, artist::{Artist, ArtistId, ArtistMeta},
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid}, musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
}; };
@ -46,12 +46,27 @@ impl FetchState {
} }
} }
enum FetchError {
NothingToFetch,
SubmitError(DaemonError),
}
impl From<DaemonError> for FetchError {
fn from(value: DaemonError) -> Self {
FetchError::SubmitError(value)
}
}
impl AppMachine<FetchState> { impl AppMachine<FetchState> {
fn fetch_state(inner: AppInner, state: FetchState) -> Self { fn fetch_state(inner: AppInner, state: FetchState) -> Self {
AppMachine::new(inner, state) AppMachine::new(inner, state)
} }
pub fn app_fetch_new(inner: AppInner) -> App { pub fn app_fetch_first(inner: AppInner) -> App {
Self::app_fetch_new(inner, true)
}
fn app_fetch_new(inner: AppInner, first: bool) -> App {
let coll = inner.music_hoard.get_collection(); let coll = inner.music_hoard.get_collection();
let artist = match inner.selection.state_artist(coll) { let artist = match inner.selection.state_artist(coll) {
Some(artist_state) => &coll[artist_state.index], Some(artist_state) => &coll[artist_state.index],
@ -61,16 +76,38 @@ impl AppMachine<FetchState> {
}; };
let (fetch_tx, fetch_rx) = mpsc::channel::<MbApiResult>(); let (fetch_tx, fetch_rx) = mpsc::channel::<MbApiResult>();
if let Err(err) = Self::submit_fetch_job(&*inner.musicbrainz, fetch_tx, artist) {
return AppMachine::error_state(inner, err.to_string()).into();
}
let fetch = FetchState::new(fetch_rx); let fetch = FetchState::new(fetch_rx);
AppMachine::app_fetch(inner, fetch, true) match Self::submit_fetch_job(&*inner.musicbrainz, fetch_tx, artist) {
Ok(()) => AppMachine::fetch_state(inner, fetch).into(),
Err(FetchError::NothingToFetch) => {
if first {
AppMachine::match_state(inner, MatchState::new(None, fetch)).into()
} else {
AppMachine::browse_state(inner).into()
}
}
Err(FetchError::SubmitError(daemon_err)) => {
AppMachine::error_state(inner, daemon_err.to_string()).into()
}
}
} }
pub fn app_fetch_next(inner: AppInner, fetch: FetchState) -> App { pub fn app_fetch_next(inner: AppInner, mut fetch: FetchState) -> App {
Self::app_fetch(inner, fetch, false) match fetch.try_recv() {
Ok(fetch_result) => match fetch_result {
Ok(next_match) => {
let current = Some(next_match);
AppMachine::match_state(inner, MatchState::new(current, fetch)).into()
}
Err(fetch_err) => {
AppMachine::error_state(inner, format!("fetch failed: {fetch_err}")).into()
}
},
Err(recv_err) => match recv_err {
TryRecvError::Empty => AppMachine::fetch_state(inner, fetch).into(),
TryRecvError::Disconnected => Self::app_fetch_new(inner, false),
},
}
} }
pub fn app_lookup_artist( pub fn app_lookup_artist(
@ -86,10 +123,13 @@ impl AppMachine<FetchState> {
pub fn app_lookup_album( pub fn app_lookup_album(
inner: AppInner, inner: AppInner,
fetch: FetchState, fetch: FetchState,
artist_id: &ArtistId,
album: &AlbumMeta, album: &AlbumMeta,
mbid: Mbid, mbid: Mbid,
) -> App { ) -> App {
let f = Self::submit_lookup_release_group_job; let f = |mb: &dyn IMbJobSender, rs, album, mbid| {
Self::submit_lookup_release_group_job(mb, rs, artist_id, album, mbid)
};
Self::app_lookup(f, inner, fetch, album, mbid) Self::app_lookup(f, inner, fetch, album, mbid)
} }
@ -111,48 +151,33 @@ impl AppMachine<FetchState> {
Self::app_fetch_next(inner, fetch) Self::app_fetch_next(inner, fetch)
} }
fn app_fetch(inner: AppInner, mut fetch: FetchState, first: bool) -> App {
match fetch.try_recv() {
Ok(fetch_result) => match fetch_result {
Ok(next_match) => {
let current = Some(next_match);
AppMachine::match_state(inner, MatchState::new(current, fetch)).into()
}
Err(fetch_err) => {
AppMachine::error_state(inner, format!("fetch failed: {fetch_err}")).into()
}
},
Err(recv_err) => match recv_err {
TryRecvError::Empty => AppMachine::fetch_state(inner, fetch).into(),
TryRecvError::Disconnected => {
if first {
AppMachine::match_state(inner, MatchState::new(None, fetch)).into()
} else {
AppMachine::browse_state(inner).into()
}
}
},
}
}
fn submit_fetch_job( fn submit_fetch_job(
musicbrainz: &dyn IMbJobSender, musicbrainz: &dyn IMbJobSender,
result_sender: ResultSender, result_sender: ResultSender,
artist: &Artist, artist: &Artist,
) -> Result<(), DaemonError> { ) -> Result<(), FetchError> {
let requests = match artist.meta.musicbrainz { let requests = match artist.meta.musicbrainz {
MbRefOption::Some(ref arid) => { MbRefOption::Some(ref arid) => {
let arid = arid.mbid(); let arid = arid.mbid();
let albums = artist.albums.iter(); let albums = artist.albums.iter();
albums albums
.filter(|album| matches!(album.meta.musicbrainz, MbRefOption::None)) .filter(|album| matches!(album.meta.musicbrainz, MbRefOption::None))
.map(|album| MbParams::search_release_group(arid.clone(), album.meta.clone())) .map(|album| {
MbParams::search_release_group(
artist.meta.id.clone(),
arid.clone(),
album.meta.clone(),
)
})
.collect() .collect()
} }
MbRefOption::CannotHaveMbid => return Ok(()), MbRefOption::CannotHaveMbid => VecDeque::new(),
MbRefOption::None => VecDeque::from([MbParams::search_artist(artist.meta.clone())]), MbRefOption::None => VecDeque::from([MbParams::search_artist(artist.meta.clone())]),
}; };
musicbrainz.submit_background_job(result_sender, requests) if requests.is_empty() {
return Err(FetchError::NothingToFetch);
}
Ok(musicbrainz.submit_background_job(result_sender, requests)?)
} }
fn submit_lookup_artist_job( fn submit_lookup_artist_job(
@ -168,10 +193,15 @@ impl AppMachine<FetchState> {
fn submit_lookup_release_group_job( fn submit_lookup_release_group_job(
musicbrainz: &dyn IMbJobSender, musicbrainz: &dyn IMbJobSender,
result_sender: ResultSender, result_sender: ResultSender,
artist_id: &ArtistId,
album: &AlbumMeta, album: &AlbumMeta,
mbid: Mbid, mbid: Mbid,
) -> Result<(), DaemonError> { ) -> Result<(), DaemonError> {
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid)]); let requests = VecDeque::from([MbParams::lookup_release_group(
artist_id.clone(),
album.clone(),
mbid,
)]);
musicbrainz.submit_foreground_job(result_sender, requests) musicbrainz.submit_foreground_job(result_sender, requests)
} }
} }
@ -207,7 +237,11 @@ impl IAppEventFetch for AppMachine<FetchState> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use mockall::predicate; use mockall::predicate;
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid}; use musichoard::collection::{
album::AlbumMeta,
artist::{ArtistId, ArtistMeta},
musicbrainz::Mbid,
};
use crate::tui::{ use crate::tui::{
app::{ app::{
@ -257,18 +291,23 @@ mod tests {
#[test] #[test]
fn fetch_no_artist() { fn fetch_no_artist() {
let app = AppMachine::app_fetch_new(inner(music_hoard(vec![]))); let app = AppMachine::app_fetch_first(inner(music_hoard(vec![])));
assert!(matches!(app.state(), AppState::Error(_))); assert!(matches!(app.state(), AppState::Error(_)));
} }
fn search_release_group_expectation( fn search_release_group_expectation(
job_sender: &mut MockIMbJobSender, job_sender: &mut MockIMbJobSender,
arid: &Mbid, artist_id: &ArtistId,
artist_mbid: &Mbid,
albums: &[AlbumMeta], albums: &[AlbumMeta],
) { ) {
let mut requests = VecDeque::new(); let mut requests = VecDeque::new();
for album in albums.iter() { for album in albums.iter() {
requests.push_back(MbParams::search_release_group(arid.clone(), album.clone())); requests.push_back(MbParams::search_release_group(
artist_id.clone(),
artist_mbid.clone(),
album.clone(),
));
} }
job_sender job_sender
.expect_submit_background_job() .expect_submit_background_job()
@ -281,13 +320,19 @@ mod tests {
fn fetch_albums() { fn fetch_albums() {
let mut mb_job_sender = MockIMbJobSender::new(); let mut mb_job_sender = MockIMbJobSender::new();
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); let artist_id = COLLECTION[1].meta.id.clone();
let artist_mbid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
let album_1_meta = COLLECTION[1].albums[0].meta.clone(); let album_1_meta = COLLECTION[1].albums[0].meta.clone();
let album_4_meta = COLLECTION[1].albums[3].meta.clone(); let album_4_meta = COLLECTION[1].albums[3].meta.clone();
// Other albums have an MBID and so they will be skipped. // Other albums have an MBID and so they will be skipped.
search_release_group_expectation(&mut mb_job_sender, &arid, &[album_1_meta, album_4_meta]); search_release_group_expectation(
&mut mb_job_sender,
&artist_id,
&artist_mbid,
&[album_1_meta, album_4_meta],
);
let music_hoard = music_hoard(COLLECTION.to_owned()); let music_hoard = music_hoard(COLLECTION.to_owned());
let inner = AppInner::new(music_hoard, mb_job_sender); let inner = AppInner::new(music_hoard, mb_job_sender);
@ -300,8 +345,16 @@ mod tests {
assert!(matches!(app, AppState::Match(_))); assert!(matches!(app, AppState::Match(_)));
} }
fn lookup_album_expectation(job_sender: &mut MockIMbJobSender, album: &AlbumMeta) { fn lookup_album_expectation(
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid())]); job_sender: &mut MockIMbJobSender,
artist_id: &ArtistId,
album: &AlbumMeta,
) {
let requests = VecDeque::from([MbParams::lookup_release_group(
artist_id.clone(),
album.clone(),
mbid(),
)]);
job_sender job_sender
.expect_submit_foreground_job() .expect_submit_foreground_job()
.with(predicate::always(), predicate::eq(requests)) .with(predicate::always(), predicate::eq(requests))
@ -313,8 +366,9 @@ mod tests {
fn lookup_album() { fn lookup_album() {
let mut mb_job_sender = MockIMbJobSender::new(); let mut mb_job_sender = MockIMbJobSender::new();
let artist_id = COLLECTION[1].meta.id.clone();
let album = COLLECTION[1].albums[0].meta.clone(); let album = COLLECTION[1].albums[0].meta.clone();
lookup_album_expectation(&mut mb_job_sender, &album); lookup_album_expectation(&mut mb_job_sender, &artist_id, &album);
let music_hoard = music_hoard(COLLECTION.to_owned()); let music_hoard = music_hoard(COLLECTION.to_owned());
let inner = AppInner::new(music_hoard, mb_job_sender); let inner = AppInner::new(music_hoard, mb_job_sender);
@ -322,7 +376,7 @@ mod tests {
let (_fetch_tx, fetch_rx) = mpsc::channel(); let (_fetch_tx, fetch_rx) = mpsc::channel();
let fetch = FetchState::new(fetch_rx); let fetch = FetchState::new(fetch_rx);
AppMachine::app_lookup_album(inner, fetch, &album, mbid()); AppMachine::app_lookup_album(inner, fetch, &artist_id, &album, mbid());
} }
fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) { fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
@ -442,7 +496,7 @@ mod tests {
let inner = inner(music_hoard(COLLECTION.clone())); let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = FetchState::new(rx); let fetch = FetchState::new(rx);
let mut app = AppMachine::app_fetch(inner, fetch, true); let mut app = AppMachine::app_fetch_next(inner, fetch);
assert!(matches!(app, AppState::Match(_))); assert!(matches!(app, AppState::Match(_)));
let public = app.get(); let public = app.get();
@ -465,7 +519,7 @@ mod tests {
let inner = inner(music_hoard(COLLECTION.clone())); let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = FetchState::new(rx); let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true); let app = AppMachine::app_fetch_next(inner, fetch);
assert!(matches!(app, AppState::Error(_))); assert!(matches!(app, AppState::Error(_)));
} }
@ -475,7 +529,7 @@ mod tests {
let inner = inner(music_hoard(COLLECTION.clone())); let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = FetchState::new(rx); let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true); let app = AppMachine::app_fetch_next(inner, fetch);
assert!(matches!(app, AppState::Fetch(_))); assert!(matches!(app, AppState::Fetch(_)));
} }
@ -485,7 +539,7 @@ mod tests {
let inner = inner(music_hoard(COLLECTION.clone())); let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = FetchState::new(rx); let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true); let app = AppMachine::app_fetch_next(inner, fetch);
assert!(matches!(app, AppState::Match(_))); assert!(matches!(app, AppState::Match(_)));
} }
@ -504,7 +558,7 @@ mod tests {
let inner = inner(music_hoard(COLLECTION.clone())); let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = FetchState::new(rx); let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true); let app = AppMachine::app_fetch_next(inner, fetch);
assert!(matches!(app, AppState::Fetch(_))); assert!(matches!(app, AppState::Fetch(_)));
let artist = COLLECTION[3].meta.clone(); let artist = COLLECTION[3].meta.clone();

View File

@ -1,13 +1,100 @@
use std::cmp; use std::cmp;
use musichoard::collection::musicbrainz::Mbid; use musichoard::collection::{
album::AlbumMeta,
use crate::tui::app::{ artist::{ArtistId, ArtistMeta},
machine::{fetch_state::FetchState, input::Input, App, AppInner, AppMachine}, musicbrainz::{MbRefOption, Mbid},
AlbumMatches, AppPublicState, AppState, ArtistMatches, IAppInteractMatch, ListOption,
LookupOption, MatchStateInfo, MatchStatePublic, MissOption, SearchOption, WidgetState,
}; };
use crate::tui::{
app::{
machine::{fetch_state::FetchState, input::Input, App, AppInner, AppMachine},
AlbumMatches, AppPublicState, AppState, ArtistMatches, IAppInteractMatch, ListOption,
LookupOption, MatchStateInfo, MatchStatePublic, MissOption, SearchOption, WidgetState,
},
lib::IMusicHoard,
};
impl LookupOption<ArtistMeta> {
fn set(
self,
music_hoard: &mut dyn IMusicHoard,
meta: &ArtistMeta,
) -> Result<(), musichoard::Error> {
let mbref = match self {
LookupOption::Match(lookup) => lookup.item.musicbrainz,
LookupOption::None(MissOption::CannotHaveMbid) => MbRefOption::CannotHaveMbid,
_ => panic!(),
};
music_hoard.set_artist_musicbrainz(&meta.id, mbref)
}
}
impl SearchOption<ArtistMeta> {
fn set(
self,
music_hoard: &mut dyn IMusicHoard,
meta: &ArtistMeta,
) -> Result<(), musichoard::Error> {
let mbref = match self {
SearchOption::Match(search) => search.item.musicbrainz,
SearchOption::None(MissOption::CannotHaveMbid) => MbRefOption::CannotHaveMbid,
_ => panic!(),
};
music_hoard.set_artist_musicbrainz(&meta.id, mbref)
}
}
impl LookupOption<AlbumMeta> {
fn set(
self,
music_hoard: &mut dyn IMusicHoard,
artist: &ArtistId,
meta: &AlbumMeta,
) -> Result<(), musichoard::Error> {
let (mbref, primary_type, secondary_types) = match self {
LookupOption::Match(lookup) => (
lookup.item.musicbrainz,
lookup.item.primary_type,
lookup.item.secondary_types,
),
LookupOption::None(MissOption::CannotHaveMbid) => {
(MbRefOption::CannotHaveMbid, None, Vec::new())
}
_ => panic!(),
};
music_hoard.set_album_musicbrainz(artist, &meta.id, mbref)?;
music_hoard.set_album_primary_type(artist, &meta.id, primary_type)?;
music_hoard.set_album_secondary_types(artist, &meta.id, secondary_types)?;
Ok(())
}
}
impl SearchOption<AlbumMeta> {
fn set(
self,
music_hoard: &mut dyn IMusicHoard,
artist: &ArtistId,
meta: &AlbumMeta,
) -> Result<(), musichoard::Error> {
let (mbref, primary_type, secondary_types) = match self {
SearchOption::Match(lookup) => (
lookup.item.musicbrainz,
lookup.item.primary_type,
lookup.item.secondary_types,
),
SearchOption::None(MissOption::CannotHaveMbid) => {
(MbRefOption::CannotHaveMbid, None, Vec::new())
}
_ => panic!(),
};
music_hoard.set_album_musicbrainz(artist, &meta.id, mbref)?;
music_hoard.set_album_primary_type(artist, &meta.id, primary_type)?;
music_hoard.set_album_secondary_types(artist, &meta.id, secondary_types)?;
Ok(())
}
}
impl<T: PartialEq> ListOption<T> { impl<T: PartialEq> ListOption<T> {
fn len(&self) -> usize { fn len(&self) -> usize {
match self { match self {
@ -42,6 +129,35 @@ impl<T: PartialEq> ListOption<T> {
} }
} }
impl ListOption<ArtistMeta> {
fn set(
self,
music_hoard: &mut dyn IMusicHoard,
meta: &ArtistMeta,
index: usize,
) -> Result<(), musichoard::Error> {
match self {
ListOption::Lookup(mut list) => list.swap_remove(index).set(music_hoard, meta),
ListOption::Search(mut list) => list.swap_remove(index).set(music_hoard, meta),
}
}
}
impl ListOption<AlbumMeta> {
fn set(
self,
music_hoard: &mut dyn IMusicHoard,
artist: &ArtistId,
meta: &AlbumMeta,
index: usize,
) -> Result<(), musichoard::Error> {
match self {
ListOption::Lookup(mut list) => list.swap_remove(index).set(music_hoard, artist, meta),
ListOption::Search(mut list) => list.swap_remove(index).set(music_hoard, artist, meta),
}
}
}
impl ArtistMatches { impl ArtistMatches {
fn len(&self) -> usize { fn len(&self) -> usize {
self.list.len() self.list.len()
@ -58,6 +174,10 @@ impl ArtistMatches {
fn is_manual_input_mbid(&self, index: usize) -> bool { fn is_manual_input_mbid(&self, index: usize) -> bool {
self.list.is_manual_input_mbid(index) self.list.is_manual_input_mbid(index)
} }
fn set(self, music_hoard: &mut dyn IMusicHoard, index: usize) -> Result<(), musichoard::Error> {
self.list.set(music_hoard, &self.matching, index)
}
} }
impl AlbumMatches { impl AlbumMatches {
@ -76,6 +196,11 @@ impl AlbumMatches {
fn is_manual_input_mbid(&self, index: usize) -> bool { fn is_manual_input_mbid(&self, index: usize) -> bool {
self.list.is_manual_input_mbid(index) self.list.is_manual_input_mbid(index)
} }
fn set(self, music_hoard: &mut dyn IMusicHoard, index: usize) -> Result<(), musichoard::Error> {
self.list
.set(music_hoard, &self.artist, &self.matching, index)
}
} }
impl MatchStateInfo { impl MatchStateInfo {
@ -106,6 +231,13 @@ impl MatchStateInfo {
Self::Album(a) => a.is_manual_input_mbid(index), Self::Album(a) => a.is_manual_input_mbid(index),
} }
} }
fn set(self, music_hoard: &mut dyn IMusicHoard, index: usize) -> Result<(), musichoard::Error> {
match self {
Self::Artist(a) => a.set(music_hoard, index),
Self::Album(a) => a.set(music_hoard, index),
}
}
} }
pub struct MatchState { pub struct MatchState {
@ -146,8 +278,15 @@ impl AppMachine<MatchState> {
AppMachine::app_lookup_artist(self.inner, self.state.fetch, matching, mbid) AppMachine::app_lookup_artist(self.inner, self.state.fetch, matching, mbid)
} }
MatchStateInfo::Album(album_matches) => { MatchStateInfo::Album(album_matches) => {
let artist_id = &album_matches.artist;
let matching = &album_matches.matching; let matching = &album_matches.matching;
AppMachine::app_lookup_album(self.inner, self.state.fetch, matching, mbid) AppMachine::app_lookup_album(
self.inner,
self.state.fetch,
artist_id,
matching,
mbid,
)
} }
} }
} }
@ -197,6 +336,7 @@ impl IAppInteractMatch for AppMachine<MatchState> {
fn select(mut self) -> Self::APP { fn select(mut self) -> Self::APP {
if let Some(index) = self.state.state.list.selected() { if let Some(index) = self.state.state.list.selected() {
// selected() implies current exists // selected() implies current exists
if self if self
.state .state
.current .current
@ -207,7 +347,17 @@ impl IAppInteractMatch for AppMachine<MatchState> {
self.input.replace(Input::default()); self.input.replace(Input::default());
return self.into(); return self.into();
} }
if let Err(err) = self
.state
.current
.unwrap()
.set(&mut *self.inner.music_hoard, index)
{
return AppMachine::error_state(self.inner, err.to_string()).into();
}
} }
AppMachine::app_fetch_next(self.inner, self.state.fetch) AppMachine::app_fetch_next(self.inner, self.state.fetch)
} }
@ -279,6 +429,7 @@ mod tests {
} }
fn album_match() -> MatchStateInfo { fn album_match() -> MatchStateInfo {
let artist_id = ArtistId::new("Artist");
let album = AlbumMeta::new( let album = AlbumMeta::new(
AlbumId::new("Album"), AlbumId::new("Album"),
AlbumDate::new(Some(1990), Some(5), None), AlbumDate::new(Some(1990), Some(5), None),
@ -295,10 +446,11 @@ mod tests {
let album_match_2 = Match::new(100, album_2); let album_match_2 = Match::new(100, album_2);
let list = vec![album_match_1.clone(), album_match_2.clone()]; let list = vec![album_match_1.clone(), album_match_2.clone()];
MatchStateInfo::album_search(album, list) MatchStateInfo::album_search(artist_id, album, list)
} }
fn album_lookup() -> MatchStateInfo { fn album_lookup() -> MatchStateInfo {
let artist_id = ArtistId::new("Artist");
let album = AlbumMeta::new( let album = AlbumMeta::new(
AlbumId::new("Album"), AlbumId::new("Album"),
AlbumDate::new(Some(1990), Some(5), None), AlbumDate::new(Some(1990), Some(5), None),
@ -306,7 +458,7 @@ mod tests {
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
); );
let lookup = Lookup::new(album.clone()); let lookup = Lookup::new(album.clone());
MatchStateInfo::album_lookup(album, lookup) MatchStateInfo::album_lookup(artist_id, album, lookup)
} }
fn fetch_state() -> FetchState { fn fetch_state() -> FetchState {
@ -518,15 +670,21 @@ mod tests {
#[test] #[test]
fn select_manual_input_album() { fn select_manual_input_album() {
let mut mb_job_sender = MockIMbJobSender::new(); let mut mb_job_sender = MockIMbJobSender::new();
let artist_id = ArtistId::new("Artist");
let album = AlbumMeta::new("Album", 1990, None, vec![]); let album = AlbumMeta::new("Album", 1990, None, vec![]);
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid())]); let requests = VecDeque::from([MbParams::lookup_release_group(
artist_id.clone(),
album.clone(),
mbid(),
)]);
mb_job_sender mb_job_sender
.expect_submit_foreground_job() .expect_submit_foreground_job()
.with(predicate::always(), predicate::eq(requests)) .with(predicate::always(), predicate::eq(requests))
.return_once(|_, _| Ok(())); .return_once(|_, _| Ok(()));
let matches_vec: Vec<Match<AlbumMeta>> = vec![]; let matches_vec: Vec<Match<AlbumMeta>> = vec![];
let album_match = MatchStateInfo::album_search(album.clone(), matches_vec); let album_match =
MatchStateInfo::album_search(artist_id.clone(), album.clone(), matches_vec);
let matches = AppMachine::match_state( let matches = AppMachine::match_state(
inner_with_mb(music_hoard(vec![]), mb_job_sender), inner_with_mb(music_hoard(vec![]), mb_job_sender),
match_state(Some(album_match)), match_state(Some(album_match)),

View File

@ -4,7 +4,11 @@ mod selection;
pub use machine::App; pub use machine::App;
pub use selection::{Category, Delta, Selection, WidgetState}; pub use selection::{Category, Delta, Selection, WidgetState};
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, Collection}; use musichoard::collection::{
album::AlbumMeta,
artist::{ArtistId, ArtistMeta},
Collection,
};
use crate::tui::lib::interface::musicbrainz::api::Match; use crate::tui::lib::interface::musicbrainz::api::Match;
@ -220,6 +224,7 @@ pub struct ArtistMatches {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlbumMatches { pub struct AlbumMatches {
pub artist: ArtistId,
pub matching: AlbumMeta, pub matching: AlbumMeta,
pub list: ListOption<AlbumMeta>, pub list: ListOption<AlbumMeta>,
} }
@ -240,11 +245,16 @@ impl MatchStateInfo {
} }
pub fn album_search<M: Into<SearchOption<AlbumMeta>>>( pub fn album_search<M: Into<SearchOption<AlbumMeta>>>(
artist: ArtistId,
matching: AlbumMeta, matching: AlbumMeta,
list: Vec<M>, list: Vec<M>,
) -> Self { ) -> Self {
let list = ListOption::Search(list.into_iter().map(Into::into).collect()); let list = ListOption::Search(list.into_iter().map(Into::into).collect());
MatchStateInfo::Album(AlbumMatches { matching, list }) MatchStateInfo::Album(AlbumMatches {
artist,
matching,
list,
})
} }
pub fn artist_lookup<M: Into<LookupOption<ArtistMeta>>>(matching: ArtistMeta, item: M) -> Self { pub fn artist_lookup<M: Into<LookupOption<ArtistMeta>>>(matching: ArtistMeta, item: M) -> Self {
@ -252,9 +262,17 @@ impl MatchStateInfo {
MatchStateInfo::Artist(ArtistMatches { matching, list }) MatchStateInfo::Artist(ArtistMatches { matching, list })
} }
pub fn album_lookup<M: Into<LookupOption<AlbumMeta>>>(matching: AlbumMeta, item: M) -> Self { pub fn album_lookup<M: Into<LookupOption<AlbumMeta>>>(
artist: ArtistId,
matching: AlbumMeta,
item: M,
) -> Self {
let list = ListOption::Lookup(vec![item.into()]); let list = ListOption::Lookup(vec![item.into()]);
MatchStateInfo::Album(AlbumMatches { matching, list }) MatchStateInfo::Album(AlbumMatches {
artist,
matching,
list,
})
} }
} }

View File

@ -245,15 +245,15 @@ impl JobInstance {
.map(|rv| MatchStateInfo::artist_lookup(params.artist, rv)), .map(|rv| MatchStateInfo::artist_lookup(params.artist, rv)),
LookupParams::ReleaseGroup(params) => musicbrainz LookupParams::ReleaseGroup(params) => musicbrainz
.lookup_release_group(&params.mbid) .lookup_release_group(&params.mbid)
.map(|rv| MatchStateInfo::album_lookup(params.album, rv)), .map(|rv| MatchStateInfo::album_lookup(params.artist_id, params.album, rv)),
}, },
MbParams::Search(search) => match search { MbParams::Search(search) => match search {
SearchParams::Artist(params) => musicbrainz SearchParams::Artist(params) => musicbrainz
.search_artist(&params.artist) .search_artist(&params.artist)
.map(|rv| MatchStateInfo::artist_search(params.artist, rv)), .map(|rv| MatchStateInfo::artist_search(params.artist, rv)),
SearchParams::ReleaseGroup(params) => musicbrainz SearchParams::ReleaseGroup(params) => musicbrainz
.search_release_group(&params.arid, &params.album) .search_release_group(&params.artist_mbid, &params.album)
.map(|rv| MatchStateInfo::album_search(params.album, rv)), .map(|rv| MatchStateInfo::album_search(params.artist_id, params.album, rv)),
}, },
}; };
self.return_result(event_sender, result) self.return_result(event_sender, result)
@ -315,7 +315,7 @@ mod tests {
use mockall::{predicate, Sequence}; use mockall::{predicate, Sequence};
use musichoard::collection::{ use musichoard::collection::{
album::AlbumMeta, album::AlbumMeta,
artist::ArtistMeta, artist::{ArtistId, ArtistMeta},
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid}, musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
}; };
@ -397,9 +397,10 @@ mod tests {
} }
fn lookup_release_group_requests() -> VecDeque<MbParams> { fn lookup_release_group_requests() -> VecDeque<MbParams> {
let artist_id = COLLECTION[1].meta.id.clone();
let album = COLLECTION[1].albums[0].meta.clone(); let album = COLLECTION[1].albums[0].meta.clone();
let mbid = mbid(); let mbid = mbid();
VecDeque::from([MbParams::lookup_release_group(album, mbid)]) VecDeque::from([MbParams::lookup_release_group(artist_id, album, mbid)])
} }
fn search_artist_requests() -> VecDeque<MbParams> { fn search_artist_requests() -> VecDeque<MbParams> {
@ -421,15 +422,20 @@ mod tests {
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.musicbrainz); let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.musicbrainz);
let arid = mb_ref_opt_unwrap(mbref).mbid().clone(); let arid = mb_ref_opt_unwrap(mbref).mbid().clone();
let artist_id = COLLECTION[1].meta.id.clone();
let album_1 = COLLECTION[1].albums[0].meta.clone(); let album_1 = COLLECTION[1].albums[0].meta.clone();
let album_4 = COLLECTION[1].albums[3].meta.clone(); let album_4 = COLLECTION[1].albums[3].meta.clone();
VecDeque::from([ VecDeque::from([
MbParams::search_release_group(arid.clone(), album_1), MbParams::search_release_group(artist_id.clone(), arid.clone(), album_1),
MbParams::search_release_group(arid.clone(), album_4), MbParams::search_release_group(artist_id.clone(), arid.clone(), album_4),
]) ])
} }
fn album_artist_id() -> ArtistId {
COLLECTION[1].meta.id.clone()
}
fn album_arid_expectation() -> Mbid { fn album_arid_expectation() -> Mbid {
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.musicbrainz); let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.musicbrainz);
mb_ref_opt_unwrap(mbref).mbid().clone() mb_ref_opt_unwrap(mbref).mbid().clone()
@ -611,7 +617,11 @@ mod tests {
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
let result = result_receiver.try_recv().unwrap(); let result = result_receiver.try_recv().unwrap();
assert_eq!(result, Ok(MatchStateInfo::album_lookup(album, lookup))); let artist_id = album_artist_id();
assert_eq!(
result,
Ok(MatchStateInfo::album_lookup(artist_id, album, lookup))
);
} }
fn search_artist_expectation( fn search_artist_expectation(
@ -701,11 +711,27 @@ mod tests {
let result = daemon.execute_next_job(); let result = daemon.execute_next_job();
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
let result = result_receiver.try_recv().unwrap(); let artist_id = album_artist_id();
assert_eq!(result, Ok(MatchStateInfo::album_search(album_1, matches_1)));
let result = result_receiver.try_recv().unwrap(); let result = result_receiver.try_recv().unwrap();
assert_eq!(result, Ok(MatchStateInfo::album_search(album_4, matches_4))); assert_eq!(
result,
Ok(MatchStateInfo::album_search(
artist_id.clone(),
album_1,
matches_1
))
);
let result = result_receiver.try_recv().unwrap();
assert_eq!(
result,
Ok(MatchStateInfo::album_search(
artist_id.clone(),
album_4,
matches_4
))
);
} }
#[test] #[test]

View File

@ -1,6 +1,10 @@
use std::{collections::VecDeque, fmt, sync::mpsc}; use std::{collections::VecDeque, fmt, sync::mpsc};
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid}; use musichoard::collection::{
album::AlbumMeta,
artist::{ArtistId, ArtistMeta},
musicbrainz::Mbid,
};
use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as MbApiError}; use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as MbApiError};
@ -60,6 +64,7 @@ pub struct LookupArtistParams {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct LookupReleaseGroupParams { pub struct LookupReleaseGroupParams {
pub artist_id: ArtistId,
pub album: AlbumMeta, pub album: AlbumMeta,
pub mbid: Mbid, pub mbid: Mbid,
} }
@ -77,7 +82,8 @@ pub struct SearchArtistParams {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct SearchReleaseGroupParams { pub struct SearchReleaseGroupParams {
pub arid: Mbid, pub artist_id: ArtistId,
pub artist_mbid: Mbid,
pub album: AlbumMeta, pub album: AlbumMeta,
} }
@ -86,8 +92,9 @@ impl MbParams {
MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid })) MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid }))
} }
pub fn lookup_release_group(album: AlbumMeta, mbid: Mbid) -> Self { pub fn lookup_release_group(artist_id: ArtistId, album: AlbumMeta, mbid: Mbid) -> Self {
MbParams::Lookup(LookupParams::ReleaseGroup(LookupReleaseGroupParams { MbParams::Lookup(LookupParams::ReleaseGroup(LookupReleaseGroupParams {
artist_id,
album, album,
mbid, mbid,
})) }))
@ -97,9 +104,10 @@ impl MbParams {
MbParams::Search(SearchParams::Artist(SearchArtistParams { artist })) MbParams::Search(SearchParams::Artist(SearchArtistParams { artist }))
} }
pub fn search_release_group(arid: Mbid, album: AlbumMeta) -> Self { pub fn search_release_group(artist_id: ArtistId, artist_mbid: Mbid, album: AlbumMeta) -> Self {
MbParams::Search(SearchParams::ReleaseGroup(SearchReleaseGroupParams { MbParams::Search(SearchParams::ReleaseGroup(SearchReleaseGroupParams {
arid, artist_id,
artist_mbid,
album, album,
})) }))
} }

View File

@ -2,7 +2,12 @@ pub mod external;
pub mod interface; pub mod interface;
use musichoard::{ use musichoard::{
collection::Collection, collection::{
album::{AlbumId, AlbumPrimaryType, AlbumSecondaryType},
artist::ArtistId,
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption},
Collection,
},
interface::{database::IDatabase, library::ILibrary}, interface::{database::IDatabase, library::ILibrary},
IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, MusicHoard, IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, MusicHoard,
}; };
@ -15,6 +20,30 @@ pub trait IMusicHoard {
fn rescan_library(&mut self) -> Result<(), musichoard::Error>; fn rescan_library(&mut self) -> Result<(), musichoard::Error>;
fn reload_database(&mut self) -> Result<(), musichoard::Error>; fn reload_database(&mut self) -> Result<(), musichoard::Error>;
fn get_collection(&self) -> &Collection; fn get_collection(&self) -> &Collection;
fn set_artist_musicbrainz(
&mut self,
id: &ArtistId,
mbref: MbRefOption<MbArtistRef>,
) -> Result<(), musichoard::Error>;
fn set_album_musicbrainz(
&mut self,
artist_id: &ArtistId,
album_id: &AlbumId,
mbref: MbRefOption<MbAlbumRef>,
) -> Result<(), musichoard::Error>;
fn set_album_primary_type(
&mut self,
artist_id: &ArtistId,
album_id: &AlbumId,
primary_type: Option<AlbumPrimaryType>,
) -> Result<(), musichoard::Error>;
fn set_album_secondary_types(
&mut self,
artist_id: &ArtistId,
album_id: &AlbumId,
secondary_types: Vec<AlbumSecondaryType>,
) -> Result<(), musichoard::Error>;
} }
// GRCOV_EXCL_START // GRCOV_EXCL_START
@ -30,5 +59,50 @@ impl<Database: IDatabase, Library: ILibrary> IMusicHoard for MusicHoard<Database
fn get_collection(&self) -> &Collection { fn get_collection(&self) -> &Collection {
<Self as IMusicHoardBase>::get_collection(self) <Self as IMusicHoardBase>::get_collection(self)
} }
fn set_artist_musicbrainz(
&mut self,
id: &ArtistId,
mbref: MbRefOption<MbArtistRef>,
) -> Result<(), musichoard::Error> {
<Self as IMusicHoardDatabase>::set_artist_musicbrainz(self, id, mbref)
}
fn set_album_musicbrainz(
&mut self,
artist_id: &ArtistId,
album_id: &AlbumId,
mbref: MbRefOption<MbAlbumRef>,
) -> Result<(), musichoard::Error> {
<Self as IMusicHoardDatabase>::set_album_musicbrainz(self, artist_id, album_id, mbref)
}
fn set_album_primary_type(
&mut self,
artist_id: &ArtistId,
album_id: &AlbumId,
primary_type: Option<AlbumPrimaryType>,
) -> Result<(), musichoard::Error> {
<Self as IMusicHoardDatabase>::set_album_primary_type(
self,
artist_id,
album_id,
primary_type,
)
}
fn set_album_secondary_types(
&mut self,
artist_id: &ArtistId,
album_id: &AlbumId,
secondary_types: Vec<AlbumSecondaryType>,
) -> Result<(), musichoard::Error> {
<Self as IMusicHoardDatabase>::set_album_secondary_types(
self,
artist_id,
album_id,
secondary_types,
)
}
} }
// GRCOV_EXCL_STOP // GRCOV_EXCL_STOP

View File

@ -373,6 +373,10 @@ mod tests {
info info
} }
fn album_artist_id() -> ArtistId {
ArtistId::new("Artist")
}
fn album_meta() -> AlbumMeta { fn album_meta() -> AlbumMeta {
AlbumMeta::new( AlbumMeta::new(
AlbumId::new("An Album"), AlbumId::new("An Album"),
@ -383,21 +387,23 @@ mod tests {
} }
fn album_matches() -> MatchStateInfo { fn album_matches() -> MatchStateInfo {
let artist_id = album_artist_id();
let album = album_meta(); let album = album_meta();
let album_match = Match::new(80, album.clone()); let album_match = Match::new(80, album.clone());
let list = vec![album_match.clone(), album_match.clone()]; let list = vec![album_match.clone(), album_match.clone()];
let mut info = MatchStateInfo::album_search(album, list); let mut info = MatchStateInfo::album_search(artist_id, album, list);
info.push_cannot_have_mbid(); info.push_cannot_have_mbid();
info.push_manual_input_mbid(); info.push_manual_input_mbid();
info info
} }
fn album_lookup() -> MatchStateInfo { fn album_lookup() -> MatchStateInfo {
let artist_id = album_artist_id();
let album = album_meta(); let album = album_meta();
let album_lookup = Lookup::new(album.clone()); let album_lookup = Lookup::new(album.clone());
let mut info = MatchStateInfo::album_lookup(album, album_lookup); let mut info = MatchStateInfo::album_lookup(artist_id, album, album_lookup);
info.push_cannot_have_mbid(); info.push_cannot_have_mbid();
info.push_manual_input_mbid(); info.push_manual_input_mbid();
info info