Provide a keyboard shortcut to pull all release groups of an artist (#233)
Part 3 of #160 Closes #160 Reviewed-on: #233
This commit is contained in:
parent
76b7e7bd22
commit
429294c6a5
@ -144,19 +144,18 @@ impl AlbumStatus {
|
||||
}
|
||||
|
||||
impl Album {
|
||||
pub fn new<Id: Into<AlbumId>, Date: Into<AlbumDate>>(
|
||||
id: Id,
|
||||
date: Date,
|
||||
primary_type: Option<AlbumPrimaryType>,
|
||||
secondary_types: Vec<AlbumSecondaryType>,
|
||||
) -> Self {
|
||||
let info = AlbumInfo::new(MbRefOption::None, primary_type, secondary_types);
|
||||
pub fn new<Id: Into<AlbumId>>(id: Id) -> Self {
|
||||
Album {
|
||||
meta: AlbumMeta::new(id, date, info),
|
||||
meta: AlbumMeta::new(id),
|
||||
tracks: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_date<Date: Into<AlbumDate>>(mut self, date: Date) -> Self {
|
||||
self.meta.date = date.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_status(&self) -> AlbumStatus {
|
||||
AlbumStatus::from_tracks(&self.tracks)
|
||||
}
|
||||
@ -183,19 +182,25 @@ impl Merge for Album {
|
||||
}
|
||||
|
||||
impl AlbumMeta {
|
||||
pub fn new<Id: Into<AlbumId>, Date: Into<AlbumDate>>(
|
||||
id: Id,
|
||||
date: Date,
|
||||
info: AlbumInfo,
|
||||
) -> Self {
|
||||
pub fn new<Id: Into<AlbumId>>(id: Id) -> Self {
|
||||
AlbumMeta {
|
||||
id: id.into(),
|
||||
date: date.into(),
|
||||
date: AlbumDate::default(),
|
||||
seq: AlbumSeq::default(),
|
||||
info,
|
||||
info: AlbumInfo::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_date<Date: Into<AlbumDate>>(mut self, date: Date) -> Self {
|
||||
self.date = date.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_info(mut self, info: AlbumInfo) -> Self {
|
||||
self.info = info;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) {
|
||||
(&self.date, &self.seq, &self.id)
|
||||
}
|
||||
@ -258,7 +263,9 @@ 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);
|
||||
self.secondary_types.merge_in_place(other.secondary_types);
|
||||
if self.secondary_types.is_empty() {
|
||||
self.secondary_types = other.secondary_types;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,13 +318,13 @@ mod tests {
|
||||
let album_id_1 = AlbumId {
|
||||
title: String::from("album z"),
|
||||
};
|
||||
let mut album_1 = Album::new(album_id_1, date.clone(), None, vec![]);
|
||||
let mut album_1 = Album::new(album_id_1).with_date(date.clone());
|
||||
album_1.meta.set_seq(AlbumSeq(1));
|
||||
|
||||
let album_id_2 = AlbumId {
|
||||
title: String::from("album a"),
|
||||
};
|
||||
let mut album_2 = Album::new(album_id_2, date.clone(), None, vec![]);
|
||||
let mut album_2 = Album::new(album_id_2).with_date(date.clone());
|
||||
album_2.meta.set_seq(AlbumSeq(2));
|
||||
|
||||
assert_ne!(album_1, album_2);
|
||||
@ -327,7 +334,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn set_clear_seq() {
|
||||
let mut album = Album::new("An album", AlbumDate::default(), None, vec![]);
|
||||
let mut album = Album::new("An album");
|
||||
|
||||
assert_eq!(album.meta.seq, AlbumSeq(0));
|
||||
|
||||
|
@ -46,6 +46,14 @@ pub enum MbRefOption<T> {
|
||||
}
|
||||
|
||||
impl<T> MbRefOption<T> {
|
||||
pub fn is_some(&self) -> bool {
|
||||
matches!(self, MbRefOption::Some(_))
|
||||
}
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, MbRefOption::None)
|
||||
}
|
||||
|
||||
pub fn or(self, optb: MbRefOption<T>) -> MbRefOption<T> {
|
||||
match (&self, &optb) {
|
||||
(MbRefOption::Some(_), _) | (MbRefOption::CannotHaveMbid, MbRefOption::None) => self,
|
||||
|
@ -34,6 +34,7 @@ pub trait IMusicHoardBasePrivate {
|
||||
artist_id: &ArtistId,
|
||||
) -> Result<&'a mut Artist, Error>;
|
||||
|
||||
fn get_album<'a>(artist: &'a mut Artist, album_id: &AlbumId) -> Option<&'a Album>;
|
||||
fn get_album_mut<'a>(artist: &'a mut Artist, album_id: &AlbumId) -> Option<&'a mut Album>;
|
||||
fn get_album_mut_or_err<'a>(
|
||||
artist: &'a mut Artist,
|
||||
@ -79,6 +80,10 @@ impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library>
|
||||
})
|
||||
}
|
||||
|
||||
fn get_album<'a>(artist: &'a mut Artist, album_id: &AlbumId) -> Option<&'a Album> {
|
||||
artist.albums.iter().find(|a| &a.meta.id == album_id)
|
||||
}
|
||||
|
||||
fn get_album_mut<'a>(artist: &'a mut Artist, album_id: &AlbumId) -> Option<&'a mut Album> {
|
||||
artist.albums.iter_mut().find(|a| &a.meta.id == album_id)
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
use std::mem;
|
||||
|
||||
use crate::{
|
||||
collection::{album::AlbumInfo, artist::ArtistInfo, merge::Merge},
|
||||
collection::{
|
||||
album::{AlbumInfo, AlbumMeta},
|
||||
artist::ArtistInfo,
|
||||
merge::Merge,
|
||||
},
|
||||
core::{
|
||||
collection::{
|
||||
album::{Album, AlbumId, AlbumSeq},
|
||||
@ -57,6 +61,17 @@ pub trait IMusicHoardDatabase {
|
||||
property: S,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn add_album<ArtistIdRef: AsRef<ArtistId>>(
|
||||
&mut self,
|
||||
artist_id: ArtistIdRef,
|
||||
album_meta: AlbumMeta,
|
||||
) -> Result<(), Error>;
|
||||
fn remove_album<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||
&mut self,
|
||||
artist_id: ArtistIdRef,
|
||||
album_id: AlbumIdRef,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn set_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||
&mut self,
|
||||
artist_id: ArtistIdRef,
|
||||
@ -68,15 +83,15 @@ pub trait IMusicHoardDatabase {
|
||||
artist_id: ArtistIdRef,
|
||||
album_id: AlbumIdRef,
|
||||
) -> Result<(), Error>;
|
||||
fn merge_album_info<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||
fn merge_album_info<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||
&mut self,
|
||||
artist_id: Id,
|
||||
artist_id: ArtistIdRef,
|
||||
album_id: AlbumIdRef,
|
||||
info: AlbumInfo,
|
||||
) -> Result<(), Error>;
|
||||
fn clear_album_info<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||
fn clear_album_info<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||
&mut self,
|
||||
artist_id: Id,
|
||||
artist_id: ArtistIdRef,
|
||||
album_id: AlbumIdRef,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
@ -194,6 +209,39 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_album<ArtistIdRef: AsRef<ArtistId>>(
|
||||
&mut self,
|
||||
artist_id: ArtistIdRef,
|
||||
album_meta: AlbumMeta,
|
||||
) -> Result<(), Error> {
|
||||
let album = Album {
|
||||
meta: album_meta,
|
||||
tracks: vec![],
|
||||
};
|
||||
self.update_artist(artist_id.as_ref(), |artist| {
|
||||
if Self::get_album(artist, &album.meta.id).is_none() {
|
||||
artist.albums.push(album);
|
||||
artist.albums.sort_unstable();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_album<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||
&mut self,
|
||||
artist_id: ArtistIdRef,
|
||||
album_id: AlbumIdRef,
|
||||
) -> Result<(), Error> {
|
||||
self.update_artist(artist_id.as_ref(), |artist| {
|
||||
let index_opt = artist
|
||||
.albums
|
||||
.iter()
|
||||
.position(|a| &a.meta.id == album_id.as_ref());
|
||||
if let Some(index) = index_opt {
|
||||
artist.albums.remove(index);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn set_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||
&mut self,
|
||||
artist_id: ArtistIdRef,
|
||||
@ -344,7 +392,7 @@ mod tests {
|
||||
musicbrainz::{MbArtistRef, MbRefOption},
|
||||
},
|
||||
core::{
|
||||
collection::{album::AlbumDate, artist::ArtistId},
|
||||
collection::artist::ArtistId,
|
||||
interface::database::{self, MockIDatabase},
|
||||
musichoard::{base::IMusicHoardBase, NoLibrary},
|
||||
testmod::FULL_COLLECTION,
|
||||
@ -604,6 +652,62 @@ mod tests {
|
||||
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn album_new_delete() {
|
||||
let album_id = AlbumId::new("an album");
|
||||
let album_meta = AlbumMeta::new(album_id.clone());
|
||||
let album_id_2 = AlbumId::new("another album");
|
||||
let album_meta_2 = AlbumMeta::new(album_id_2);
|
||||
|
||||
let collection = FULL_COLLECTION.to_owned();
|
||||
let artist_id = collection[0].meta.id.clone();
|
||||
let mut with_album = collection.clone();
|
||||
with_album[0].albums.push(Album::new(album_id));
|
||||
with_album[0].albums.sort_unstable();
|
||||
|
||||
let mut database = MockIDatabase::new();
|
||||
let mut seq = Sequence::new();
|
||||
database
|
||||
.expect_load()
|
||||
.times(1)
|
||||
.times(1)
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|| Ok(FULL_COLLECTION.to_owned()));
|
||||
database
|
||||
.expect_save()
|
||||
.times(1)
|
||||
.in_sequence(&mut seq)
|
||||
.with(predicate::eq(with_album.clone()))
|
||||
.returning(|_| Ok(()));
|
||||
database
|
||||
.expect_save()
|
||||
.times(1)
|
||||
.in_sequence(&mut seq)
|
||||
.with(predicate::eq(collection.clone()))
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
let mut music_hoard = MusicHoard::database(database).unwrap();
|
||||
assert_eq!(music_hoard.collection, collection);
|
||||
|
||||
assert!(music_hoard
|
||||
.add_album(&artist_id, album_meta.clone())
|
||||
.is_ok());
|
||||
assert_eq!(music_hoard.collection, with_album);
|
||||
|
||||
assert!(music_hoard
|
||||
.add_album(&artist_id, album_meta.clone())
|
||||
.is_ok());
|
||||
assert_eq!(music_hoard.collection, with_album);
|
||||
|
||||
assert!(music_hoard
|
||||
.remove_album(&artist_id, &album_meta_2.id)
|
||||
.is_ok());
|
||||
assert_eq!(music_hoard.collection, with_album);
|
||||
|
||||
assert!(music_hoard.remove_album(&artist_id, &album_meta.id).is_ok());
|
||||
assert_eq!(music_hoard.collection, collection);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_clear_album_seq() {
|
||||
let mut database = MockIDatabase::new();
|
||||
@ -613,12 +717,7 @@ mod tests {
|
||||
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(),
|
||||
AlbumDate::default(),
|
||||
None,
|
||||
vec![],
|
||||
));
|
||||
database_result[0].albums.push(Album::new(album_id.clone()));
|
||||
|
||||
database
|
||||
.expect_load()
|
||||
@ -658,12 +757,7 @@ mod tests {
|
||||
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(),
|
||||
AlbumDate::default(),
|
||||
None,
|
||||
vec![],
|
||||
));
|
||||
database_result[0].albums.push(Album::new(album_id.clone()));
|
||||
|
||||
database
|
||||
.expect_load()
|
||||
|
@ -109,7 +109,7 @@ impl<Database, Library: ILibrary> MusicHoard<Database, Library> {
|
||||
{
|
||||
Some(album) => album.tracks.push(track),
|
||||
None => {
|
||||
let mut album = Album::new(album_id, album_date, None, vec![]);
|
||||
let mut album = Album::new(album_id).with_date(album_date);
|
||||
album.tracks.push(track);
|
||||
artist.albums.push(album);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use musichoard::collection::{
|
||||
album::{Album, AlbumId},
|
||||
album::{Album, AlbumId, AlbumMeta},
|
||||
artist::{Artist, ArtistId, ArtistMeta},
|
||||
musicbrainz::{IMusicBrainzRef, MbArtistRef, MbRefOption, Mbid},
|
||||
};
|
||||
@ -13,48 +13,65 @@ use musichoard::collection::{
|
||||
use crate::tui::{
|
||||
app::{
|
||||
machine::{match_state::MatchState, App, AppInner, AppMachine},
|
||||
selection::KeySelection,
|
||||
AppPublicState, AppState, Category, IAppEventFetch, IAppInteractFetch,
|
||||
},
|
||||
lib::interface::musicbrainz::daemon::{
|
||||
Error as DaemonError, IMbJobSender, MbApiResult, MbParams, MbReturn, ResultSender,
|
||||
EntityList, Error as DaemonError, IMbJobSender, MbApiResult, MbParams, MbReturn,
|
||||
ResultSender,
|
||||
},
|
||||
};
|
||||
|
||||
pub type FetchReceiver = mpsc::Receiver<MbApiResult>;
|
||||
pub type MbApiReceiver = mpsc::Receiver<MbApiResult>;
|
||||
pub struct FetchState {
|
||||
fetch_rx: FetchReceiver,
|
||||
lookup_rx: Option<FetchReceiver>,
|
||||
search_rx: Option<MbApiReceiver>,
|
||||
lookup_rx: Option<MbApiReceiver>,
|
||||
fetch_rx: Option<MbApiReceiver>,
|
||||
}
|
||||
|
||||
struct SubmitJob {
|
||||
fetch: FetchState,
|
||||
requests: VecDeque<MbParams>,
|
||||
}
|
||||
|
||||
macro_rules! try_recv_mb_api_receiver {
|
||||
($rx:expr) => {
|
||||
if let Some(rx) = &($rx) {
|
||||
match rx.try_recv() {
|
||||
x @ Ok(_) | x @ Err(TryRecvError::Empty) => return x,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
($rx).take();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl FetchState {
|
||||
pub fn new(fetch_rx: FetchReceiver) -> Self {
|
||||
pub fn search(search_rx: MbApiReceiver) -> Self {
|
||||
FetchState {
|
||||
fetch_rx,
|
||||
search_rx: Some(search_rx),
|
||||
lookup_rx: None,
|
||||
fetch_rx: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch(fetch_rx: MbApiReceiver) -> Self {
|
||||
FetchState {
|
||||
search_rx: None,
|
||||
lookup_rx: None,
|
||||
fetch_rx: Some(fetch_rx),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_recv(&mut self) -> Result<MbApiResult, TryRecvError> {
|
||||
if let Some(lookup_rx) = &self.lookup_rx {
|
||||
match lookup_rx.try_recv() {
|
||||
x @ Ok(_) | x @ Err(TryRecvError::Empty) => return x,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
self.lookup_rx.take();
|
||||
}
|
||||
}
|
||||
try_recv_mb_api_receiver!(self.lookup_rx);
|
||||
try_recv_mb_api_receiver!(self.search_rx);
|
||||
|
||||
match &self.fetch_rx {
|
||||
Some(fetch_rx) => fetch_rx.try_recv(),
|
||||
None => Err(TryRecvError::Disconnected),
|
||||
}
|
||||
self.fetch_rx.try_recv()
|
||||
}
|
||||
}
|
||||
|
||||
enum FetchError {
|
||||
NothingToFetch,
|
||||
SubmitError(DaemonError),
|
||||
}
|
||||
|
||||
impl From<DaemonError> for FetchError {
|
||||
fn from(value: DaemonError) -> Self {
|
||||
FetchError::SubmitError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,75 +85,137 @@ impl AppMachine<FetchState> {
|
||||
}
|
||||
|
||||
fn app_fetch_new(inner: AppInner) -> App {
|
||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||
let job = match Self::fetch_job(&inner, rx) {
|
||||
Ok(job) => job,
|
||||
Err(err) => return AppMachine::error_state(inner, err.to_string()).into(),
|
||||
};
|
||||
|
||||
if job.requests.is_empty() {
|
||||
return AppMachine::browse_state(inner).into();
|
||||
}
|
||||
|
||||
match inner.musicbrainz.submit_background_job(tx, job.requests) {
|
||||
Ok(()) => AppMachine::fetch_state(inner, job.fetch).into(),
|
||||
Err(err) => AppMachine::error_state(inner, err.to_string()).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_job(inner: &AppInner, rx: MbApiReceiver) -> Result<SubmitJob, &'static str> {
|
||||
let coll = inner.music_hoard.get_collection();
|
||||
|
||||
let artist = match inner.selection.state_artist(coll) {
|
||||
Some(artist_state) => &coll[artist_state.index],
|
||||
None => {
|
||||
let err = "cannot fetch artist: no artist selected";
|
||||
return AppMachine::error_state(inner, err).into();
|
||||
}
|
||||
None => return Err("cannot fetch: no artist selected"),
|
||||
};
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel::<MbApiResult>();
|
||||
let fetch = FetchState::new(fetch_rx);
|
||||
|
||||
let mb = &*inner.musicbrainz;
|
||||
let result = match inner.selection.category() {
|
||||
Category::Artist => Self::submit_search_artist_job(mb, fetch_tx, artist),
|
||||
let requests = match inner.selection.category() {
|
||||
Category::Artist => {
|
||||
let fetch: FetchState;
|
||||
let mut requests = Self::search_artist_job(artist);
|
||||
if requests.is_empty() {
|
||||
fetch = FetchState::fetch(rx);
|
||||
requests = Self::browse_release_group_job(&artist.meta.info.musicbrainz);
|
||||
} else {
|
||||
fetch = FetchState::search(rx);
|
||||
}
|
||||
SubmitJob { fetch, requests }
|
||||
}
|
||||
_ => {
|
||||
let arid = match artist.meta.info.musicbrainz {
|
||||
MbRefOption::Some(ref mbref) => mbref,
|
||||
_ => {
|
||||
let err = "cannot fetch album: artist has no MBID";
|
||||
return AppMachine::error_state(inner, err).into();
|
||||
}
|
||||
_ => return Err("cannot fetch album: artist has no MBID"),
|
||||
};
|
||||
let album = match inner.selection.state_album(coll) {
|
||||
Some(album_state) => &artist.albums[album_state.index],
|
||||
None => {
|
||||
let err = "cannot fetch album: no album selected";
|
||||
return AppMachine::error_state(inner, err).into();
|
||||
}
|
||||
None => return Err("cannot fetch album: no album selected"),
|
||||
};
|
||||
let artist_id = &artist.meta.id;
|
||||
Self::submit_search_release_group_job(mb, fetch_tx, artist_id, arid, album)
|
||||
SubmitJob {
|
||||
fetch: FetchState::search(rx),
|
||||
requests: Self::search_release_group_job(artist_id, arid, album),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(()) => AppMachine::fetch_state(inner, fetch).into(),
|
||||
Err(FetchError::NothingToFetch) => AppMachine::browse_state(inner).into(),
|
||||
Err(FetchError::SubmitError(daemon_err)) => {
|
||||
AppMachine::error_state(inner, daemon_err.to_string()).into()
|
||||
}
|
||||
}
|
||||
Ok(requests)
|
||||
}
|
||||
|
||||
pub fn app_fetch_next(inner: AppInner, mut fetch: FetchState) -> App {
|
||||
match fetch.try_recv() {
|
||||
Ok(fetch_result) => match fetch_result {
|
||||
Ok(retval) => Self::handle_mb_api_return(inner, fetch, retval),
|
||||
Err(fetch_err) => {
|
||||
AppMachine::error_state(inner, format!("fetch failed: {fetch_err}")).into()
|
||||
pub fn app_fetch_next(mut inner: AppInner, mut fetch: FetchState) -> App {
|
||||
loop {
|
||||
let app: App = match fetch.try_recv() {
|
||||
Ok(fetch_result) => match fetch_result {
|
||||
Ok(MbReturn::Match(next_match)) => {
|
||||
AppMachine::match_state(inner, MatchState::new(next_match, fetch)).into()
|
||||
}
|
||||
Ok(MbReturn::Fetch(list)) => {
|
||||
match Self::apply_fetch_results(&mut inner, list) {
|
||||
Ok(()) => continue,
|
||||
Err(err) => AppMachine::error_state(inner, err.to_string()).into(),
|
||||
}
|
||||
}
|
||||
Err(fetch_err) => {
|
||||
AppMachine::error_state(inner, format!("fetch failed: {fetch_err}")).into()
|
||||
}
|
||||
},
|
||||
Err(TryRecvError::Empty) => AppMachine::fetch_state(inner, fetch).into(),
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
if fetch.fetch_rx.is_some() {
|
||||
AppMachine::browse_state(inner).into()
|
||||
} else {
|
||||
Self::app_fetch_new(inner)
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(recv_err) => match recv_err {
|
||||
TryRecvError::Empty => AppMachine::fetch_state(inner, fetch).into(),
|
||||
TryRecvError::Disconnected => Self::app_fetch_new(inner),
|
||||
},
|
||||
};
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mb_api_return(inner: AppInner, fetch: FetchState, retval: MbReturn) -> App {
|
||||
match retval {
|
||||
MbReturn::Match(next_match) => {
|
||||
AppMachine::match_state(inner, MatchState::new(next_match, fetch)).into()
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
fn apply_fetch_results(
|
||||
inner: &mut AppInner,
|
||||
list: EntityList,
|
||||
) -> Result<(), musichoard::Error> {
|
||||
match list {
|
||||
EntityList::Album(fetch_albums) => Self::apply_album_results(inner, fetch_albums),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_album_results(
|
||||
inner: &mut AppInner,
|
||||
fetch_albums: Vec<AlbumMeta>,
|
||||
) -> Result<(), musichoard::Error> {
|
||||
let coll = inner.music_hoard.get_collection();
|
||||
|
||||
let artist_state = inner.selection.state_artist(coll).unwrap();
|
||||
let artist = &coll[artist_state.index];
|
||||
let selection = KeySelection::get(coll, &inner.selection);
|
||||
|
||||
let artist_id = &artist.meta.id.clone();
|
||||
for new in Self::new_albums(fetch_albums, &artist.albums).into_iter() {
|
||||
inner.music_hoard.add_album(artist_id, new)?;
|
||||
}
|
||||
|
||||
let coll = inner.music_hoard.get_collection();
|
||||
inner.selection.select_by_id(coll, selection);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_albums(fetch_albums: Vec<AlbumMeta>, albums: &[Album]) -> Vec<AlbumMeta> {
|
||||
let mut new_albums = vec![];
|
||||
for alb in fetch_albums.into_iter() {
|
||||
let existing = albums.iter().find(|old| Self::album_match(&old.meta, &alb));
|
||||
if existing.is_none() {
|
||||
new_albums.push(alb);
|
||||
}
|
||||
}
|
||||
new_albums
|
||||
}
|
||||
|
||||
fn album_match(old: &AlbumMeta, new: &AlbumMeta) -> bool {
|
||||
old.info.musicbrainz.is_some() && (old.info.musicbrainz == new.info.musicbrainz)
|
||||
}
|
||||
|
||||
pub fn app_lookup_artist(
|
||||
inner: AppInner,
|
||||
fetch: FetchState,
|
||||
@ -178,36 +257,26 @@ impl AppMachine<FetchState> {
|
||||
Self::app_fetch_next(inner, fetch)
|
||||
}
|
||||
|
||||
fn submit_search_artist_job(
|
||||
musicbrainz: &dyn IMbJobSender,
|
||||
result_sender: ResultSender,
|
||||
artist: &Artist,
|
||||
) -> Result<(), FetchError> {
|
||||
let requests = match artist.meta.info.musicbrainz {
|
||||
fn search_artist_job(artist: &Artist) -> VecDeque<MbParams> {
|
||||
match artist.meta.info.musicbrainz {
|
||||
MbRefOption::Some(ref arid) => {
|
||||
Self::search_albums_requests(&artist.meta.id, arid, &artist.albums)
|
||||
}
|
||||
MbRefOption::CannotHaveMbid => VecDeque::new(),
|
||||
MbRefOption::None => Self::search_artist_request(&artist.meta),
|
||||
};
|
||||
if requests.is_empty() {
|
||||
return Err(FetchError::NothingToFetch);
|
||||
}
|
||||
Ok(musicbrainz.submit_background_job(result_sender, requests)?)
|
||||
}
|
||||
|
||||
fn submit_search_release_group_job(
|
||||
musicbrainz: &dyn IMbJobSender,
|
||||
result_sender: ResultSender,
|
||||
fn search_release_group_job(
|
||||
artist_id: &ArtistId,
|
||||
artist_mbid: &MbArtistRef,
|
||||
album: &Album,
|
||||
) -> Result<(), FetchError> {
|
||||
if !matches!(album.meta.info.musicbrainz, MbRefOption::None) {
|
||||
return Err(FetchError::NothingToFetch);
|
||||
}
|
||||
let requests = Self::search_albums_requests(artist_id, artist_mbid, slice::from_ref(album));
|
||||
Ok(musicbrainz.submit_background_job(result_sender, requests)?)
|
||||
) -> VecDeque<MbParams> {
|
||||
Self::search_albums_requests(artist_id, artist_mbid, slice::from_ref(album))
|
||||
}
|
||||
|
||||
fn search_artist_request(meta: &ArtistMeta) -> VecDeque<MbParams> {
|
||||
VecDeque::from([MbParams::search_artist(meta.clone())])
|
||||
}
|
||||
|
||||
fn search_albums_requests(
|
||||
@ -218,15 +287,22 @@ impl AppMachine<FetchState> {
|
||||
let arid = arid.mbid();
|
||||
albums
|
||||
.iter()
|
||||
.filter(|album| matches!(album.meta.info.musicbrainz, MbRefOption::None))
|
||||
.filter(|album| album.meta.info.musicbrainz.is_none())
|
||||
.map(|album| {
|
||||
MbParams::search_release_group(artist.clone(), arid.clone(), album.meta.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn search_artist_request(meta: &ArtistMeta) -> VecDeque<MbParams> {
|
||||
VecDeque::from([MbParams::search_artist(meta.clone())])
|
||||
fn browse_release_group_job(mbopt: &MbRefOption<MbArtistRef>) -> VecDeque<MbParams> {
|
||||
match mbopt {
|
||||
MbRefOption::Some(mbref) => Self::browse_release_group_request(mbref),
|
||||
_ => VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn browse_release_group_request(mbref: &MbArtistRef) -> VecDeque<MbParams> {
|
||||
VecDeque::from([MbParams::browse_release_group(mbref.mbid().clone())])
|
||||
}
|
||||
|
||||
fn submit_lookup_artist_job(
|
||||
@ -294,7 +370,7 @@ mod tests {
|
||||
|
||||
use crate::tui::{
|
||||
app::{
|
||||
machine::tests::{inner, music_hoard},
|
||||
machine::tests::{inner, inner_with_mb, music_hoard},
|
||||
Delta, EntityMatches, IApp, IAppAccess, IAppInteractBrowse, MatchOption,
|
||||
},
|
||||
lib::interface::musicbrainz::{self, api::Entity, daemon::MockIMbJobSender},
|
||||
@ -312,7 +388,7 @@ mod tests {
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
let (lookup_tx, lookup_rx) = mpsc::channel();
|
||||
|
||||
let mut fetch = FetchState::new(fetch_rx);
|
||||
let mut fetch = FetchState::search(fetch_rx);
|
||||
fetch.lookup_rx.replace(lookup_rx);
|
||||
|
||||
let artist = COLLECTION[3].meta.clone();
|
||||
@ -494,7 +570,7 @@ mod tests {
|
||||
let inner = AppInner::new(music_hoard, mb_job_sender);
|
||||
|
||||
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
||||
let fetch = FetchState::new(fetch_rx);
|
||||
let fetch = FetchState::search(fetch_rx);
|
||||
|
||||
AppMachine::app_lookup_album(inner, fetch, &artist_id, &album_id, mbid());
|
||||
}
|
||||
@ -548,7 +624,7 @@ mod tests {
|
||||
let inner = AppInner::new(music_hoard, mb_job_sender);
|
||||
|
||||
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
||||
let fetch = FetchState::new(fetch_rx);
|
||||
let fetch = FetchState::search(fetch_rx);
|
||||
|
||||
AppMachine::app_lookup_artist(inner, fetch, &artist, mbid());
|
||||
}
|
||||
@ -597,14 +673,14 @@ mod tests {
|
||||
let inner = AppInner::new(music_hoard, mb_job_sender);
|
||||
|
||||
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
||||
let fetch = FetchState::new(fetch_rx);
|
||||
let fetch = FetchState::search(fetch_rx);
|
||||
|
||||
let app = AppMachine::app_lookup_artist(inner, fetch, &artist, mbid());
|
||||
assert!(matches!(app, AppState::Error(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_ok_fetch_ok() {
|
||||
fn recv_ok_match_ok() {
|
||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||
|
||||
let artist = COLLECTION[3].meta.clone();
|
||||
@ -615,7 +691,7 @@ mod tests {
|
||||
tx.send(fetch_result).unwrap();
|
||||
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = FetchState::new(rx);
|
||||
let fetch = FetchState::search(rx);
|
||||
let mut app = AppMachine::app_fetch_next(inner, fetch);
|
||||
assert!(matches!(app, AppState::Match(_)));
|
||||
|
||||
@ -631,35 +707,102 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_ok_fetch_err() {
|
||||
fn recv_ok_search_err() {
|
||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||
|
||||
let fetch_result = Err(musicbrainz::api::Error::RateLimit);
|
||||
tx.send(fetch_result).unwrap();
|
||||
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = FetchState::new(rx);
|
||||
let fetch = FetchState::search(rx);
|
||||
let app = AppMachine::app_fetch_next(inner, fetch);
|
||||
assert!(matches!(app, AppState::Error(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_ok_fetch_ok() {
|
||||
let collection = COLLECTION.clone();
|
||||
|
||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||
let fetch = FetchState::fetch(rx);
|
||||
|
||||
let artist_id = collection[0].meta.id.clone();
|
||||
let old_album = collection[0].albums[0].meta.clone();
|
||||
let new_album = AlbumMeta::new(AlbumId::new("some new album"));
|
||||
|
||||
let release_group_fetch = EntityList::Album(vec![old_album.clone(), new_album.clone()]);
|
||||
let fetch_result = Ok(MbReturn::Fetch(release_group_fetch));
|
||||
tx.send(fetch_result).unwrap();
|
||||
drop(tx);
|
||||
|
||||
let mut music_hoard = music_hoard(collection);
|
||||
music_hoard
|
||||
.expect_add_album()
|
||||
.with(predicate::eq(artist_id), predicate::eq(new_album))
|
||||
.times(1)
|
||||
.return_once(|_, _| Ok(()));
|
||||
|
||||
let app = AppMachine::app_fetch_next(inner(music_hoard), fetch);
|
||||
assert!(matches!(app, AppState::Browse(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_ok_fetch_ok_add_album_err() {
|
||||
let collection = COLLECTION.clone();
|
||||
|
||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||
let fetch = FetchState::fetch(rx);
|
||||
|
||||
let artist_id = collection[0].meta.id.clone();
|
||||
let old_album = collection[0].albums[0].meta.clone();
|
||||
let new_album = AlbumMeta::new(AlbumId::new("some new album"));
|
||||
|
||||
let release_group_fetch = EntityList::Album(vec![old_album.clone(), new_album.clone()]);
|
||||
let fetch_result = Ok(MbReturn::Fetch(release_group_fetch));
|
||||
tx.send(fetch_result).unwrap();
|
||||
drop(tx);
|
||||
|
||||
let mut music_hoard = music_hoard(collection);
|
||||
music_hoard
|
||||
.expect_add_album()
|
||||
.with(predicate::eq(artist_id), predicate::eq(new_album))
|
||||
.times(1)
|
||||
.return_once(|_, _| Err(musichoard::Error::CollectionError(String::from("get rekt"))));
|
||||
|
||||
let app = AppMachine::app_fetch_next(inner(music_hoard), fetch);
|
||||
assert!(matches!(app, AppState::Error(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_err_empty() {
|
||||
let (_tx, rx) = mpsc::channel::<MbApiResult>();
|
||||
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = FetchState::new(rx);
|
||||
let fetch = FetchState::search(rx);
|
||||
let app = AppMachine::app_fetch_next(inner, fetch);
|
||||
assert!(matches!(app, AppState::Fetch(_)));
|
||||
}
|
||||
|
||||
fn browse_release_group_expectation(artist: &Artist) -> MockIMbJobSender {
|
||||
let requests = AppMachine::browse_release_group_job(&artist.meta.info.musicbrainz);
|
||||
let mut mb_job_sender = MockIMbJobSender::new();
|
||||
mb_job_sender
|
||||
.expect_submit_background_job()
|
||||
.with(predicate::always(), predicate::eq(requests))
|
||||
.times(1)
|
||||
.return_once(|_, _| Ok(()));
|
||||
mb_job_sender
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_err_empty_first() {
|
||||
let mut collection = COLLECTION.clone();
|
||||
collection[0].albums.clear();
|
||||
|
||||
let app = AppMachine::app_fetch_first(inner(music_hoard(collection)));
|
||||
assert!(matches!(app, AppState::Browse(_)));
|
||||
let mb_job_sender = browse_release_group_expectation(&collection[0]);
|
||||
let inner = inner_with_mb(music_hoard(collection), mb_job_sender);
|
||||
let app = AppMachine::app_fetch_first(inner);
|
||||
assert!(matches!(app, AppState::Fetch(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -667,11 +810,25 @@ mod tests {
|
||||
let mut collection = COLLECTION.clone();
|
||||
collection[0].albums.clear();
|
||||
|
||||
let (_, rx) = mpsc::channel::<MbApiResult>();
|
||||
let fetch = FetchState::new(rx);
|
||||
let (_tx, rx) = mpsc::channel::<MbApiResult>();
|
||||
let fetch = FetchState::search(rx);
|
||||
|
||||
let app = AppMachine::app_fetch_next(inner(music_hoard(collection)), fetch);
|
||||
assert!(matches!(app, AppState::Browse(_)));
|
||||
assert!(matches!(app, AppState::Fetch(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_err_disconnected_search_next() {
|
||||
let mut collection = COLLECTION.clone();
|
||||
collection[0].albums.clear();
|
||||
|
||||
let (_, rx) = mpsc::channel::<MbApiResult>();
|
||||
let fetch = FetchState::search(rx);
|
||||
|
||||
let mb_job_sender = browse_release_group_expectation(&collection[0]);
|
||||
let inner = inner_with_mb(music_hoard(collection), mb_job_sender);
|
||||
let app = AppMachine::app_fetch_next(inner, fetch);
|
||||
assert!(matches!(app, AppState::Fetch(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -679,7 +836,7 @@ mod tests {
|
||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = FetchState::new(rx);
|
||||
let fetch = FetchState::search(rx);
|
||||
let app = AppMachine::app_fetch_next(inner, fetch);
|
||||
assert!(matches!(app, AppState::Fetch(_)));
|
||||
|
||||
@ -696,7 +853,7 @@ mod tests {
|
||||
fn abort() {
|
||||
let (_, rx) = mpsc::channel::<MbApiResult>();
|
||||
|
||||
let fetch = FetchState::new(rx);
|
||||
let fetch = FetchState::search(rx);
|
||||
let app = AppMachine::fetch_state(inner(music_hoard(COLLECTION.clone())), fetch);
|
||||
|
||||
let app = app.abort();
|
||||
|
@ -319,15 +319,13 @@ mod tests {
|
||||
}
|
||||
|
||||
fn album_meta(id: AlbumId) -> AlbumMeta {
|
||||
AlbumMeta::new(
|
||||
id,
|
||||
AlbumDate::new(Some(1990), Some(5), None),
|
||||
AlbumInfo::new(
|
||||
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],
|
||||
),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fn album_match() -> EntityMatches {
|
||||
@ -357,7 +355,7 @@ mod tests {
|
||||
|
||||
fn fetch_state() -> FetchState {
|
||||
let (_, rx) = mpsc::channel();
|
||||
FetchState::new(rx)
|
||||
FetchState::search(rx)
|
||||
}
|
||||
|
||||
fn match_state(match_state_info: EntityMatches) -> MatchState {
|
||||
@ -388,7 +386,7 @@ mod tests {
|
||||
fn match_state_flow(mut matches_info: EntityMatches, len: usize) {
|
||||
// tx must exist for rx to return Empty rather than Disconnected.
|
||||
let (_tx, rx) = mpsc::channel();
|
||||
let app_matches = MatchState::new(matches_info.clone(), FetchState::new(rx));
|
||||
let app_matches = MatchState::new(matches_info.clone(), FetchState::search(rx));
|
||||
|
||||
let mut music_hoard = music_hoard(vec![]);
|
||||
let artist_id = ArtistId::new("Artist");
|
||||
@ -487,7 +485,7 @@ mod tests {
|
||||
let matches_info = artist_match();
|
||||
|
||||
let (_tx, rx) = mpsc::channel();
|
||||
let app_matches = MatchState::new(matches_info.clone(), FetchState::new(rx));
|
||||
let app_matches = MatchState::new(matches_info.clone(), FetchState::search(rx));
|
||||
|
||||
let mut music_hoard = music_hoard(vec![]);
|
||||
match matches_info {
|
||||
@ -511,7 +509,7 @@ mod tests {
|
||||
let matches_info = album_match();
|
||||
|
||||
let (_tx, rx) = mpsc::channel();
|
||||
let app_matches = MatchState::new(matches_info.clone(), FetchState::new(rx));
|
||||
let app_matches = MatchState::new(matches_info.clone(), FetchState::search(rx));
|
||||
|
||||
let mut music_hoard = music_hoard(vec![]);
|
||||
match matches_info {
|
||||
@ -535,7 +533,7 @@ mod tests {
|
||||
let matches_info = artist_match();
|
||||
|
||||
let (_tx, rx) = mpsc::channel();
|
||||
let app_matches = MatchState::new(matches_info.clone(), FetchState::new(rx));
|
||||
let app_matches = MatchState::new(matches_info.clone(), FetchState::search(rx));
|
||||
|
||||
let mut music_hoard = music_hoard(vec![]);
|
||||
match matches_info {
|
||||
@ -625,7 +623,7 @@ mod tests {
|
||||
fn select_manual_input_album() {
|
||||
let mut mb_job_sender = MockIMbJobSender::new();
|
||||
let artist_id = ArtistId::new("Artist");
|
||||
let album = AlbumMeta::new("Album", 1990, AlbumInfo::default());
|
||||
let album = AlbumMeta::new("Album").with_date(1990);
|
||||
let requests = VecDeque::from([MbParams::lookup_release_group(
|
||||
artist_id.clone(),
|
||||
album.id.clone(),
|
||||
|
@ -493,7 +493,7 @@ mod tests {
|
||||
|
||||
let (_, rx) = mpsc::channel();
|
||||
let inner = app.unwrap_browse().inner;
|
||||
let state = FetchState::new(rx);
|
||||
let state = FetchState::search(rx);
|
||||
app = AppMachine::new(inner, state).into();
|
||||
|
||||
let state = app.state();
|
||||
@ -518,7 +518,7 @@ mod tests {
|
||||
assert!(app.is_running());
|
||||
|
||||
let (_, rx) = mpsc::channel();
|
||||
let fetch = FetchState::new(rx);
|
||||
let fetch = FetchState::search(rx);
|
||||
let artist = ArtistMeta::new(ArtistId::new("Artist"));
|
||||
let info = EntityMatches::artist_lookup(artist.clone(), Entity::new(artist.clone()));
|
||||
app =
|
||||
|
@ -59,7 +59,6 @@ pub trait IMbJobSender {
|
||||
pub enum MbParams {
|
||||
Lookup(LookupParams),
|
||||
Search(SearchParams),
|
||||
#[allow(dead_code)] // TODO: remove with completion of #160
|
||||
Browse(BrowseParams),
|
||||
}
|
||||
|
||||
@ -102,7 +101,6 @@ pub struct SearchReleaseGroupParams {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum BrowseParams {
|
||||
#[allow(dead_code)] // TODO: remove with completion of #160
|
||||
ReleaseGroup(BrowseReleaseGroupParams),
|
||||
}
|
||||
|
||||
@ -136,7 +134,6 @@ impl MbParams {
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // TODO: to be removed by completion of #160
|
||||
pub fn browse_release_group(artist: Mbid) -> Self {
|
||||
MbParams::Browse(BrowseParams::ReleaseGroup(BrowseReleaseGroupParams {
|
||||
artist,
|
||||
|
@ -3,7 +3,7 @@ pub mod interface;
|
||||
|
||||
use musichoard::{
|
||||
collection::{
|
||||
album::{AlbumId, AlbumInfo},
|
||||
album::{AlbumId, AlbumInfo, AlbumMeta},
|
||||
artist::{ArtistId, ArtistInfo},
|
||||
Collection,
|
||||
},
|
||||
@ -20,6 +20,12 @@ pub trait IMusicHoard {
|
||||
fn reload_database(&mut self) -> Result<(), musichoard::Error>;
|
||||
fn get_collection(&self) -> &Collection;
|
||||
|
||||
fn add_album(
|
||||
&mut self,
|
||||
artist_id: &ArtistId,
|
||||
album_meta: AlbumMeta,
|
||||
) -> Result<(), musichoard::Error>;
|
||||
|
||||
fn merge_artist_info(
|
||||
&mut self,
|
||||
id: &ArtistId,
|
||||
@ -47,6 +53,14 @@ impl<Database: IDatabase, Library: ILibrary> IMusicHoard for MusicHoard<Database
|
||||
<Self as IMusicHoardBase>::get_collection(self)
|
||||
}
|
||||
|
||||
fn add_album(
|
||||
&mut self,
|
||||
artist_id: &ArtistId,
|
||||
album_meta: AlbumMeta,
|
||||
) -> Result<(), musichoard::Error> {
|
||||
<Self as IMusicHoardDatabase>::add_album(self, artist_id, album_meta)
|
||||
}
|
||||
|
||||
fn merge_artist_info(
|
||||
&mut self,
|
||||
id: &ArtistId,
|
||||
|
@ -289,9 +289,7 @@ mod tests {
|
||||
#[test]
|
||||
fn empty_album() {
|
||||
let mut artists: Vec<Artist> = vec![Artist::new(ArtistId::new("An artist"))];
|
||||
artists[0]
|
||||
.albums
|
||||
.push(Album::new("An album", AlbumDate::default(), None, vec![]));
|
||||
artists[0].albums.push(Album::new("An album"));
|
||||
let mut selection = Selection::new(&artists);
|
||||
|
||||
draw_test_suite(&artists, &mut selection);
|
||||
@ -361,15 +359,13 @@ mod tests {
|
||||
}
|
||||
|
||||
fn album_meta(id: AlbumId) -> AlbumMeta {
|
||||
AlbumMeta::new(
|
||||
id,
|
||||
AlbumDate::new(Some(1990), Some(5), None),
|
||||
AlbumInfo::new(
|
||||
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],
|
||||
),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fn album_matches() -> EntityMatches {
|
||||
|
Loading…
x
Reference in New Issue
Block a user