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