Separate metadata from collections #209
@ -145,20 +145,13 @@ impl Album {
|
||||
secondary_types: Vec<AlbumSecondaryType>,
|
||||
) -> Self {
|
||||
Album {
|
||||
meta: AlbumMeta {
|
||||
id: id.into(),
|
||||
date: date.into(),
|
||||
seq: AlbumSeq::default(),
|
||||
musicbrainz: None,
|
||||
primary_type,
|
||||
secondary_types,
|
||||
},
|
||||
meta: AlbumMeta::new(id, date, primary_type, secondary_types),
|
||||
tracks: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) {
|
||||
(&self.meta.date, &self.meta.seq, &self.meta.id)
|
||||
self.meta.get_sort_key()
|
||||
}
|
||||
|
||||
pub fn get_status(&self) -> AlbumStatus {
|
||||
@ -166,19 +159,19 @@ impl Album {
|
||||
}
|
||||
|
||||
pub fn set_seq(&mut self, seq: AlbumSeq) {
|
||||
self.meta.seq = seq;
|
||||
self.meta.set_seq(seq);
|
||||
}
|
||||
|
||||
pub fn clear_seq(&mut self) {
|
||||
self.meta.seq = AlbumSeq::default();
|
||||
self.meta.clear_seq();
|
||||
}
|
||||
|
||||
pub fn set_musicbrainz_ref(&mut self, mbref: MbAlbumRef) {
|
||||
_ = self.meta.musicbrainz.insert(mbref);
|
||||
self.meta.set_musicbrainz_ref(mbref);
|
||||
}
|
||||
|
||||
pub fn clear_musicbrainz_ref(&mut self) {
|
||||
_ = self.meta.musicbrainz.take();
|
||||
self.meta.clear_musicbrainz_ref();
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,26 +183,79 @@ impl PartialOrd for Album {
|
||||
|
||||
impl Ord for Album {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.get_sort_key().cmp(&other.get_sort_key())
|
||||
self.meta.cmp(&other.meta)
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for Album {
|
||||
fn merge_in_place(&mut self, other: Self) {
|
||||
assert_eq!(self.meta.id, other.meta.id);
|
||||
self.meta.seq = std::cmp::max(self.meta.seq, other.meta.seq);
|
||||
|
||||
self.meta.musicbrainz = self.meta.musicbrainz.take().or(other.meta.musicbrainz);
|
||||
self.meta.primary_type = self.meta.primary_type.take().or(other.meta.primary_type);
|
||||
self.meta
|
||||
.secondary_types
|
||||
.merge_in_place(other.meta.secondary_types);
|
||||
|
||||
self.meta.merge_in_place(other.meta);
|
||||
let tracks = mem::take(&mut self.tracks);
|
||||
self.tracks = MergeSorted::new(tracks.into_iter(), other.tracks.into_iter()).collect();
|
||||
}
|
||||
}
|
||||
|
||||
impl AlbumMeta {
|
||||
pub fn new<Id: Into<AlbumId>, Date: Into<AlbumDate>>(
|
||||
id: Id,
|
||||
date: Date,
|
||||
primary_type: Option<AlbumPrimaryType>,
|
||||
secondary_types: Vec<AlbumSecondaryType>,
|
||||
) -> Self {
|
||||
AlbumMeta {
|
||||
id: id.into(),
|
||||
date: date.into(),
|
||||
seq: AlbumSeq::default(),
|
||||
musicbrainz: None,
|
||||
primary_type,
|
||||
secondary_types,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) {
|
||||
(&self.date, &self.seq, &self.id)
|
||||
}
|
||||
|
||||
pub fn set_seq(&mut self, seq: AlbumSeq) {
|
||||
self.seq = seq;
|
||||
}
|
||||
|
||||
pub fn clear_seq(&mut self) {
|
||||
self.seq = AlbumSeq::default();
|
||||
}
|
||||
|
||||
pub fn set_musicbrainz_ref(&mut self, mbref: MbAlbumRef) {
|
||||
_ = self.musicbrainz.insert(mbref);
|
||||
}
|
||||
|
||||
pub fn clear_musicbrainz_ref(&mut self) {
|
||||
_ = self.musicbrainz.take();
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AlbumMeta {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for AlbumMeta {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.get_sort_key().cmp(&other.get_sort_key())
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for AlbumMeta {
|
||||
fn merge_in_place(&mut self, other: Self) {
|
||||
assert_eq!(self.id, other.id);
|
||||
self.seq = std::cmp::max(self.seq, other.seq);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String>> From<S> for AlbumId {
|
||||
fn from(value: S) -> Self {
|
||||
AlbumId::new(value)
|
||||
|
@ -44,34 +44,29 @@ impl Artist {
|
||||
/// Create new [`Artist`] with the given [`ArtistId`].
|
||||
pub fn new<Id: Into<ArtistId>>(id: Id) -> Self {
|
||||
Artist {
|
||||
meta: ArtistMeta {
|
||||
id: id.into(),
|
||||
sort: None,
|
||||
musicbrainz: None,
|
||||
properties: HashMap::new(),
|
||||
},
|
||||
meta: ArtistMeta::new(id),
|
||||
albums: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sort_key(&self) -> (&ArtistId,) {
|
||||
(self.meta.sort.as_ref().unwrap_or(&self.meta.id),)
|
||||
self.meta.get_sort_key()
|
||||
}
|
||||
|
||||
pub fn set_sort_key<SORT: Into<ArtistId>>(&mut self, sort: SORT) {
|
||||
self.meta.sort = Some(sort.into());
|
||||
self.meta.set_sort_key(sort);
|
||||
}
|
||||
|
||||
pub fn clear_sort_key(&mut self) {
|
||||
_ = self.meta.sort.take();
|
||||
self.meta.clear_sort_key();
|
||||
}
|
||||
|
||||
pub fn set_musicbrainz_ref(&mut self, mbref: MbArtistRef) {
|
||||
_ = self.meta.musicbrainz.insert(mbref);
|
||||
self.meta.set_musicbrainz_ref(mbref);
|
||||
}
|
||||
|
||||
pub fn clear_musicbrainz_ref(&mut self) {
|
||||
_ = self.meta.musicbrainz.take();
|
||||
self.meta.clear_musicbrainz_ref();
|
||||
}
|
||||
|
||||
// In the functions below, it would be better to use `contains` instead of `iter().any`, but for
|
||||
@ -79,43 +74,19 @@ impl Artist {
|
||||
// https://stackoverflow.com/questions/48985924/why-does-a-str-not-coerce-to-a-string-when-using-veccontains
|
||||
|
||||
pub fn add_to_property<S: AsRef<str> + Into<String>>(&mut self, property: S, values: Vec<S>) {
|
||||
match self.meta.properties.get_mut(property.as_ref()) {
|
||||
Some(container) => {
|
||||
container.append(
|
||||
&mut values
|
||||
.into_iter()
|
||||
.filter(|val| !container.iter().any(|x| x == val.as_ref()))
|
||||
.map(|val| val.into())
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
self.meta.properties.insert(
|
||||
property.into(),
|
||||
values.into_iter().map(|s| s.into()).collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.meta.add_to_property(property, values);
|
||||
}
|
||||
|
||||
pub fn remove_from_property<S: AsRef<str>>(&mut self, property: S, values: Vec<S>) {
|
||||
if let Some(container) = self.meta.properties.get_mut(property.as_ref()) {
|
||||
container.retain(|val| !values.iter().any(|x| x.as_ref() == val));
|
||||
if container.is_empty() {
|
||||
self.meta.properties.remove(property.as_ref());
|
||||
}
|
||||
}
|
||||
self.meta.remove_from_property(property, values);
|
||||
}
|
||||
|
||||
pub fn set_property<S: AsRef<str> + Into<String>>(&mut self, property: S, values: Vec<S>) {
|
||||
self.meta.properties.insert(
|
||||
property.into(),
|
||||
values.into_iter().map(|s| s.into()).collect(),
|
||||
);
|
||||
self.meta.set_property(property, values);
|
||||
}
|
||||
|
||||
pub fn clear_property<S: AsRef<str>>(&mut self, property: S) {
|
||||
self.meta.properties.remove(property.as_ref());
|
||||
self.meta.clear_property(property);
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,23 +98,115 @@ impl PartialOrd for Artist {
|
||||
|
||||
impl Ord for Artist {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.get_sort_key().cmp(&other.get_sort_key())
|
||||
self.meta.cmp(&other.meta)
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for Artist {
|
||||
fn merge_in_place(&mut self, other: Self) {
|
||||
assert_eq!(self.meta.id, other.meta.id);
|
||||
|
||||
self.meta.sort = self.meta.sort.take().or(other.meta.sort);
|
||||
self.meta.musicbrainz = self.meta.musicbrainz.take().or(other.meta.musicbrainz);
|
||||
self.meta.properties.merge_in_place(other.meta.properties);
|
||||
|
||||
self.meta.merge_in_place(other.meta);
|
||||
let albums = mem::take(&mut self.albums);
|
||||
self.albums = MergeCollections::merge_iter(albums, other.albums);
|
||||
}
|
||||
}
|
||||
|
||||
impl ArtistMeta {
|
||||
pub fn new<Id: Into<ArtistId>>(id: Id) -> Self {
|
||||
ArtistMeta {
|
||||
id: id.into(),
|
||||
sort: None,
|
||||
musicbrainz: None,
|
||||
properties: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sort_key(&self) -> (&ArtistId,) {
|
||||
(self.sort.as_ref().unwrap_or(&self.id),)
|
||||
}
|
||||
|
||||
pub fn set_sort_key<SORT: Into<ArtistId>>(&mut self, sort: SORT) {
|
||||
self.sort = Some(sort.into());
|
||||
}
|
||||
|
||||
pub fn clear_sort_key(&mut self) {
|
||||
_ = self.sort.take();
|
||||
}
|
||||
|
||||
pub fn set_musicbrainz_ref(&mut self, mbref: MbArtistRef) {
|
||||
_ = self.musicbrainz.insert(mbref);
|
||||
}
|
||||
|
||||
pub fn clear_musicbrainz_ref(&mut self) {
|
||||
_ = self.musicbrainz.take();
|
||||
}
|
||||
|
||||
// In the functions below, it would be better to use `contains` instead of `iter().any`, but for
|
||||
// type reasons that does not work:
|
||||
// https://stackoverflow.com/questions/48985924/why-does-a-str-not-coerce-to-a-string-when-using-veccontains
|
||||
|
||||
pub fn add_to_property<S: AsRef<str> + Into<String>>(&mut self, property: S, values: Vec<S>) {
|
||||
match self.properties.get_mut(property.as_ref()) {
|
||||
Some(container) => {
|
||||
container.append(
|
||||
&mut values
|
||||
.into_iter()
|
||||
.filter(|val| !container.iter().any(|x| x == val.as_ref()))
|
||||
.map(|val| val.into())
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
self.properties.insert(
|
||||
property.into(),
|
||||
values.into_iter().map(|s| s.into()).collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_from_property<S: AsRef<str>>(&mut self, property: S, values: Vec<S>) {
|
||||
if let Some(container) = self.properties.get_mut(property.as_ref()) {
|
||||
container.retain(|val| !values.iter().any(|x| x.as_ref() == val));
|
||||
if container.is_empty() {
|
||||
self.properties.remove(property.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_property<S: AsRef<str> + Into<String>>(&mut self, property: S, values: Vec<S>) {
|
||||
self.properties.insert(
|
||||
property.into(),
|
||||
values.into_iter().map(|s| s.into()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clear_property<S: AsRef<str>>(&mut self, property: S) {
|
||||
self.properties.remove(property.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ArtistMeta {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ArtistMeta {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.get_sort_key().cmp(&other.get_sort_key())
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for ArtistMeta {
|
||||
fn merge_in_place(&mut self, other: Self) {
|
||||
assert_eq!(self.id, other.id);
|
||||
|
||||
self.sort = self.sort.take().or(other.sort);
|
||||
self.musicbrainz = self.musicbrainz.take().or(other.musicbrainz);
|
||||
self.properties.merge_in_place(other.properties);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String>> From<S> for ArtistId {
|
||||
fn from(value: S) -> Self {
|
||||
ArtistId::new(value)
|
||||
|
@ -103,9 +103,13 @@ impl IAppInteractBrowse for AppMachine<AppBrowse> {
|
||||
continue;
|
||||
}
|
||||
|
||||
match self.inner.musicbrainz.search_release_group(arid, album) {
|
||||
match self
|
||||
.inner
|
||||
.musicbrainz
|
||||
.search_release_group(arid, &album.meta)
|
||||
{
|
||||
Ok(list) => matches_tx
|
||||
.send(AppMatchesInfo::album(album.clone(), list))
|
||||
.send(AppMatchesInfo::album(album.meta.clone(), list))
|
||||
.expect("send fails only if receiver is disconnected"),
|
||||
Err(err) => return AppMachine::error(self.inner, err.to_string()).into(),
|
||||
}
|
||||
@ -115,9 +119,9 @@ impl IAppInteractBrowse for AppMachine<AppBrowse> {
|
||||
}
|
||||
}
|
||||
}
|
||||
None => match self.inner.musicbrainz.search_artist(artist) {
|
||||
None => match self.inner.musicbrainz.search_artist(&artist.meta) {
|
||||
Ok(list) => matches_tx
|
||||
.send(AppMatchesInfo::artist(artist.clone(), list))
|
||||
.send(AppMatchesInfo::artist(artist.meta.clone(), list))
|
||||
.expect("send fails only if receiver is disconnected"),
|
||||
Err(err) => return AppMachine::error(self.inner, err.to_string()).into(),
|
||||
},
|
||||
@ -134,7 +138,7 @@ impl IAppInteractBrowse for AppMachine<AppBrowse> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mockall::{predicate, Sequence};
|
||||
use musichoard::collection::{album::Album, artist::Artist, musicbrainz::Mbid};
|
||||
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid};
|
||||
|
||||
use crate::tui::{
|
||||
app::{
|
||||
@ -223,8 +227,8 @@ mod tests {
|
||||
let mut mb_api = Box::new(MockIMusicBrainz::new());
|
||||
|
||||
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||
let album_1 = COLLECTION[1].albums[0].clone();
|
||||
let album_4 = COLLECTION[1].albums[3].clone();
|
||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
let album_match_1_1 = Match::new(100, album_1.clone());
|
||||
let album_match_1_2 = Match::new(50, album_4.clone());
|
||||
@ -233,8 +237,8 @@ mod tests {
|
||||
let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()];
|
||||
let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()];
|
||||
|
||||
let result_1: Result<Vec<Match<Album>>, musicbrainz::Error> = Ok(matches_1.clone());
|
||||
let result_4: Result<Vec<Match<Album>>, musicbrainz::Error> = Ok(matches_4.clone());
|
||||
let result_1: Result<Vec<Match<AlbumMeta>>, musicbrainz::Error> = Ok(matches_1.clone());
|
||||
let result_4: Result<Vec<Match<AlbumMeta>>, musicbrainz::Error> = Ok(matches_4.clone());
|
||||
|
||||
// Other albums have an MBID and so they will be skipped.
|
||||
let mut seq = Sequence::new();
|
||||
@ -263,7 +267,7 @@ mod tests {
|
||||
|
||||
let public_matches = public.state.unwrap_matches();
|
||||
|
||||
let mut matches_1: Vec<MatchOption<Album>> =
|
||||
let mut matches_1: Vec<MatchOption<AlbumMeta>> =
|
||||
matches_1.into_iter().map(Into::into).collect();
|
||||
matches_1.push(MatchOption::CannotHaveMbid);
|
||||
let expected = Some(AppMatchesInfo::Album(AppAlbumMatches {
|
||||
@ -279,7 +283,7 @@ mod tests {
|
||||
|
||||
let public_matches = public.state.unwrap_matches();
|
||||
|
||||
let mut matches_4: Vec<MatchOption<Album>> =
|
||||
let mut matches_4: Vec<MatchOption<AlbumMeta>> =
|
||||
matches_4.into_iter().map(Into::into).collect();
|
||||
matches_4.push(MatchOption::CannotHaveMbid);
|
||||
let expected = Some(AppMatchesInfo::Album(AppAlbumMatches {
|
||||
@ -303,13 +307,13 @@ mod tests {
|
||||
fn fetch_musicbrainz_no_artist_mbid() {
|
||||
let mut mb_api = Box::new(MockIMusicBrainz::new());
|
||||
|
||||
let artist = COLLECTION[3].clone();
|
||||
let artist = COLLECTION[3].meta.clone();
|
||||
|
||||
let artist_match_1 = Match::new(100, artist.clone());
|
||||
let artist_match_2 = Match::new(50, artist.clone());
|
||||
let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
|
||||
|
||||
let result: Result<Vec<Match<Artist>>, musicbrainz::Error> = Ok(matches.clone());
|
||||
let result: Result<Vec<Match<ArtistMeta>>, musicbrainz::Error> = Ok(matches.clone());
|
||||
|
||||
mb_api
|
||||
.expect_search_artist()
|
||||
@ -330,7 +334,8 @@ mod tests {
|
||||
|
||||
let public_matches = public.state.unwrap_matches();
|
||||
|
||||
let mut matches: Vec<MatchOption<Artist>> = matches.into_iter().map(Into::into).collect();
|
||||
let mut matches: Vec<MatchOption<ArtistMeta>> =
|
||||
matches.into_iter().map(Into::into).collect();
|
||||
matches.push(MatchOption::CannotHaveMbid);
|
||||
let expected = Some(AppMatchesInfo::Artist(AppArtistMatches {
|
||||
matching: artist.clone(),
|
||||
|
@ -144,8 +144,8 @@ impl IAppInteractMatchesPrivate for AppMatches {
|
||||
mod tests {
|
||||
use mpsc::Receiver;
|
||||
use musichoard::collection::{
|
||||
album::{Album, AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
|
||||
artist::{Artist, ArtistId},
|
||||
album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
|
||||
artist::{ArtistId, ArtistMeta},
|
||||
};
|
||||
|
||||
use crate::tui::{
|
||||
@ -159,7 +159,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn artist_matches_info_vec() -> Vec<AppMatchesInfo> {
|
||||
let artist_1 = Artist::new(ArtistId::new("Artist 1"));
|
||||
let artist_1 = ArtistMeta::new(ArtistId::new("Artist 1"));
|
||||
|
||||
let artist_1_1 = artist_1.clone();
|
||||
let artist_match_1_1 = Match::new(100, artist_1_1);
|
||||
@ -171,7 +171,7 @@ mod tests {
|
||||
let list = vec![artist_match_1_1.clone(), artist_match_1_2.clone()];
|
||||
let matches_info_1 = AppMatchesInfo::artist(artist_1.clone(), list);
|
||||
|
||||
let artist_2 = Artist::new(ArtistId::new("Artist 2"));
|
||||
let artist_2 = ArtistMeta::new(ArtistId::new("Artist 2"));
|
||||
|
||||
let artist_2_1 = artist_1.clone();
|
||||
let album_match_2_1 = Match::new(100, artist_2_1);
|
||||
@ -183,7 +183,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn album_matches_info_vec() -> Vec<AppMatchesInfo> {
|
||||
let album_1 = Album::new(
|
||||
let album_1 = AlbumMeta::new(
|
||||
AlbumId::new("Album 1"),
|
||||
AlbumDate::new(Some(1990), Some(5), None),
|
||||
Some(AlbumPrimaryType::Album),
|
||||
@ -194,14 +194,14 @@ mod tests {
|
||||
let album_match_1_1 = Match::new(100, album_1_1);
|
||||
|
||||
let mut album_1_2 = album_1.clone();
|
||||
album_1_2.meta.id.title.push_str(" extra title part");
|
||||
album_1_2.meta.secondary_types.pop();
|
||||
album_1_2.id.title.push_str(" extra title part");
|
||||
album_1_2.secondary_types.pop();
|
||||
let album_match_1_2 = Match::new(100, album_1_2);
|
||||
|
||||
let list = vec![album_match_1_1.clone(), album_match_1_2.clone()];
|
||||
let matches_info_1 = AppMatchesInfo::album(album_1.clone(), list);
|
||||
|
||||
let album_2 = Album::new(
|
||||
let album_2 = AlbumMeta::new(
|
||||
AlbumId::new("Album 2"),
|
||||
AlbumDate::new(Some(2001), None, None),
|
||||
Some(AlbumPrimaryType::Album),
|
||||
|
@ -4,7 +4,7 @@ mod selection;
|
||||
pub use machine::App;
|
||||
pub use selection::{Category, Delta, Selection, WidgetState};
|
||||
|
||||
use musichoard::collection::{album::Album, artist::Artist, Collection};
|
||||
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, Collection};
|
||||
|
||||
use crate::tui::lib::interface::musicbrainz::Match;
|
||||
|
||||
@ -143,14 +143,14 @@ impl<T> From<Match<T>> for MatchOption<T> {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AppArtistMatches {
|
||||
pub matching: Artist,
|
||||
pub list: Vec<MatchOption<Artist>>,
|
||||
pub matching: ArtistMeta,
|
||||
pub list: Vec<MatchOption<ArtistMeta>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AppAlbumMatches {
|
||||
pub matching: Album,
|
||||
pub list: Vec<MatchOption<Album>>,
|
||||
pub matching: AlbumMeta,
|
||||
pub list: Vec<MatchOption<AlbumMeta>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@ -160,13 +160,13 @@ pub enum AppMatchesInfo {
|
||||
}
|
||||
|
||||
impl AppMatchesInfo {
|
||||
pub fn artist<M: Into<MatchOption<Artist>>>(matching: Artist, list: Vec<M>) -> Self {
|
||||
let list: Vec<MatchOption<Artist>> = list.into_iter().map(Into::into).collect();
|
||||
pub fn artist<M: Into<MatchOption<ArtistMeta>>>(matching: ArtistMeta, list: Vec<M>) -> Self {
|
||||
let list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
|
||||
AppMatchesInfo::Artist(AppArtistMatches { matching, list })
|
||||
}
|
||||
|
||||
pub fn album<M: Into<MatchOption<Album>>>(matching: Album, list: Vec<M>) -> Self {
|
||||
let list: Vec<MatchOption<Album>> = list.into_iter().map(Into::into).collect();
|
||||
pub fn album<M: Into<MatchOption<AlbumMeta>>>(matching: AlbumMeta, list: Vec<M>) -> Self {
|
||||
let list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
|
||||
AppMatchesInfo::Album(AppAlbumMatches { matching, list })
|
||||
}
|
||||
}
|
||||
|
24
src/tui/lib/external/musicbrainz/mod.rs
vendored
24
src/tui/lib/external/musicbrainz/mod.rs
vendored
@ -2,8 +2,8 @@
|
||||
|
||||
use musichoard::{
|
||||
collection::{
|
||||
album::{Album, AlbumDate},
|
||||
artist::Artist,
|
||||
album::{AlbumDate, AlbumMeta},
|
||||
artist::ArtistMeta,
|
||||
musicbrainz::Mbid,
|
||||
},
|
||||
external::musicbrainz::{
|
||||
@ -32,8 +32,8 @@ impl<Http> MusicBrainz<Http> {
|
||||
}
|
||||
|
||||
impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
||||
fn search_artist(&mut self, artist: &Artist) -> Result<Vec<Match<Artist>>, Error> {
|
||||
let query = SearchArtistRequest::new().string(&artist.meta.id.name);
|
||||
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error> {
|
||||
let query = SearchArtistRequest::new().string(&artist.id.name);
|
||||
|
||||
let mb_response = self.client.search_artist(query)?;
|
||||
|
||||
@ -47,18 +47,18 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
||||
fn search_release_group(
|
||||
&mut self,
|
||||
arid: &Mbid,
|
||||
album: &Album,
|
||||
) -> Result<Vec<Match<Album>>, Error> {
|
||||
album: &AlbumMeta,
|
||||
) -> Result<Vec<Match<AlbumMeta>>, Error> {
|
||||
// Some release groups may have a promotional early release messing up the search. Searching
|
||||
// with just the year should be enough anyway.
|
||||
let date = AlbumDate::new(album.meta.date.year, None, None);
|
||||
let date = AlbumDate::new(album.date.year, None, None);
|
||||
|
||||
let query = SearchReleaseGroupRequest::new()
|
||||
.arid(arid)
|
||||
.and()
|
||||
.first_release_date(&date)
|
||||
.and()
|
||||
.release_group(&album.meta.id.title);
|
||||
.release_group(&album.id.title);
|
||||
|
||||
let mb_response = self.client.search_release_group(query)?;
|
||||
|
||||
@ -70,8 +70,8 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<Artist> {
|
||||
let mut artist = Artist::new(entity.name);
|
||||
fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<ArtistMeta> {
|
||||
let mut artist = ArtistMeta::new(entity.name);
|
||||
if let Some(sort) = entity.sort {
|
||||
artist.set_sort_key(sort);
|
||||
}
|
||||
@ -87,8 +87,8 @@ fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Mat
|
||||
|
||||
fn from_search_release_group_response_release_group(
|
||||
entity: SearchReleaseGroupResponseReleaseGroup,
|
||||
) -> Match<Album> {
|
||||
let mut album = Album::new(
|
||||
) -> Match<AlbumMeta> {
|
||||
let mut album = AlbumMeta::new(
|
||||
entity.title,
|
||||
entity.first_release_date,
|
||||
Some(entity.primary_type),
|
||||
|
@ -3,17 +3,17 @@
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
use musichoard::collection::{album::Album, artist::Artist, musicbrainz::Mbid};
|
||||
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid};
|
||||
|
||||
/// Trait for interacting with the MusicBrainz API.
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait IMusicBrainz {
|
||||
fn search_artist(&mut self, name: &Artist) -> Result<Vec<Match<Artist>>, Error>;
|
||||
fn search_artist(&mut self, name: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error>;
|
||||
fn search_release_group(
|
||||
&mut self,
|
||||
arid: &Mbid,
|
||||
album: &Album,
|
||||
) -> Result<Vec<Match<Album>>, Error>;
|
||||
album: &AlbumMeta,
|
||||
) -> Result<Vec<Match<AlbumMeta>>, Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -1,6 +1,6 @@
|
||||
use musichoard::collection::{
|
||||
album::{Album, AlbumDate, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq, AlbumStatus},
|
||||
artist::Artist,
|
||||
album::{AlbumDate, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq, AlbumStatus},
|
||||
artist::ArtistMeta,
|
||||
track::{TrackFormat, TrackQuality},
|
||||
};
|
||||
|
||||
@ -98,15 +98,15 @@ impl UiDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_artist_matching(artist: &Artist) -> String {
|
||||
format!("Matching artist: {}", &artist.meta.id.name)
|
||||
pub fn display_artist_matching(artist: &ArtistMeta) -> String {
|
||||
format!("Matching artist: {}", &artist.id.name)
|
||||
}
|
||||
|
||||
pub fn display_album_matching(album: &Album) -> String {
|
||||
pub fn display_album_matching(album: &AlbumMeta) -> String {
|
||||
format!(
|
||||
"Matching album: {} | {}",
|
||||
UiDisplay::display_album_date(&album.meta.date),
|
||||
&album.meta.id.title
|
||||
UiDisplay::display_album_date(&album.date),
|
||||
&album.id.title
|
||||
)
|
||||
}
|
||||
|
||||
@ -124,11 +124,11 @@ impl UiDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_artist_match(match_option: &MatchOption<Artist>) -> String {
|
||||
pub fn display_artist_match(match_option: &MatchOption<ArtistMeta>) -> String {
|
||||
match match_option {
|
||||
MatchOption::Match(match_artist) => format!(
|
||||
"{}{} ({}%)",
|
||||
&match_artist.item.meta.id.name,
|
||||
&match_artist.item.id.name,
|
||||
&match_artist
|
||||
.disambiguation
|
||||
.as_ref()
|
||||
@ -140,15 +140,15 @@ impl UiDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_album_match(match_option: &MatchOption<Album>) -> String {
|
||||
pub fn display_album_match(match_option: &MatchOption<AlbumMeta>) -> String {
|
||||
match match_option {
|
||||
MatchOption::Match(match_album) => format!(
|
||||
"{:010} | {} [{}] ({}%)",
|
||||
UiDisplay::display_album_date(&match_album.item.meta.date),
|
||||
&match_album.item.meta.id.title,
|
||||
UiDisplay::display_album_date(&match_album.item.date),
|
||||
&match_album.item.id.title,
|
||||
UiDisplay::display_type(
|
||||
&match_album.item.meta.primary_type,
|
||||
&match_album.item.meta.secondary_types
|
||||
&match_album.item.primary_type,
|
||||
&match_album.item.secondary_types
|
||||
),
|
||||
match_album.score,
|
||||
),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use musichoard::collection::{album::Album, artist::Artist};
|
||||
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta};
|
||||
use ratatui::widgets::{List, ListItem};
|
||||
|
||||
use crate::tui::{
|
||||
@ -32,8 +32,8 @@ impl<'a, 'b> MatchesState<'a, 'b> {
|
||||
}
|
||||
|
||||
fn artists(
|
||||
matching: &Artist,
|
||||
matches: &[MatchOption<Artist>],
|
||||
matching: &ArtistMeta,
|
||||
matches: &[MatchOption<ArtistMeta>],
|
||||
state: &'b mut WidgetState,
|
||||
) -> Self {
|
||||
let matching = UiDisplay::display_artist_matching(matching);
|
||||
@ -54,8 +54,8 @@ impl<'a, 'b> MatchesState<'a, 'b> {
|
||||
}
|
||||
|
||||
fn albums(
|
||||
matching: &Album,
|
||||
matches: &[MatchOption<Album>],
|
||||
matching: &AlbumMeta,
|
||||
matches: &[MatchOption<AlbumMeta>],
|
||||
state: &'b mut WidgetState,
|
||||
) -> Self {
|
||||
let matching = UiDisplay::display_album_matching(matching);
|
||||
|
@ -177,8 +177,8 @@ impl IUi for Ui {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use musichoard::collection::{
|
||||
album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
|
||||
artist::{Artist, ArtistId},
|
||||
album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
|
||||
artist::{Artist, ArtistId, ArtistMeta},
|
||||
};
|
||||
|
||||
use crate::tui::{
|
||||
@ -224,14 +224,14 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn artist_matches(matching: Artist, list: Vec<Match<Artist>>) -> AppMatchesInfo {
|
||||
let mut list: Vec<MatchOption<Artist>> = list.into_iter().map(Into::into).collect();
|
||||
fn artist_matches(matching: ArtistMeta, list: Vec<Match<ArtistMeta>>) -> AppMatchesInfo {
|
||||
let mut list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
|
||||
list.push(MatchOption::CannotHaveMbid);
|
||||
AppMatchesInfo::artist(matching, list)
|
||||
}
|
||||
|
||||
fn album_matches(matching: Album, list: Vec<Match<Album>>) -> AppMatchesInfo {
|
||||
let mut list: Vec<MatchOption<Album>> = list.into_iter().map(Into::into).collect();
|
||||
fn album_matches(matching: AlbumMeta, list: Vec<Match<AlbumMeta>>) -> AppMatchesInfo {
|
||||
let mut list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
|
||||
list.push(MatchOption::CannotHaveMbid);
|
||||
AppMatchesInfo::album(matching, list)
|
||||
}
|
||||
@ -328,7 +328,7 @@ mod tests {
|
||||
|
||||
let mut terminal = terminal();
|
||||
|
||||
let artist = Artist::new(ArtistId::new("an artist"));
|
||||
let artist = ArtistMeta::new(ArtistId::new("an artist"));
|
||||
let artist_match = Match {
|
||||
score: 80,
|
||||
item: artist.clone(),
|
||||
@ -357,7 +357,7 @@ mod tests {
|
||||
|
||||
let mut terminal = terminal();
|
||||
|
||||
let album = Album::new(
|
||||
let album = AlbumMeta::new(
|
||||
AlbumId::new("An Album"),
|
||||
AlbumDate::new(Some(1990), Some(5), None),
|
||||
Some(AlbumPrimaryType::Album),
|
||||
|
Loading…
Reference in New Issue
Block a user