Enable fetch to apply modifications to the database #221
@ -51,8 +51,6 @@ enum ArtistCommand {
|
|||||||
Remove,
|
Remove,
|
||||||
#[structopt(about = "Edit the artist's sort name")]
|
#[structopt(about = "Edit the artist's sort name")]
|
||||||
Sort(SortCommand),
|
Sort(SortCommand),
|
||||||
#[structopt(name = "musicbrainz", about = "Edit the MusicBrainz URL of an artist")]
|
|
||||||
MusicBrainz(MusicBrainzCommand),
|
|
||||||
#[structopt(about = "Edit a property of an artist")]
|
#[structopt(about = "Edit a property of an artist")]
|
||||||
Property(PropertyCommand),
|
Property(PropertyCommand),
|
||||||
#[structopt(about = "Modify the artist's album information")]
|
#[structopt(about = "Modify the artist's album information")]
|
||||||
@ -73,20 +71,6 @@ struct SortValue {
|
|||||||
sort: String,
|
sort: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
|
||||||
enum MusicBrainzCommand {
|
|
||||||
#[structopt(about = "Set the MusicBrainz URL overwriting any existing value")]
|
|
||||||
Set(MusicBrainzValue),
|
|
||||||
#[structopt(about = "Clear the MusicBrainz URL)")]
|
|
||||||
Clear,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
|
||||||
struct MusicBrainzValue {
|
|
||||||
#[structopt(help = "The MusicBrainz URL")]
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
enum PropertyCommand {
|
enum PropertyCommand {
|
||||||
#[structopt(about = "Add values to the property without overwriting existing values")]
|
#[structopt(about = "Add values to the property without overwriting existing values")]
|
||||||
@ -173,9 +157,6 @@ impl ArtistCommand {
|
|||||||
ArtistCommand::Sort(sort_command) => {
|
ArtistCommand::Sort(sort_command) => {
|
||||||
sort_command.handle(music_hoard, artist_name);
|
sort_command.handle(music_hoard, artist_name);
|
||||||
}
|
}
|
||||||
ArtistCommand::MusicBrainz(musicbrainz_command) => {
|
|
||||||
musicbrainz_command.handle(music_hoard, artist_name)
|
|
||||||
}
|
|
||||||
ArtistCommand::Property(property_command) => {
|
ArtistCommand::Property(property_command) => {
|
||||||
property_command.handle(music_hoard, artist_name);
|
property_command.handle(music_hoard, artist_name);
|
||||||
}
|
}
|
||||||
@ -190,10 +171,7 @@ impl SortCommand {
|
|||||||
fn handle(self, music_hoard: &mut MH, artist_name: &str) {
|
fn handle(self, music_hoard: &mut MH, artist_name: &str) {
|
||||||
match self {
|
match self {
|
||||||
SortCommand::Set(artist_sort_value) => music_hoard
|
SortCommand::Set(artist_sort_value) => music_hoard
|
||||||
.set_artist_sort(
|
.set_artist_sort(ArtistId::new(artist_name), artist_sort_value.sort)
|
||||||
ArtistId::new(artist_name),
|
|
||||||
ArtistId::new(artist_sort_value.sort),
|
|
||||||
)
|
|
||||||
.expect("faild to set artist sort name"),
|
.expect("faild to set artist sort name"),
|
||||||
SortCommand::Clear => music_hoard
|
SortCommand::Clear => music_hoard
|
||||||
.clear_artist_sort(ArtistId::new(artist_name))
|
.clear_artist_sort(ArtistId::new(artist_name))
|
||||||
@ -202,19 +180,6 @@ impl SortCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MusicBrainzCommand {
|
|
||||||
fn handle(self, music_hoard: &mut MH, artist_name: &str) {
|
|
||||||
match self {
|
|
||||||
MusicBrainzCommand::Set(musicbrainz_value) => music_hoard
|
|
||||||
.set_artist_musicbrainz(ArtistId::new(artist_name), musicbrainz_value.url)
|
|
||||||
.expect("failed to set MusicBrainz URL"),
|
|
||||||
MusicBrainzCommand::Clear => music_hoard
|
|
||||||
.clear_artist_musicbrainz(ArtistId::new(artist_name))
|
|
||||||
.expect("failed to clear MusicBrainz URL"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PropertyCommand {
|
impl PropertyCommand {
|
||||||
fn handle(self, music_hoard: &mut MH, artist_name: &str) {
|
fn handle(self, music_hoard: &mut MH, artist_name: &str) {
|
||||||
match self {
|
match self {
|
||||||
|
@ -22,6 +22,12 @@ pub struct AlbumMeta {
|
|||||||
pub id: AlbumId,
|
pub id: AlbumId,
|
||||||
pub date: AlbumDate,
|
pub date: AlbumDate,
|
||||||
pub seq: AlbumSeq,
|
pub seq: AlbumSeq,
|
||||||
|
pub info: AlbumInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Album non-identifier metadata.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct AlbumInfo {
|
||||||
pub musicbrainz: MbRefOption<MbAlbumRef>,
|
pub musicbrainz: MbRefOption<MbAlbumRef>,
|
||||||
pub primary_type: Option<AlbumPrimaryType>,
|
pub primary_type: Option<AlbumPrimaryType>,
|
||||||
pub secondary_types: Vec<AlbumSecondaryType>,
|
pub secondary_types: Vec<AlbumSecondaryType>,
|
||||||
@ -144,8 +150,9 @@ impl Album {
|
|||||||
primary_type: Option<AlbumPrimaryType>,
|
primary_type: Option<AlbumPrimaryType>,
|
||||||
secondary_types: Vec<AlbumSecondaryType>,
|
secondary_types: Vec<AlbumSecondaryType>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let info = AlbumInfo::new(MbRefOption::None, primary_type, secondary_types);
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta::new(id, date, primary_type, secondary_types),
|
meta: AlbumMeta::new(id, date, info),
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,16 +186,13 @@ impl AlbumMeta {
|
|||||||
pub fn new<Id: Into<AlbumId>, Date: Into<AlbumDate>>(
|
pub fn new<Id: Into<AlbumId>, Date: Into<AlbumDate>>(
|
||||||
id: Id,
|
id: Id,
|
||||||
date: Date,
|
date: Date,
|
||||||
primary_type: Option<AlbumPrimaryType>,
|
info: AlbumInfo,
|
||||||
secondary_types: Vec<AlbumSecondaryType>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
AlbumMeta {
|
AlbumMeta {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
date: date.into(),
|
date: date.into(),
|
||||||
seq: AlbumSeq::default(),
|
seq: AlbumSeq::default(),
|
||||||
musicbrainz: MbRefOption::None,
|
info,
|
||||||
primary_type,
|
|
||||||
secondary_types,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,13 +207,29 @@ impl AlbumMeta {
|
|||||||
pub fn clear_seq(&mut self) {
|
pub fn clear_seq(&mut self) {
|
||||||
self.seq = AlbumSeq::default();
|
self.seq = AlbumSeq::default();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_musicbrainz_ref(&mut self, mbref: MbAlbumRef) {
|
impl Default for AlbumInfo {
|
||||||
self.musicbrainz.replace(mbref);
|
fn default() -> Self {
|
||||||
|
AlbumInfo {
|
||||||
|
musicbrainz: MbRefOption::None,
|
||||||
|
primary_type: None,
|
||||||
|
secondary_types: Vec::new(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_musicbrainz_ref(&mut self) {
|
impl AlbumInfo {
|
||||||
self.musicbrainz.take();
|
pub fn new(
|
||||||
|
musicbrainz: MbRefOption<MbAlbumRef>,
|
||||||
|
primary_type: Option<AlbumPrimaryType>,
|
||||||
|
secondary_types: Vec<AlbumSecondaryType>,
|
||||||
|
) -> Self {
|
||||||
|
AlbumInfo {
|
||||||
|
musicbrainz,
|
||||||
|
primary_type,
|
||||||
|
secondary_types,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +250,12 @@ impl Merge for AlbumMeta {
|
|||||||
assert_eq!(self.id, other.id);
|
assert_eq!(self.id, other.id);
|
||||||
self.seq = std::cmp::max(self.seq, other.seq);
|
self.seq = std::cmp::max(self.seq, other.seq);
|
||||||
|
|
||||||
|
self.info.merge_in_place(other.info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Merge for AlbumInfo {
|
||||||
|
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);
|
self.secondary_types.merge_in_place(other.secondary_types);
|
||||||
@ -354,40 +380,4 @@ mod tests {
|
|||||||
let merged = left.clone().merge(right);
|
let merged = left.clone().merge(right);
|
||||||
assert_eq!(expected, merged);
|
assert_eq!(expected, merged);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_clear_musicbrainz_url() {
|
|
||||||
const MUSICBRAINZ: &str =
|
|
||||||
"https://musicbrainz.org/release-group/c12897a3-af7a-3466-8892-58af84765813";
|
|
||||||
const MUSICBRAINZ_2: &str =
|
|
||||||
"https://musicbrainz.org/release-group/0eaa9306-e6df-47be-94ce-04bfe3df782c";
|
|
||||||
|
|
||||||
let mut album = Album::new(AlbumId::new("an album"), AlbumDate::default(), None, vec![]);
|
|
||||||
|
|
||||||
let mut expected: MbRefOption<MbAlbumRef> = MbRefOption::None;
|
|
||||||
assert_eq!(album.meta.musicbrainz, expected);
|
|
||||||
|
|
||||||
// Setting a URL on an album.
|
|
||||||
album
|
|
||||||
.meta
|
|
||||||
.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
|
|
||||||
expected.replace(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
|
|
||||||
assert_eq!(album.meta.musicbrainz, expected);
|
|
||||||
|
|
||||||
album
|
|
||||||
.meta
|
|
||||||
.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
|
|
||||||
assert_eq!(album.meta.musicbrainz, expected);
|
|
||||||
|
|
||||||
album
|
|
||||||
.meta
|
|
||||||
.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap());
|
|
||||||
expected.replace(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap());
|
|
||||||
assert_eq!(album.meta.musicbrainz, expected);
|
|
||||||
|
|
||||||
// Clearing URLs.
|
|
||||||
album.meta.clear_musicbrainz_ref();
|
|
||||||
expected.take();
|
|
||||||
assert_eq!(album.meta.musicbrainz, expected);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,13 @@ pub struct Artist {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ArtistMeta {
|
pub struct ArtistMeta {
|
||||||
pub id: ArtistId,
|
pub id: ArtistId,
|
||||||
pub sort: Option<ArtistId>,
|
pub sort: Option<String>,
|
||||||
|
pub info: ArtistInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Artist non-identifier metadata.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ArtistInfo {
|
||||||
pub musicbrainz: MbRefOption<MbArtistRef>,
|
pub musicbrainz: MbRefOption<MbArtistRef>,
|
||||||
pub properties: HashMap<String, Vec<String>>,
|
pub properties: HashMap<String, Vec<String>>,
|
||||||
}
|
}
|
||||||
@ -75,25 +81,39 @@ impl ArtistMeta {
|
|||||||
ArtistMeta {
|
ArtistMeta {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
sort: None,
|
sort: None,
|
||||||
musicbrainz: MbRefOption::None,
|
info: ArtistInfo::default(),
|
||||||
properties: HashMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sort_key(&self) -> (&ArtistId,) {
|
pub fn get_sort_key(&self) -> (&str,) {
|
||||||
(self.sort.as_ref().unwrap_or(&self.id),)
|
(self.sort.as_ref().unwrap_or(&self.id.name),)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sort_key<SORT: Into<ArtistId>>(&mut self, sort: SORT) {
|
pub fn set_sort_key<S: Into<String>>(&mut self, sort: S) {
|
||||||
self.sort = Some(sort.into());
|
self.sort = Some(sort.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_sort_key(&mut self) {
|
pub fn clear_sort_key(&mut self) {
|
||||||
self.sort.take();
|
self.sort.take();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_musicbrainz_ref(&mut self, mbref: MbArtistRef) {
|
impl Default for ArtistInfo {
|
||||||
self.musicbrainz.replace(mbref);
|
fn default() -> Self {
|
||||||
|
Self::new(MbRefOption::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArtistInfo {
|
||||||
|
pub fn new(musicbrainz: MbRefOption<MbArtistRef>) -> Self {
|
||||||
|
ArtistInfo {
|
||||||
|
musicbrainz,
|
||||||
|
properties: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_musicbrainz_ref(&mut self, mbref: MbRefOption<MbArtistRef>) {
|
||||||
|
self.musicbrainz = mbref
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_musicbrainz_ref(&mut self) {
|
pub fn clear_musicbrainz_ref(&mut self) {
|
||||||
@ -162,6 +182,12 @@ impl Merge for ArtistMeta {
|
|||||||
assert_eq!(self.id, other.id);
|
assert_eq!(self.id, other.id);
|
||||||
|
|
||||||
self.sort = self.sort.take().or(other.sort);
|
self.sort = self.sort.take().or(other.sort);
|
||||||
|
self.info.merge_in_place(other.info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Merge for ArtistInfo {
|
||||||
|
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.properties.merge_in_place(other.properties);
|
self.properties.merge_in_place(other.properties);
|
||||||
}
|
}
|
||||||
@ -207,14 +233,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn artist_sort_set_clear() {
|
fn artist_sort_set_clear() {
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist_id = ArtistId::new("an artist");
|
||||||
let sort_id_1 = ArtistId::new("sort id 1");
|
let sort_id_1 = String::from("sort id 1");
|
||||||
let sort_id_2 = ArtistId::new("sort id 2");
|
let sort_id_2 = String::from("sort id 2");
|
||||||
|
|
||||||
let mut artist = Artist::new(&artist_id.name);
|
let mut artist = Artist::new(&artist_id.name);
|
||||||
|
|
||||||
assert_eq!(artist.meta.id, artist_id);
|
assert_eq!(artist.meta.id, artist_id);
|
||||||
assert_eq!(artist.meta.sort, None);
|
assert_eq!(artist.meta.sort, None);
|
||||||
assert_eq!(artist.meta.get_sort_key(), (&artist_id,));
|
assert_eq!(artist.meta.get_sort_key(), (artist_id.name.as_str(),));
|
||||||
assert!(artist.meta < ArtistMeta::new(sort_id_1.clone()));
|
assert!(artist.meta < ArtistMeta::new(sort_id_1.clone()));
|
||||||
assert!(artist.meta < ArtistMeta::new(sort_id_2.clone()));
|
assert!(artist.meta < ArtistMeta::new(sort_id_2.clone()));
|
||||||
assert!(artist < Artist::new(sort_id_1.clone()));
|
assert!(artist < Artist::new(sort_id_1.clone()));
|
||||||
@ -224,7 +250,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(artist.meta.id, artist_id);
|
assert_eq!(artist.meta.id, artist_id);
|
||||||
assert_eq!(artist.meta.sort.as_ref(), Some(&sort_id_1));
|
assert_eq!(artist.meta.sort.as_ref(), Some(&sort_id_1));
|
||||||
assert_eq!(artist.meta.get_sort_key(), (&sort_id_1,));
|
assert_eq!(artist.meta.get_sort_key(), (sort_id_1.as_str(),));
|
||||||
assert!(artist.meta > ArtistMeta::new(artist_id.clone()));
|
assert!(artist.meta > ArtistMeta::new(artist_id.clone()));
|
||||||
assert!(artist.meta < ArtistMeta::new(sort_id_2.clone()));
|
assert!(artist.meta < ArtistMeta::new(sort_id_2.clone()));
|
||||||
assert!(artist > Artist::new(artist_id.clone()));
|
assert!(artist > Artist::new(artist_id.clone()));
|
||||||
@ -234,7 +260,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(artist.meta.id, artist_id);
|
assert_eq!(artist.meta.id, artist_id);
|
||||||
assert_eq!(artist.meta.sort.as_ref(), Some(&sort_id_2));
|
assert_eq!(artist.meta.sort.as_ref(), Some(&sort_id_2));
|
||||||
assert_eq!(artist.meta.get_sort_key(), (&sort_id_2,));
|
assert_eq!(artist.meta.get_sort_key(), (sort_id_2.as_str(),));
|
||||||
assert!(artist.meta > ArtistMeta::new(artist_id.clone()));
|
assert!(artist.meta > ArtistMeta::new(artist_id.clone()));
|
||||||
assert!(artist.meta > ArtistMeta::new(sort_id_1.clone()));
|
assert!(artist.meta > ArtistMeta::new(sort_id_1.clone()));
|
||||||
assert!(artist > Artist::new(artist_id.clone()));
|
assert!(artist > Artist::new(artist_id.clone()));
|
||||||
@ -244,7 +270,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(artist.meta.id, artist_id);
|
assert_eq!(artist.meta.id, artist_id);
|
||||||
assert_eq!(artist.meta.sort, None);
|
assert_eq!(artist.meta.sort, None);
|
||||||
assert_eq!(artist.meta.get_sort_key(), (&artist_id,));
|
assert_eq!(artist.meta.get_sort_key(), (artist_id.name.as_str(),));
|
||||||
assert!(artist.meta < ArtistMeta::new(sort_id_1.clone()));
|
assert!(artist.meta < ArtistMeta::new(sort_id_1.clone()));
|
||||||
assert!(artist.meta < ArtistMeta::new(sort_id_2.clone()));
|
assert!(artist.meta < ArtistMeta::new(sort_id_2.clone()));
|
||||||
assert!(artist < Artist::new(sort_id_1.clone()));
|
assert!(artist < Artist::new(sort_id_1.clone()));
|
||||||
@ -256,160 +282,132 @@ mod tests {
|
|||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut artist = Artist::new(ArtistId::new("an artist"));
|
||||||
|
|
||||||
let mut expected: MbRefOption<MbArtistRef> = MbRefOption::None;
|
let mut expected: MbRefOption<MbArtistRef> = MbRefOption::None;
|
||||||
assert_eq!(artist.meta.musicbrainz, expected);
|
assert_eq!(artist.meta.info.musicbrainz, expected);
|
||||||
|
|
||||||
// Setting a URL on an artist.
|
// Setting a URL on an artist.
|
||||||
artist
|
artist.meta.info.set_musicbrainz_ref(MbRefOption::Some(
|
||||||
.meta
|
MbArtistRef::from_url_str(MUSICBRAINZ).unwrap(),
|
||||||
.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
|
));
|
||||||
expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
|
expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
|
||||||
assert_eq!(artist.meta.musicbrainz, expected);
|
assert_eq!(artist.meta.info.musicbrainz, expected);
|
||||||
|
|
||||||
artist
|
artist.meta.info.set_musicbrainz_ref(MbRefOption::Some(
|
||||||
.meta
|
MbArtistRef::from_url_str(MUSICBRAINZ).unwrap(),
|
||||||
.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
|
));
|
||||||
assert_eq!(artist.meta.musicbrainz, expected);
|
assert_eq!(artist.meta.info.musicbrainz, expected);
|
||||||
|
|
||||||
artist
|
artist.meta.info.set_musicbrainz_ref(MbRefOption::Some(
|
||||||
.meta
|
MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap(),
|
||||||
.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap());
|
));
|
||||||
expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap());
|
expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap());
|
||||||
assert_eq!(artist.meta.musicbrainz, expected);
|
assert_eq!(artist.meta.info.musicbrainz, expected);
|
||||||
|
|
||||||
// Clearing URLs.
|
// Clearing URLs.
|
||||||
artist.meta.clear_musicbrainz_ref();
|
artist.meta.info.clear_musicbrainz_ref();
|
||||||
expected.take();
|
expected.take();
|
||||||
assert_eq!(artist.meta.musicbrainz, expected);
|
assert_eq!(artist.meta.info.musicbrainz, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_to_remove_from_property() {
|
fn add_to_remove_from_property() {
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut artist = Artist::new(ArtistId::new("an artist"));
|
||||||
|
|
||||||
|
let info = &mut artist.meta.info;
|
||||||
let mut expected: Vec<String> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert!(artist.meta.properties.is_empty());
|
assert!(info.properties.is_empty());
|
||||||
|
|
||||||
// Adding a single URL.
|
// Adding a single URL.
|
||||||
artist
|
info.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
.meta
|
|
||||||
.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Adding a URL that already exists is ok, but does not do anything.
|
// Adding a URL that already exists is ok, but does not do anything.
|
||||||
artist
|
info.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
.meta
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
|
||||||
|
|
||||||
// Adding another single URL.
|
// Adding another single URL.
|
||||||
artist
|
info.add_to_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
.meta
|
|
||||||
.add_to_property("MusicButler", vec![MUSICBUTLER_2]);
|
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
artist
|
info.add_to_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
.meta
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
.add_to_property("MusicButler", vec![MUSICBUTLER_2]);
|
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
|
||||||
|
|
||||||
// Removing a URL.
|
// Removing a URL.
|
||||||
artist
|
info.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
.meta
|
|
||||||
.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
|
||||||
expected.retain(|url| url != MUSICBUTLER);
|
expected.retain(|url| url != MUSICBUTLER);
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Removing URls that do not exist is okay, they will be ignored.
|
// Removing URls that do not exist is okay, they will be ignored.
|
||||||
artist
|
info.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
.meta
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
|
||||||
|
|
||||||
// Removing a URL.
|
// Removing a URL.
|
||||||
artist
|
info.remove_from_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
.meta
|
|
||||||
.remove_from_property("MusicButler", vec![MUSICBUTLER_2]);
|
|
||||||
expected.retain(|url| url.as_str() != MUSICBUTLER_2);
|
expected.retain(|url| url.as_str() != MUSICBUTLER_2);
|
||||||
assert!(artist.meta.properties.is_empty());
|
assert!(info.properties.is_empty());
|
||||||
|
|
||||||
artist
|
info.remove_from_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
.meta
|
assert!(info.properties.is_empty());
|
||||||
.remove_from_property("MusicButler", vec![MUSICBUTLER_2]);
|
|
||||||
assert!(artist.meta.properties.is_empty());
|
|
||||||
|
|
||||||
// Adding URLs if some exist is okay, they will be ignored.
|
// Adding URLs if some exist is okay, they will be ignored.
|
||||||
artist
|
info.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
.meta
|
|
||||||
.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
artist
|
info.add_to_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.meta
|
|
||||||
.add_to_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Removing URLs if some do not exist is okay, they will be ignored.
|
// Removing URLs if some do not exist is okay, they will be ignored.
|
||||||
artist
|
info.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
.meta
|
|
||||||
.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
|
||||||
expected.retain(|url| url.as_str() != MUSICBUTLER);
|
expected.retain(|url| url.as_str() != MUSICBUTLER);
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
artist
|
info.remove_from_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.meta
|
|
||||||
.remove_from_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
|
||||||
expected.retain(|url| url.as_str() != MUSICBUTLER_2);
|
expected.retain(|url| url.as_str() != MUSICBUTLER_2);
|
||||||
assert!(artist.meta.properties.is_empty());
|
assert!(info.properties.is_empty());
|
||||||
|
|
||||||
// Adding mutliple URLs without clashes.
|
// Adding mutliple URLs without clashes.
|
||||||
artist
|
info.add_to_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.meta
|
|
||||||
.add_to_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Removing multiple URLs without clashes.
|
// Removing multiple URLs without clashes.
|
||||||
artist
|
info.remove_from_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.meta
|
|
||||||
.remove_from_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
|
||||||
expected.clear();
|
expected.clear();
|
||||||
assert!(artist.meta.properties.is_empty());
|
assert!(info.properties.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_musicbutler_urls() {
|
fn set_clear_musicbutler_urls() {
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut artist = Artist::new(ArtistId::new("an artist"));
|
||||||
|
|
||||||
|
let info = &mut artist.meta.info;
|
||||||
let mut expected: Vec<String> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert!(artist.meta.properties.is_empty());
|
assert!(info.properties.is_empty());
|
||||||
|
|
||||||
// Set URLs.
|
// Set URLs.
|
||||||
artist.meta.set_property("MusicButler", vec![MUSICBUTLER]);
|
info.set_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
artist.meta.set_property("MusicButler", vec![MUSICBUTLER_2]);
|
info.set_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
expected.clear();
|
expected.clear();
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
artist
|
info.set_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.meta
|
|
||||||
.set_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
|
||||||
expected.clear();
|
expected.clear();
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(artist.meta.properties.get("MusicButler"), Some(&expected));
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Clear URLs.
|
// Clear URLs.
|
||||||
artist.meta.clear_property("MusicButler");
|
info.clear_property("MusicButler");
|
||||||
expected.clear();
|
expected.clear();
|
||||||
assert!(artist.meta.properties.is_empty());
|
assert!(info.properties.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -417,14 +415,15 @@ mod tests {
|
|||||||
let left = FULL_COLLECTION[0].to_owned();
|
let left = FULL_COLLECTION[0].to_owned();
|
||||||
let mut right = FULL_COLLECTION[1].to_owned();
|
let mut right = FULL_COLLECTION[1].to_owned();
|
||||||
right.meta.id = left.meta.id.clone();
|
right.meta.id = left.meta.id.clone();
|
||||||
right.meta.musicbrainz = MbRefOption::None;
|
right.meta.info.musicbrainz = MbRefOption::None;
|
||||||
right.meta.properties = HashMap::new();
|
right.meta.info.properties = HashMap::new();
|
||||||
|
|
||||||
let mut expected = left.clone();
|
let mut expected = left.clone();
|
||||||
expected.meta.properties = expected
|
expected.meta.info.properties = expected
|
||||||
.meta
|
.meta
|
||||||
|
.info
|
||||||
.properties
|
.properties
|
||||||
.merge(right.clone().meta.properties);
|
.merge(right.clone().meta.info.properties);
|
||||||
expected.albums.append(&mut right.albums.clone());
|
expected.albums.append(&mut right.albums.clone());
|
||||||
expected.albums.sort_unstable();
|
expected.albums.sort_unstable();
|
||||||
|
|
||||||
@ -445,10 +444,11 @@ mod tests {
|
|||||||
left.albums.sort_unstable();
|
left.albums.sort_unstable();
|
||||||
|
|
||||||
let mut expected = left.clone();
|
let mut expected = left.clone();
|
||||||
expected.meta.properties = expected
|
expected.meta.info.properties = expected
|
||||||
.meta
|
.meta
|
||||||
|
.info
|
||||||
.properties
|
.properties
|
||||||
.merge(right.clone().meta.properties);
|
.merge(right.clone().meta.info.properties);
|
||||||
expected.albums.append(&mut right.albums.clone());
|
expected.albums.append(&mut right.albums.clone());
|
||||||
expected.albums.sort_unstable();
|
expected.albums.sort_unstable();
|
||||||
expected.albums.dedup();
|
expected.albums.dedup();
|
||||||
|
@ -97,7 +97,7 @@ impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library>
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::core::{collection::artist::ArtistId, testmod::FULL_COLLECTION};
|
use crate::core::testmod::FULL_COLLECTION;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -190,7 +190,7 @@ mod tests {
|
|||||||
let mut right: Vec<Artist> = vec![left.last().unwrap().clone()];
|
let mut right: Vec<Artist> = vec![left.last().unwrap().clone()];
|
||||||
|
|
||||||
assert!(right.first().unwrap() > left.first().unwrap());
|
assert!(right.first().unwrap() > left.first().unwrap());
|
||||||
let artist_sort = Some(ArtistId::new("Album_Artist 0"));
|
let artist_sort = Some(String::from("Album_Artist 0"));
|
||||||
right[0].meta.sort = artist_sort.clone();
|
right[0].meta.sort = artist_sort.clone();
|
||||||
assert!(right.first().unwrap() < left.first().unwrap());
|
assert!(right.first().unwrap() < left.first().unwrap());
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use crate::core::{
|
use crate::{
|
||||||
|
collection::{album::AlbumInfo, artist::ArtistInfo},
|
||||||
|
core::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{Album, AlbumId, AlbumSeq},
|
album::{Album, AlbumId, AlbumSeq},
|
||||||
artist::{Artist, ArtistId},
|
artist::{Artist, ArtistId},
|
||||||
musicbrainz::MbArtistRef,
|
|
||||||
Collection,
|
Collection,
|
||||||
},
|
},
|
||||||
interface::database::IDatabase,
|
interface::database::IDatabase,
|
||||||
musichoard::{base::IMusicHoardBasePrivate, Error, MusicHoard, NoDatabase},
|
musichoard::{base::IMusicHoardBasePrivate, Error, MusicHoard, NoDatabase},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait IMusicHoardDatabase {
|
pub trait IMusicHoardDatabase {
|
||||||
@ -15,20 +17,19 @@ pub trait IMusicHoardDatabase {
|
|||||||
fn add_artist<IntoId: Into<ArtistId>>(&mut self, artist_id: IntoId) -> Result<(), Error>;
|
fn add_artist<IntoId: Into<ArtistId>>(&mut self, artist_id: IntoId) -> Result<(), Error>;
|
||||||
fn remove_artist<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
fn remove_artist<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
||||||
|
|
||||||
fn set_artist_sort<Id: AsRef<ArtistId>, IntoId: Into<ArtistId>>(
|
fn set_artist_sort<Id: AsRef<ArtistId>, S: Into<String>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: Id,
|
artist_id: Id,
|
||||||
artist_sort: IntoId,
|
artist_sort: S,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
fn clear_artist_sort<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
fn clear_artist_sort<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
||||||
|
|
||||||
fn set_artist_musicbrainz<Id: AsRef<ArtistId>, S: AsRef<str>>(
|
fn set_artist_info<Id: AsRef<ArtistId>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: Id,
|
artist_id: Id,
|
||||||
url: S,
|
info: ArtistInfo,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
fn clear_artist_musicbrainz<Id: AsRef<ArtistId>>(&mut self, artist_id: Id)
|
fn clear_artist_info<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
||||||
-> Result<(), Error>;
|
|
||||||
|
|
||||||
fn add_to_artist_property<Id: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
|
fn add_to_artist_property<Id: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -65,6 +66,17 @@ pub trait IMusicHoardDatabase {
|
|||||||
artist_id: ArtistIdRef,
|
artist_id: ArtistIdRef,
|
||||||
album_id: AlbumIdRef,
|
album_id: AlbumIdRef,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
|
fn set_album_info<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: Id,
|
||||||
|
album_id: AlbumIdRef,
|
||||||
|
info: AlbumInfo,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
fn clear_album_info<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: Id,
|
||||||
|
album_id: AlbumIdRef,
|
||||||
|
) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database, Library> {
|
impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database, Library> {
|
||||||
@ -100,10 +112,10 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_artist_sort<Id: AsRef<ArtistId>, IntoId: Into<ArtistId>>(
|
fn set_artist_sort<Id: AsRef<ArtistId>, S: Into<String>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: Id,
|
artist_id: Id,
|
||||||
artist_sort: IntoId,
|
artist_sort: S,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_artist_and(
|
self.update_artist_and(
|
||||||
artist_id.as_ref(),
|
artist_id.as_ref(),
|
||||||
@ -120,23 +132,17 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_artist_musicbrainz<Id: AsRef<ArtistId>, S: AsRef<str>>(
|
fn set_artist_info<Id: AsRef<ArtistId>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: Id,
|
artist_id: Id,
|
||||||
url: S,
|
info: ArtistInfo,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mb = MbArtistRef::from_url_str(url)?;
|
self.update_artist(artist_id.as_ref(), |artist| artist.meta.info = info)
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
|
||||||
artist.meta.set_musicbrainz_ref(mb)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_artist_musicbrainz<Id: AsRef<ArtistId>>(
|
fn clear_artist_info<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
self.update_artist(artist_id.as_ref(), |artist| {
|
||||||
artist.meta.clear_musicbrainz_ref()
|
artist.meta.info = ArtistInfo::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +153,7 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
values: Vec<S>,
|
values: Vec<S>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
self.update_artist(artist_id.as_ref(), |artist| {
|
||||||
artist.meta.add_to_property(property, values)
|
artist.meta.info.add_to_property(property, values)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +164,7 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
values: Vec<S>,
|
values: Vec<S>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
self.update_artist(artist_id.as_ref(), |artist| {
|
||||||
artist.meta.remove_from_property(property, values)
|
artist.meta.info.remove_from_property(property, values)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +175,7 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
values: Vec<S>,
|
values: Vec<S>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
self.update_artist(artist_id.as_ref(), |artist| {
|
||||||
artist.meta.set_property(property, values)
|
artist.meta.info.set_property(property, values)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +185,7 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
property: S,
|
property: S,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
self.update_artist(artist_id.as_ref(), |artist| {
|
||||||
artist.meta.clear_property(property)
|
artist.meta.info.clear_property(property)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +200,6 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
album_id.as_ref(),
|
album_id.as_ref(),
|
||||||
|album| album.meta.set_seq(AlbumSeq(seq)),
|
|album| album.meta.set_seq(AlbumSeq(seq)),
|
||||||
|artist| artist.albums.sort_unstable(),
|
|artist| artist.albums.sort_unstable(),
|
||||||
|_| {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,9 +213,29 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
album_id.as_ref(),
|
album_id.as_ref(),
|
||||||
|album| album.meta.clear_seq(),
|
|album| album.meta.clear_seq(),
|
||||||
|artist| artist.albums.sort_unstable(),
|
|artist| artist.albums.sort_unstable(),
|
||||||
|_| {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_album_info<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: Id,
|
||||||
|
album_id: AlbumIdRef,
|
||||||
|
info: AlbumInfo,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
|
||||||
|
album.meta.info = info
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_album_info<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: Id,
|
||||||
|
album_id: AlbumIdRef,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
|
||||||
|
album.meta.info = AlbumInfo::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IMusicHoardDatabasePrivate {
|
pub trait IMusicHoardDatabasePrivate {
|
||||||
@ -272,24 +297,34 @@ impl<Database: IDatabase, Library> MusicHoard<Database, Library> {
|
|||||||
self.update_artist_and(artist_id, fn_artist, |_| {})
|
self.update_artist_and(artist_id, fn_artist, |_| {})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_album_and<FnAlbum, FnArtist, FnColl>(
|
fn update_album_and<FnAlbum, FnArtist>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: &ArtistId,
|
artist_id: &ArtistId,
|
||||||
album_id: &AlbumId,
|
album_id: &AlbumId,
|
||||||
fn_album: FnAlbum,
|
fn_album: FnAlbum,
|
||||||
fn_artist: FnArtist,
|
fn_artist: FnArtist,
|
||||||
fn_coll: FnColl,
|
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
FnAlbum: FnOnce(&mut Album),
|
FnAlbum: FnOnce(&mut Album),
|
||||||
FnArtist: FnOnce(&mut Artist),
|
FnArtist: FnOnce(&mut Artist),
|
||||||
FnColl: FnOnce(&mut Collection),
|
|
||||||
{
|
{
|
||||||
let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id)?;
|
let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id)?;
|
||||||
let album = Self::get_album_mut_or_err(artist, album_id)?;
|
let album = Self::get_album_mut_or_err(artist, album_id)?;
|
||||||
fn_album(album);
|
fn_album(album);
|
||||||
fn_artist(artist);
|
fn_artist(artist);
|
||||||
self.update_collection(fn_coll)
|
self.update_collection(|_| {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_album<FnAlbum>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: &ArtistId,
|
||||||
|
album_id: &AlbumId,
|
||||||
|
fn_album: FnAlbum,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
FnAlbum: FnOnce(&mut Album),
|
||||||
|
{
|
||||||
|
self.update_album_and(artist_id, album_id, fn_album, |_| {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,7 +333,10 @@ mod tests {
|
|||||||
use mockall::{predicate, Sequence};
|
use mockall::{predicate, Sequence};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collection::musicbrainz::MbRefOption,
|
collection::{
|
||||||
|
album::{AlbumPrimaryType, AlbumSecondaryType},
|
||||||
|
musicbrainz::{MbArtistRef, MbRefOption},
|
||||||
|
},
|
||||||
core::{
|
core::{
|
||||||
collection::{album::AlbumDate, artist::ArtistId},
|
collection::{album::AlbumDate, artist::ArtistId},
|
||||||
interface::database::{self, MockIDatabase},
|
interface::database::{self, MockIDatabase},
|
||||||
@ -309,8 +347,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
static MUSICBRAINZ: &str =
|
static MBID: &str = "d368baa8-21ca-4759-9731-0b2753071ad8";
|
||||||
"https://musicbrainz.org/artist/d368baa8-21ca-4759-9731-0b2753071ad8";
|
|
||||||
static MUSICBUTLER: &str = "https://www.musicbutler.io/artist-page/483340948";
|
static MUSICBUTLER: &str = "https://www.musicbutler.io/artist-page/483340948";
|
||||||
static MUSICBUTLER_2: &str = "https://www.musicbutler.io/artist-page/658903042/";
|
static MUSICBUTLER_2: &str = "https://www.musicbutler.io/artist-page/658903042/";
|
||||||
|
|
||||||
@ -370,12 +407,12 @@ mod tests {
|
|||||||
let mut music_hoard: MH = MusicHoard::database(database).unwrap();
|
let mut music_hoard: MH = MusicHoard::database(database).unwrap();
|
||||||
|
|
||||||
let artist_1_id = ArtistId::new("the artist");
|
let artist_1_id = ArtistId::new("the artist");
|
||||||
let artist_1_sort = ArtistId::new("artist, the");
|
let artist_1_sort = String::from("artist, the");
|
||||||
|
|
||||||
// Must be after "artist, the", but before "the artist"
|
// Must be after "artist, the", but before "the artist"
|
||||||
let artist_2_id = ArtistId::new("b-artist");
|
let artist_2_id = ArtistId::new("b-artist");
|
||||||
|
|
||||||
assert!(artist_1_sort < artist_2_id);
|
assert!(artist_1_sort < artist_2_id.name);
|
||||||
assert!(artist_2_id < artist_1_id);
|
assert!(artist_2_id < artist_1_id);
|
||||||
|
|
||||||
assert!(music_hoard.add_artist(artist_1_id.clone()).is_ok());
|
assert!(music_hoard.add_artist(artist_1_id.clone()).is_ok());
|
||||||
@ -416,24 +453,20 @@ mod tests {
|
|||||||
fn collection_error() {
|
fn collection_error() {
|
||||||
let mut database = MockIDatabase::new();
|
let mut database = MockIDatabase::new();
|
||||||
database.expect_load().times(1).returning(|| Ok(vec![]));
|
database.expect_load().times(1).returning(|| Ok(vec![]));
|
||||||
database.expect_save().times(1).returning(|_| Ok(()));
|
let mut music_hoard = MusicHoard::database(database).unwrap();
|
||||||
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist_id = ArtistId::new("an artist");
|
||||||
let mut music_hoard = MusicHoard::database(database).unwrap();
|
|
||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
|
||||||
|
|
||||||
let actual_err = music_hoard
|
let actual_err = music_hoard
|
||||||
.set_artist_musicbrainz(&artist_id, MUSICBUTLER)
|
.set_artist_info(&artist_id, ArtistInfo::default())
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
let expected_err = Error::CollectionError(format!(
|
let expected_err =
|
||||||
"an error occurred when processing a URL: invalid artist MusicBrainz URL: {MUSICBUTLER}"
|
Error::CollectionError(format!("artist '{artist_id}' is not in the collection"));
|
||||||
));
|
|
||||||
assert_eq!(actual_err, expected_err);
|
assert_eq!(actual_err, expected_err);
|
||||||
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_musicbrainz_url() {
|
fn set_clear_artist_info() {
|
||||||
let mut database = MockIDatabase::new();
|
let mut database = MockIDatabase::new();
|
||||||
database.expect_load().times(1).returning(|| Ok(vec![]));
|
database.expect_load().times(1).returning(|| Ok(vec![]));
|
||||||
database.expect_save().times(3).returning(|_| Ok(()));
|
database.expect_save().times(3).returning(|_| Ok(()));
|
||||||
@ -445,29 +478,31 @@ mod tests {
|
|||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
||||||
|
|
||||||
let mut expected: MbRefOption<MbArtistRef> = MbRefOption::None;
|
let mut expected: MbRefOption<MbArtistRef> = MbRefOption::None;
|
||||||
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].meta.info.musicbrainz, expected);
|
||||||
|
|
||||||
|
let info = ArtistInfo::new(MbRefOption::Some(MbArtistRef::from_uuid_str(MBID).unwrap()));
|
||||||
|
|
||||||
// Setting a URL on an artist not in the collection is an error.
|
// Setting a URL on an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_artist_musicbrainz(&artist_id_2, MUSICBRAINZ)
|
.set_artist_info(&artist_id_2, info.clone())
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].meta.info.musicbrainz, expected);
|
||||||
|
|
||||||
// Setting a URL on an artist.
|
// Setting a URL on an artist.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_artist_musicbrainz(&artist_id, MUSICBRAINZ)
|
.set_artist_info(&artist_id, info.clone())
|
||||||
.is_ok());
|
.is_ok());
|
||||||
expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
|
expected.replace(MbArtistRef::from_uuid_str(MBID).unwrap());
|
||||||
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].meta.info.musicbrainz, expected);
|
||||||
|
|
||||||
// Clearing URLs on an artist that does not exist is an error.
|
// Clearing URLs on an artist that does not exist is an error.
|
||||||
assert!(music_hoard.clear_artist_musicbrainz(&artist_id_2).is_err());
|
assert!(music_hoard.clear_artist_info(&artist_id_2).is_err());
|
||||||
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].meta.info.musicbrainz, expected);
|
||||||
|
|
||||||
// Clearing URLs.
|
// Clearing URLs.
|
||||||
assert!(music_hoard.clear_artist_musicbrainz(&artist_id).is_ok());
|
assert!(music_hoard.clear_artist_info(&artist_id).is_ok());
|
||||||
expected.take();
|
expected.take();
|
||||||
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].meta.info.musicbrainz, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -483,13 +518,13 @@ mod tests {
|
|||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
||||||
|
|
||||||
let mut expected: Vec<String> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert!(music_hoard.collection[0].meta.properties.is_empty());
|
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
||||||
|
|
||||||
// Adding URLs to an artist not in the collection is an error.
|
// Adding URLs to an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.add_to_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
.add_to_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
||||||
.is_err());
|
.is_err());
|
||||||
assert!(music_hoard.collection[0].meta.properties.is_empty());
|
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
||||||
|
|
||||||
// Adding mutliple URLs without clashes.
|
// Adding mutliple URLs without clashes.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
@ -497,19 +532,15 @@ mod tests {
|
|||||||
.is_ok());
|
.is_ok());
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(
|
let info = &music_hoard.collection[0].meta.info;
|
||||||
music_hoard.collection[0].meta.properties.get("MusicButler"),
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
Some(&expected)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Removing URLs from an artist not in the collection is an error.
|
// Removing URLs from an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.remove_from_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
.remove_from_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(
|
let info = &music_hoard.collection[0].meta.info;
|
||||||
music_hoard.collection[0].meta.properties.get("MusicButler"),
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
Some(&expected)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Removing multiple URLs without clashes.
|
// Removing multiple URLs without clashes.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
@ -520,7 +551,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
expected.clear();
|
expected.clear();
|
||||||
assert!(music_hoard.collection[0].meta.properties.is_empty());
|
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -536,13 +567,13 @@ mod tests {
|
|||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
||||||
|
|
||||||
let mut expected: Vec<String> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert!(music_hoard.collection[0].meta.properties.is_empty());
|
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
||||||
|
|
||||||
// Seting URL on an artist not in the collection is an error.
|
// Seting URL on an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
.set_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
||||||
.is_err());
|
.is_err());
|
||||||
assert!(music_hoard.collection[0].meta.properties.is_empty());
|
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
||||||
|
|
||||||
// Set URLs.
|
// Set URLs.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
@ -551,10 +582,8 @@ mod tests {
|
|||||||
expected.clear();
|
expected.clear();
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(
|
let info = &music_hoard.collection[0].meta.info;
|
||||||
music_hoard.collection[0].meta.properties.get("MusicButler"),
|
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
||||||
Some(&expected)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clearing URLs on an artist that does not exist is an error.
|
// Clearing URLs on an artist that does not exist is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
@ -566,7 +595,7 @@ mod tests {
|
|||||||
.clear_artist_property(&artist_id, "MusicButler")
|
.clear_artist_property(&artist_id, "MusicButler")
|
||||||
.is_ok());
|
.is_ok());
|
||||||
expected.clear();
|
expected.clear();
|
||||||
assert!(music_hoard.collection[0].meta.properties.is_empty());
|
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -614,6 +643,65 @@ mod tests {
|
|||||||
assert_eq!(music_hoard.collection[0].albums[0].meta.seq, AlbumSeq(0));
|
assert_eq!(music_hoard.collection[0].albums[0].meta.seq, AlbumSeq(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_clear_album_info() {
|
||||||
|
let mut database = MockIDatabase::new();
|
||||||
|
|
||||||
|
let artist_id = ArtistId::new("an artist");
|
||||||
|
let album_id = AlbumId::new("an album");
|
||||||
|
let album_id_2 = AlbumId::new("another album");
|
||||||
|
|
||||||
|
let mut database_result = vec![Artist::new(artist_id.clone())];
|
||||||
|
database_result[0].albums.push(Album::new(
|
||||||
|
album_id.clone(),
|
||||||
|
AlbumDate::default(),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
|
));
|
||||||
|
|
||||||
|
database
|
||||||
|
.expect_load()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Ok(database_result));
|
||||||
|
database.expect_save().times(2).returning(|_| Ok(()));
|
||||||
|
let mut music_hoard = MusicHoard::database(database).unwrap();
|
||||||
|
|
||||||
|
let meta = &music_hoard.collection[0].albums[0].meta;
|
||||||
|
assert_eq!(meta.info.musicbrainz, MbRefOption::None);
|
||||||
|
assert_eq!(meta.info.primary_type, None);
|
||||||
|
assert_eq!(meta.info.secondary_types, Vec::new());
|
||||||
|
|
||||||
|
let info = AlbumInfo::new(
|
||||||
|
MbRefOption::CannotHaveMbid,
|
||||||
|
Some(AlbumPrimaryType::Album),
|
||||||
|
vec![AlbumSecondaryType::Live],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Seting info on an album not belonging to the artist is an error.
|
||||||
|
assert!(music_hoard
|
||||||
|
.set_album_info(&artist_id, &album_id_2, info.clone())
|
||||||
|
.is_err());
|
||||||
|
let meta = &music_hoard.collection[0].albums[0].meta;
|
||||||
|
assert_eq!(meta.info, AlbumInfo::default());
|
||||||
|
|
||||||
|
// Set info.
|
||||||
|
assert!(music_hoard
|
||||||
|
.set_album_info(&artist_id, &album_id, info.clone())
|
||||||
|
.is_ok());
|
||||||
|
let meta = &music_hoard.collection[0].albums[0].meta;
|
||||||
|
assert_eq!(meta.info, info);
|
||||||
|
|
||||||
|
// Clearing info on an album that does not exist is an error.
|
||||||
|
assert!(music_hoard
|
||||||
|
.clear_album_info(&artist_id, &album_id_2)
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
// Clear info.
|
||||||
|
assert!(music_hoard.clear_album_info(&artist_id, &album_id).is_ok());
|
||||||
|
let meta = &music_hoard.collection[0].albums[0].meta;
|
||||||
|
assert_eq!(meta.info, AlbumInfo::default());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_database() {
|
fn load_database() {
|
||||||
let mut database = MockIDatabase::new();
|
let mut database = MockIDatabase::new();
|
||||||
|
@ -52,7 +52,7 @@ impl<Database, Library: ILibrary> MusicHoard<Database, Library> {
|
|||||||
name: item.album_artist,
|
name: item.album_artist,
|
||||||
};
|
};
|
||||||
|
|
||||||
let artist_sort = item.album_artist_sort.map(|s| ArtistId { name: s });
|
let artist_sort = item.album_artist_sort;
|
||||||
|
|
||||||
let album_id = AlbumId {
|
let album_id = AlbumId {
|
||||||
title: item.album_title,
|
title: item.album_title,
|
||||||
|
@ -20,12 +20,9 @@ use crate::core::collection::{
|
|||||||
Collection,
|
Collection,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::core::{
|
use crate::core::interface::{
|
||||||
collection,
|
|
||||||
interface::{
|
|
||||||
database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError},
|
database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError},
|
||||||
library::Error as LibraryError,
|
library::Error as LibraryError,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The Music Hoard. It is responsible for pulling information from both the library and the
|
/// The Music Hoard. It is responsible for pulling information from both the library and the
|
||||||
@ -79,12 +76,6 @@ impl Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<collection::Error> for Error {
|
|
||||||
fn from(err: collection::Error) -> Self {
|
|
||||||
Error::CollectionError(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LibraryError> for Error {
|
impl From<LibraryError> for Error {
|
||||||
fn from(err: LibraryError) -> Error {
|
fn from(err: LibraryError) -> Error {
|
||||||
Error::LibraryError(err.to_string())
|
Error::LibraryError(err.to_string())
|
||||||
|
@ -2,8 +2,8 @@ use once_cell::sync::Lazy;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::core::collection::{
|
use crate::core::collection::{
|
||||||
album::{Album, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSeq},
|
album::{Album, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSeq},
|
||||||
artist::{Artist, ArtistId, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistInfo, ArtistMeta},
|
||||||
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption},
|
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption},
|
||||||
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
||||||
};
|
};
|
||||||
|
10
src/external/database/serde/deserialize.rs
vendored
10
src/external/database/serde/deserialize.rs
vendored
@ -4,8 +4,8 @@ use serde::{de::Visitor, Deserialize, Deserializer};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collection::{
|
collection::{
|
||||||
album::AlbumMeta,
|
album::{AlbumInfo, AlbumMeta},
|
||||||
artist::ArtistMeta,
|
artist::{ArtistInfo, ArtistMeta},
|
||||||
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption, Mbid},
|
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption, Mbid},
|
||||||
},
|
},
|
||||||
core::collection::{
|
core::collection::{
|
||||||
@ -114,10 +114,12 @@ impl From<DeserializeArtist> for Artist {
|
|||||||
Artist {
|
Artist {
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId::new(artist.name),
|
id: ArtistId::new(artist.name),
|
||||||
sort: artist.sort.map(ArtistId::new),
|
sort: artist.sort,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: artist.musicbrainz.into(),
|
musicbrainz: artist.musicbrainz.into(),
|
||||||
properties: artist.properties,
|
properties: artist.properties,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: artist.albums.into_iter().map(Into::into).collect(),
|
albums: artist.albums.into_iter().map(Into::into).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,10 +132,12 @@ impl From<DeserializeAlbum> for Album {
|
|||||||
id: AlbumId { title: album.title },
|
id: AlbumId { title: album.title },
|
||||||
date: AlbumDate::default(),
|
date: AlbumDate::default(),
|
||||||
seq: AlbumSeq(album.seq),
|
seq: AlbumSeq(album.seq),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: album.musicbrainz.into(),
|
musicbrainz: album.musicbrainz.into(),
|
||||||
primary_type: album.primary_type.map(Into::into),
|
primary_type: album.primary_type.map(Into::into),
|
||||||
secondary_types: album.secondary_types.into_iter().map(Into::into).collect(),
|
secondary_types: album.secondary_types.into_iter().map(Into::into).collect(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
src/external/database/serde/serialize.rs
vendored
10
src/external/database/serde/serialize.rs
vendored
@ -72,10 +72,11 @@ impl<'a> From<&'a Artist> for SerializeArtist<'a> {
|
|||||||
fn from(artist: &'a Artist) -> Self {
|
fn from(artist: &'a Artist) -> Self {
|
||||||
SerializeArtist {
|
SerializeArtist {
|
||||||
name: &artist.meta.id.name,
|
name: &artist.meta.id.name,
|
||||||
sort: artist.meta.sort.as_ref().map(|id| id.name.as_ref()),
|
sort: artist.meta.sort.as_deref(),
|
||||||
musicbrainz: (&artist.meta.musicbrainz).into(),
|
musicbrainz: (&artist.meta.info.musicbrainz).into(),
|
||||||
properties: artist
|
properties: artist
|
||||||
.meta
|
.meta
|
||||||
|
.info
|
||||||
.properties
|
.properties
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.as_ref(), v))
|
.map(|(k, v)| (k.as_ref(), v))
|
||||||
@ -90,10 +91,11 @@ impl<'a> From<&'a Album> for SerializeAlbum<'a> {
|
|||||||
SerializeAlbum {
|
SerializeAlbum {
|
||||||
title: &album.meta.id.title,
|
title: &album.meta.id.title,
|
||||||
seq: album.meta.seq.0,
|
seq: album.meta.seq.0,
|
||||||
musicbrainz: (&album.meta.musicbrainz).into(),
|
musicbrainz: (&album.meta.info.musicbrainz).into(),
|
||||||
primary_type: album.meta.primary_type.map(Into::into),
|
primary_type: album.meta.info.primary_type.map(Into::into),
|
||||||
secondary_types: album
|
secondary_types: album
|
||||||
.meta
|
.meta
|
||||||
|
.info
|
||||||
.secondary_types
|
.secondary_types
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
|
15
src/external/musicbrainz/api/mod.rs
vendored
15
src/external/musicbrainz/api/mod.rs
vendored
@ -4,8 +4,7 @@ use serde::{de::Visitor, Deserialize, Deserializer};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
|
album::{AlbumDate, AlbumPrimaryType, AlbumSecondaryType},
|
||||||
artist::ArtistId,
|
|
||||||
musicbrainz::Mbid,
|
musicbrainz::Mbid,
|
||||||
Error as CollectionError,
|
Error as CollectionError,
|
||||||
},
|
},
|
||||||
@ -62,8 +61,8 @@ impl<Http> MusicBrainzClient<Http> {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct MbArtistMeta {
|
pub struct MbArtistMeta {
|
||||||
pub id: Mbid,
|
pub id: Mbid,
|
||||||
pub name: ArtistId,
|
pub name: String,
|
||||||
pub sort_name: ArtistId,
|
pub sort_name: String,
|
||||||
pub disambiguation: Option<String>,
|
pub disambiguation: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +79,8 @@ impl From<SerdeMbArtistMeta> for MbArtistMeta {
|
|||||||
fn from(value: SerdeMbArtistMeta) -> Self {
|
fn from(value: SerdeMbArtistMeta) -> Self {
|
||||||
MbArtistMeta {
|
MbArtistMeta {
|
||||||
id: value.id.into(),
|
id: value.id.into(),
|
||||||
name: value.name.into(),
|
name: value.name,
|
||||||
sort_name: value.sort_name.into(),
|
sort_name: value.sort_name,
|
||||||
disambiguation: value.disambiguation,
|
disambiguation: value.disambiguation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +89,7 @@ impl From<SerdeMbArtistMeta> for MbArtistMeta {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct MbReleaseGroupMeta {
|
pub struct MbReleaseGroupMeta {
|
||||||
pub id: Mbid,
|
pub id: Mbid,
|
||||||
pub title: AlbumId,
|
pub title: String,
|
||||||
pub first_release_date: AlbumDate,
|
pub first_release_date: AlbumDate,
|
||||||
pub primary_type: AlbumPrimaryType,
|
pub primary_type: AlbumPrimaryType,
|
||||||
pub secondary_types: Option<Vec<AlbumSecondaryType>>,
|
pub secondary_types: Option<Vec<AlbumSecondaryType>>,
|
||||||
@ -110,7 +109,7 @@ impl From<SerdeMbReleaseGroupMeta> for MbReleaseGroupMeta {
|
|||||||
fn from(value: SerdeMbReleaseGroupMeta) -> Self {
|
fn from(value: SerdeMbReleaseGroupMeta) -> Self {
|
||||||
MbReleaseGroupMeta {
|
MbReleaseGroupMeta {
|
||||||
id: value.id.into(),
|
id: value.id.into(),
|
||||||
title: value.title.into(),
|
title: value.title,
|
||||||
first_release_date: value.first_release_date.into(),
|
first_release_date: value.first_release_date.into(),
|
||||||
primary_type: value.primary_type.into(),
|
primary_type: value.primary_type.into(),
|
||||||
secondary_types: value
|
secondary_types: value
|
||||||
|
@ -7,6 +7,7 @@ macro_rules! full_collection {
|
|||||||
name: "Album_Artist ‘A’".to_string(),
|
name: "Album_Artist ‘A’".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
||||||
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000"
|
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
@ -21,6 +22,7 @@ macro_rules! full_collection {
|
|||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -29,12 +31,14 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: 1998.into(),
|
date: 1998.into(),
|
||||||
seq: AlbumSeq(1),
|
seq: AlbumSeq(1),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
|
||||||
"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000"
|
"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -92,10 +96,12 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: (2015, 4).into(),
|
date: (2015, 4).into(),
|
||||||
seq: AlbumSeq(1),
|
seq: AlbumSeq(1),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -129,6 +135,7 @@ macro_rules! full_collection {
|
|||||||
name: "Album_Artist ‘B’".to_string(),
|
name: "Album_Artist ‘B’".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
||||||
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111"
|
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
@ -147,6 +154,7 @@ macro_rules! full_collection {
|
|||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -155,10 +163,12 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: (2003, 6, 6).into(),
|
date: (2003, 6, 6).into(),
|
||||||
seq: AlbumSeq(1),
|
seq: AlbumSeq(1),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -194,12 +204,14 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: 2008.into(),
|
date: 2008.into(),
|
||||||
seq: AlbumSeq(3),
|
seq: AlbumSeq(3),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
|
||||||
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111"
|
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -235,12 +247,14 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: 2009.into(),
|
date: 2009.into(),
|
||||||
seq: AlbumSeq(2),
|
seq: AlbumSeq(2),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbAlbumRef::from_url_str(
|
||||||
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112"
|
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -276,10 +290,12 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: 2015.into(),
|
date: 2015.into(),
|
||||||
seq: AlbumSeq(4),
|
seq: AlbumSeq(4),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -315,12 +331,12 @@ macro_rules! full_collection {
|
|||||||
id: ArtistId {
|
id: ArtistId {
|
||||||
name: "The Album_Artist ‘C’".to_string(),
|
name: "The Album_Artist ‘C’".to_string(),
|
||||||
},
|
},
|
||||||
sort: Some(ArtistId {
|
sort: Some("Album_Artist ‘C’, The".to_string()),
|
||||||
name: "Album_Artist ‘C’, The".to_string(),
|
info: ArtistInfo {
|
||||||
}),
|
|
||||||
musicbrainz: MbRefOption::CannotHaveMbid,
|
musicbrainz: MbRefOption::CannotHaveMbid,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -329,10 +345,12 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: 1985.into(),
|
date: 1985.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -368,10 +386,12 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: 2018.into(),
|
date: 2018.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -408,9 +428,11 @@ macro_rules! full_collection {
|
|||||||
name: "Album_Artist ‘D’".to_string(),
|
name: "Album_Artist ‘D’".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -419,10 +441,12 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: 1995.into(),
|
date: 1995.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -458,10 +482,12 @@ macro_rules! full_collection {
|
|||||||
},
|
},
|
||||||
date: 2028.into(),
|
date: 2028.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
|
@ -8,9 +8,11 @@ macro_rules! library_collection {
|
|||||||
name: "Album_Artist ‘A’".to_string(),
|
name: "Album_Artist ‘A’".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -19,9 +21,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: 1998.into(),
|
date: 1998.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -80,9 +80,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: (2015, 4).into(),
|
date: (2015, 4).into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -117,9 +115,11 @@ macro_rules! library_collection {
|
|||||||
name: "Album_Artist ‘B’".to_string(),
|
name: "Album_Artist ‘B’".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -128,9 +128,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: (2003, 6, 6).into(),
|
date: (2003, 6, 6).into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -167,9 +165,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: 2008.into(),
|
date: 2008.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -206,9 +202,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: 2009.into(),
|
date: 2009.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -245,9 +239,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: 2015.into(),
|
date: 2015.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -284,12 +276,12 @@ macro_rules! library_collection {
|
|||||||
id: ArtistId {
|
id: ArtistId {
|
||||||
name: "The Album_Artist ‘C’".to_string(),
|
name: "The Album_Artist ‘C’".to_string(),
|
||||||
},
|
},
|
||||||
sort: Some(ArtistId {
|
sort: Some("Album_Artist ‘C’, The".to_string()),
|
||||||
name: "Album_Artist ‘C’, The".to_string(),
|
info: ArtistInfo {
|
||||||
}),
|
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -298,9 +290,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: 1985.into(),
|
date: 1985.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -337,9 +327,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: 2018.into(),
|
date: 2018.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -377,9 +365,11 @@ macro_rules! library_collection {
|
|||||||
name: "Album_Artist ‘D’".to_string(),
|
name: "Album_Artist ‘D’".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -388,9 +378,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: 1995.into(),
|
date: 1995.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -427,9 +415,7 @@ macro_rules! library_collection {
|
|||||||
},
|
},
|
||||||
date: 2028.into(),
|
date: 2028.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
|
@ -73,7 +73,7 @@ impl IAppInteractBrowse for AppMachine<BrowseState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_musicbrainz(self) -> Self::APP {
|
fn fetch_musicbrainz(self) -> Self::APP {
|
||||||
AppMachine::app_fetch_new(self.inner)
|
AppMachine::app_fetch_first(self.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::AlbumMeta,
|
album::{Album, AlbumMeta},
|
||||||
artist::{Artist, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistMeta},
|
||||||
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
|
musicbrainz::{IMusicBrainzRef, MbArtistRef, MbRefOption, Mbid},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
@ -46,12 +46,27 @@ impl FetchState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FetchError {
|
||||||
|
NothingToFetch,
|
||||||
|
SubmitError(DaemonError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DaemonError> for FetchError {
|
||||||
|
fn from(value: DaemonError) -> Self {
|
||||||
|
FetchError::SubmitError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AppMachine<FetchState> {
|
impl AppMachine<FetchState> {
|
||||||
fn fetch_state(inner: AppInner, state: FetchState) -> Self {
|
fn fetch_state(inner: AppInner, state: FetchState) -> Self {
|
||||||
AppMachine::new(inner, state)
|
AppMachine::new(inner, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app_fetch_new(inner: AppInner) -> App {
|
pub fn app_fetch_first(inner: AppInner) -> App {
|
||||||
|
Self::app_fetch_new(inner, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_fetch_new(inner: AppInner, first: bool) -> App {
|
||||||
let coll = inner.music_hoard.get_collection();
|
let coll = inner.music_hoard.get_collection();
|
||||||
let artist = match inner.selection.state_artist(coll) {
|
let artist = match inner.selection.state_artist(coll) {
|
||||||
Some(artist_state) => &coll[artist_state.index],
|
Some(artist_state) => &coll[artist_state.index],
|
||||||
@ -61,16 +76,38 @@ impl AppMachine<FetchState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (fetch_tx, fetch_rx) = mpsc::channel::<MbApiResult>();
|
let (fetch_tx, fetch_rx) = mpsc::channel::<MbApiResult>();
|
||||||
if let Err(err) = Self::submit_fetch_job(&*inner.musicbrainz, fetch_tx, artist) {
|
|
||||||
return AppMachine::error_state(inner, err.to_string()).into();
|
|
||||||
}
|
|
||||||
|
|
||||||
let fetch = FetchState::new(fetch_rx);
|
let fetch = FetchState::new(fetch_rx);
|
||||||
AppMachine::app_fetch(inner, fetch, true)
|
match Self::submit_fetch_job(&*inner.musicbrainz, fetch_tx, artist) {
|
||||||
|
Ok(()) => AppMachine::fetch_state(inner, fetch).into(),
|
||||||
|
Err(FetchError::NothingToFetch) => {
|
||||||
|
if first {
|
||||||
|
AppMachine::match_state(inner, MatchState::new(None, fetch)).into()
|
||||||
|
} else {
|
||||||
|
AppMachine::browse_state(inner).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(FetchError::SubmitError(daemon_err)) => {
|
||||||
|
AppMachine::error_state(inner, daemon_err.to_string()).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app_fetch_next(inner: AppInner, fetch: FetchState) -> App {
|
pub fn app_fetch_next(inner: AppInner, mut fetch: FetchState) -> App {
|
||||||
Self::app_fetch(inner, fetch, false)
|
match fetch.try_recv() {
|
||||||
|
Ok(fetch_result) => match fetch_result {
|
||||||
|
Ok(next_match) => {
|
||||||
|
let current = Some(next_match);
|
||||||
|
AppMachine::match_state(inner, MatchState::new(current, fetch)).into()
|
||||||
|
}
|
||||||
|
Err(fetch_err) => {
|
||||||
|
AppMachine::error_state(inner, format!("fetch failed: {fetch_err}")).into()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(recv_err) => match recv_err {
|
||||||
|
TryRecvError::Empty => AppMachine::fetch_state(inner, fetch).into(),
|
||||||
|
TryRecvError::Disconnected => Self::app_fetch_new(inner, false),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app_lookup_artist(
|
pub fn app_lookup_artist(
|
||||||
@ -86,10 +123,13 @@ impl AppMachine<FetchState> {
|
|||||||
pub fn app_lookup_album(
|
pub fn app_lookup_album(
|
||||||
inner: AppInner,
|
inner: AppInner,
|
||||||
fetch: FetchState,
|
fetch: FetchState,
|
||||||
|
artist_id: &ArtistId,
|
||||||
album: &AlbumMeta,
|
album: &AlbumMeta,
|
||||||
mbid: Mbid,
|
mbid: Mbid,
|
||||||
) -> App {
|
) -> App {
|
||||||
let f = Self::submit_lookup_release_group_job;
|
let f = |mb: &dyn IMbJobSender, rs, album, mbid| {
|
||||||
|
Self::submit_lookup_release_group_job(mb, rs, artist_id, album, mbid)
|
||||||
|
};
|
||||||
Self::app_lookup(f, inner, fetch, album, mbid)
|
Self::app_lookup(f, inner, fetch, album, mbid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,48 +151,41 @@ impl AppMachine<FetchState> {
|
|||||||
Self::app_fetch_next(inner, fetch)
|
Self::app_fetch_next(inner, fetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app_fetch(inner: AppInner, mut fetch: FetchState, first: bool) -> App {
|
|
||||||
match fetch.try_recv() {
|
|
||||||
Ok(fetch_result) => match fetch_result {
|
|
||||||
Ok(next_match) => {
|
|
||||||
let current = Some(next_match);
|
|
||||||
AppMachine::match_state(inner, MatchState::new(current, fetch)).into()
|
|
||||||
}
|
|
||||||
Err(fetch_err) => {
|
|
||||||
AppMachine::error_state(inner, format!("fetch failed: {fetch_err}")).into()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(recv_err) => match recv_err {
|
|
||||||
TryRecvError::Empty => AppMachine::fetch_state(inner, fetch).into(),
|
|
||||||
TryRecvError::Disconnected => {
|
|
||||||
if first {
|
|
||||||
AppMachine::match_state(inner, MatchState::new(None, fetch)).into()
|
|
||||||
} else {
|
|
||||||
AppMachine::browse_state(inner).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_fetch_job(
|
fn submit_fetch_job(
|
||||||
musicbrainz: &dyn IMbJobSender,
|
musicbrainz: &dyn IMbJobSender,
|
||||||
result_sender: ResultSender,
|
result_sender: ResultSender,
|
||||||
artist: &Artist,
|
artist: &Artist,
|
||||||
) -> Result<(), DaemonError> {
|
) -> Result<(), FetchError> {
|
||||||
let requests = match artist.meta.musicbrainz {
|
let requests = match artist.meta.info.musicbrainz {
|
||||||
MbRefOption::Some(ref arid) => {
|
MbRefOption::Some(ref arid) => {
|
||||||
|
Self::fetch_albums_requests(&artist.meta.id, arid, &artist.albums)
|
||||||
|
}
|
||||||
|
MbRefOption::CannotHaveMbid => VecDeque::new(),
|
||||||
|
MbRefOption::None => Self::fetch_artist_request(&artist.meta),
|
||||||
|
};
|
||||||
|
if requests.is_empty() {
|
||||||
|
return Err(FetchError::NothingToFetch);
|
||||||
|
}
|
||||||
|
Ok(musicbrainz.submit_background_job(result_sender, requests)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_albums_requests(
|
||||||
|
artist: &ArtistId,
|
||||||
|
arid: &MbArtistRef,
|
||||||
|
albums: &[Album],
|
||||||
|
) -> VecDeque<MbParams> {
|
||||||
let arid = arid.mbid();
|
let arid = arid.mbid();
|
||||||
let albums = artist.albums.iter();
|
|
||||||
albums
|
albums
|
||||||
.filter(|album| matches!(album.meta.musicbrainz, MbRefOption::None))
|
.iter()
|
||||||
.map(|album| MbParams::search_release_group(arid.clone(), album.meta.clone()))
|
.filter(|album| matches!(album.meta.info.musicbrainz, MbRefOption::None))
|
||||||
|
.map(|album| {
|
||||||
|
MbParams::search_release_group(artist.clone(), arid.clone(), album.meta.clone())
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
MbRefOption::CannotHaveMbid => return Ok(()),
|
|
||||||
MbRefOption::None => VecDeque::from([MbParams::search_artist(artist.meta.clone())]),
|
fn fetch_artist_request(meta: &ArtistMeta) -> VecDeque<MbParams> {
|
||||||
};
|
VecDeque::from([MbParams::search_artist(meta.clone())])
|
||||||
musicbrainz.submit_background_job(result_sender, requests)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_lookup_artist_job(
|
fn submit_lookup_artist_job(
|
||||||
@ -168,10 +201,15 @@ impl AppMachine<FetchState> {
|
|||||||
fn submit_lookup_release_group_job(
|
fn submit_lookup_release_group_job(
|
||||||
musicbrainz: &dyn IMbJobSender,
|
musicbrainz: &dyn IMbJobSender,
|
||||||
result_sender: ResultSender,
|
result_sender: ResultSender,
|
||||||
|
artist_id: &ArtistId,
|
||||||
album: &AlbumMeta,
|
album: &AlbumMeta,
|
||||||
mbid: Mbid,
|
mbid: Mbid,
|
||||||
) -> Result<(), DaemonError> {
|
) -> Result<(), DaemonError> {
|
||||||
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid)]);
|
let requests = VecDeque::from([MbParams::lookup_release_group(
|
||||||
|
artist_id.clone(),
|
||||||
|
album.clone(),
|
||||||
|
mbid,
|
||||||
|
)]);
|
||||||
musicbrainz.submit_foreground_job(result_sender, requests)
|
musicbrainz.submit_foreground_job(result_sender, requests)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,12 +245,16 @@ impl IAppEventFetch for AppMachine<FetchState> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use mockall::predicate;
|
use mockall::predicate;
|
||||||
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid};
|
use musichoard::collection::{
|
||||||
|
album::AlbumMeta,
|
||||||
|
artist::{ArtistId, ArtistMeta},
|
||||||
|
musicbrainz::Mbid,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
machine::tests::{inner, music_hoard},
|
machine::tests::{inner, music_hoard},
|
||||||
Delta, IApp, IAppAccess, IAppInteractBrowse, MatchStateInfo, MissOption, SearchOption,
|
Delta, IApp, IAppAccess, IAppInteractBrowse, MatchOption, MatchStateInfo,
|
||||||
},
|
},
|
||||||
lib::interface::musicbrainz::{
|
lib::interface::musicbrainz::{
|
||||||
self,
|
self,
|
||||||
@ -257,18 +299,23 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fetch_no_artist() {
|
fn fetch_no_artist() {
|
||||||
let app = AppMachine::app_fetch_new(inner(music_hoard(vec![])));
|
let app = AppMachine::app_fetch_first(inner(music_hoard(vec![])));
|
||||||
assert!(matches!(app.state(), AppState::Error(_)));
|
assert!(matches!(app.state(), AppState::Error(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_release_group_expectation(
|
fn search_release_group_expectation(
|
||||||
job_sender: &mut MockIMbJobSender,
|
job_sender: &mut MockIMbJobSender,
|
||||||
arid: &Mbid,
|
artist_id: &ArtistId,
|
||||||
|
artist_mbid: &Mbid,
|
||||||
albums: &[AlbumMeta],
|
albums: &[AlbumMeta],
|
||||||
) {
|
) {
|
||||||
let mut requests = VecDeque::new();
|
let mut requests = VecDeque::new();
|
||||||
for album in albums.iter() {
|
for album in albums.iter() {
|
||||||
requests.push_back(MbParams::search_release_group(arid.clone(), album.clone()));
|
requests.push_back(MbParams::search_release_group(
|
||||||
|
artist_id.clone(),
|
||||||
|
artist_mbid.clone(),
|
||||||
|
album.clone(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
job_sender
|
job_sender
|
||||||
.expect_submit_background_job()
|
.expect_submit_background_job()
|
||||||
@ -281,13 +328,19 @@ mod tests {
|
|||||||
fn fetch_albums() {
|
fn fetch_albums() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
|
|
||||||
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
let artist_id = COLLECTION[1].meta.id.clone();
|
||||||
|
let artist_mbid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||||
|
|
||||||
let album_1_meta = COLLECTION[1].albums[0].meta.clone();
|
let album_1_meta = COLLECTION[1].albums[0].meta.clone();
|
||||||
let album_4_meta = COLLECTION[1].albums[3].meta.clone();
|
let album_4_meta = COLLECTION[1].albums[3].meta.clone();
|
||||||
|
|
||||||
// Other albums have an MBID and so they will be skipped.
|
// Other albums have an MBID and so they will be skipped.
|
||||||
search_release_group_expectation(&mut mb_job_sender, &arid, &[album_1_meta, album_4_meta]);
|
search_release_group_expectation(
|
||||||
|
&mut mb_job_sender,
|
||||||
|
&artist_id,
|
||||||
|
&artist_mbid,
|
||||||
|
&[album_1_meta, album_4_meta],
|
||||||
|
);
|
||||||
|
|
||||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
let inner = AppInner::new(music_hoard, mb_job_sender);
|
let inner = AppInner::new(music_hoard, mb_job_sender);
|
||||||
@ -297,11 +350,19 @@ mod tests {
|
|||||||
let app = browse.increment_selection(Delta::Line);
|
let app = browse.increment_selection(Delta::Line);
|
||||||
|
|
||||||
let app = app.unwrap_browse().fetch_musicbrainz();
|
let app = app.unwrap_browse().fetch_musicbrainz();
|
||||||
assert!(matches!(app, AppState::Match(_)));
|
assert!(matches!(app, AppState::Fetch(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_album_expectation(job_sender: &mut MockIMbJobSender, album: &AlbumMeta) {
|
fn lookup_album_expectation(
|
||||||
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid())]);
|
job_sender: &mut MockIMbJobSender,
|
||||||
|
artist_id: &ArtistId,
|
||||||
|
album: &AlbumMeta,
|
||||||
|
) {
|
||||||
|
let requests = VecDeque::from([MbParams::lookup_release_group(
|
||||||
|
artist_id.clone(),
|
||||||
|
album.clone(),
|
||||||
|
mbid(),
|
||||||
|
)]);
|
||||||
job_sender
|
job_sender
|
||||||
.expect_submit_foreground_job()
|
.expect_submit_foreground_job()
|
||||||
.with(predicate::always(), predicate::eq(requests))
|
.with(predicate::always(), predicate::eq(requests))
|
||||||
@ -313,8 +374,9 @@ mod tests {
|
|||||||
fn lookup_album() {
|
fn lookup_album() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
|
|
||||||
|
let artist_id = COLLECTION[1].meta.id.clone();
|
||||||
let album = COLLECTION[1].albums[0].meta.clone();
|
let album = COLLECTION[1].albums[0].meta.clone();
|
||||||
lookup_album_expectation(&mut mb_job_sender, &album);
|
lookup_album_expectation(&mut mb_job_sender, &artist_id, &album);
|
||||||
|
|
||||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
let inner = AppInner::new(music_hoard, mb_job_sender);
|
let inner = AppInner::new(music_hoard, mb_job_sender);
|
||||||
@ -322,7 +384,7 @@ mod tests {
|
|||||||
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
||||||
let fetch = FetchState::new(fetch_rx);
|
let fetch = FetchState::new(fetch_rx);
|
||||||
|
|
||||||
AppMachine::app_lookup_album(inner, fetch, &album, mbid());
|
AppMachine::app_lookup_album(inner, fetch, &artist_id, &album, mbid());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
|
fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
|
||||||
@ -351,7 +413,7 @@ mod tests {
|
|||||||
let app = browse.increment_selection(Delta::Line);
|
let app = browse.increment_selection(Delta::Line);
|
||||||
|
|
||||||
let app = app.unwrap_browse().fetch_musicbrainz();
|
let app = app.unwrap_browse().fetch_musicbrainz();
|
||||||
assert!(matches!(app, AppState::Match(_)));
|
assert!(matches!(app, AppState::Fetch(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
|
fn lookup_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
|
||||||
@ -442,15 +504,15 @@ mod tests {
|
|||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = FetchState::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let mut app = AppMachine::app_fetch(inner, fetch, true);
|
let mut app = AppMachine::app_fetch_next(inner, fetch);
|
||||||
assert!(matches!(app, AppState::Match(_)));
|
assert!(matches!(app, AppState::Match(_)));
|
||||||
|
|
||||||
let public = app.get();
|
let public = app.get();
|
||||||
let match_state = public.state.unwrap_match();
|
let match_state = public.state.unwrap_match();
|
||||||
let match_options = vec![
|
let match_options = vec![
|
||||||
artist_match.into(),
|
artist_match.into(),
|
||||||
SearchOption::None(MissOption::CannotHaveMbid),
|
MatchOption::CannotHaveMbid,
|
||||||
SearchOption::None(MissOption::ManualInputMbid),
|
MatchOption::ManualInputMbid,
|
||||||
];
|
];
|
||||||
let expected = MatchStateInfo::artist_search(artist, match_options);
|
let expected = MatchStateInfo::artist_search(artist, match_options);
|
||||||
assert_eq!(match_state.info, Some(expected).as_ref());
|
assert_eq!(match_state.info, Some(expected).as_ref());
|
||||||
@ -465,7 +527,7 @@ mod tests {
|
|||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = FetchState::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
let app = AppMachine::app_fetch_next(inner, fetch);
|
||||||
assert!(matches!(app, AppState::Error(_)));
|
assert!(matches!(app, AppState::Error(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,26 +537,28 @@ mod tests {
|
|||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = FetchState::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
let app = AppMachine::app_fetch_next(inner, fetch);
|
||||||
assert!(matches!(app, AppState::Fetch(_)));
|
assert!(matches!(app, AppState::Fetch(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recv_err_disconnected_first() {
|
fn recv_err_empty_first() {
|
||||||
let (_, rx) = mpsc::channel::<MbApiResult>();
|
let mut collection = COLLECTION.clone();
|
||||||
|
collection[0].albums.clear();
|
||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let app = AppMachine::app_fetch_first(inner(music_hoard(collection)));
|
||||||
let fetch = FetchState::new(rx);
|
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
|
||||||
assert!(matches!(app, AppState::Match(_)));
|
assert!(matches!(app, AppState::Match(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recv_err_disconnected_next() {
|
fn recv_err_empty_next() {
|
||||||
let (_, rx) = mpsc::channel::<MbApiResult>();
|
let mut collection = COLLECTION.clone();
|
||||||
|
collection[0].albums.clear();
|
||||||
|
|
||||||
|
let (_, rx) = mpsc::channel::<MbApiResult>();
|
||||||
let fetch = FetchState::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch_next(inner(music_hoard(COLLECTION.clone())), fetch);
|
|
||||||
|
let app = AppMachine::app_fetch_next(inner(music_hoard(collection)), fetch);
|
||||||
assert!(matches!(app, AppState::Browse(_)));
|
assert!(matches!(app, AppState::Browse(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,7 +568,7 @@ mod tests {
|
|||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = FetchState::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
let app = AppMachine::app_fetch_next(inner, fetch);
|
||||||
assert!(matches!(app, AppState::Fetch(_)));
|
assert!(matches!(app, AppState::Fetch(_)));
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let artist = COLLECTION[3].meta.clone();
|
||||||
|
@ -1,14 +1,81 @@
|
|||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
||||||
use musichoard::collection::musicbrainz::Mbid;
|
use musichoard::collection::{
|
||||||
|
album::{AlbumInfo, AlbumMeta},
|
||||||
use crate::tui::app::{
|
artist::{ArtistInfo, ArtistMeta},
|
||||||
machine::{fetch_state::FetchState, input::Input, App, AppInner, AppMachine},
|
musicbrainz::{MbRefOption, Mbid},
|
||||||
AlbumMatches, AppPublicState, AppState, ArtistMatches, IAppInteractMatch, ListOption,
|
|
||||||
LookupOption, MatchStateInfo, MatchStatePublic, MissOption, SearchOption, WidgetState,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: PartialEq> ListOption<T> {
|
use crate::tui::{
|
||||||
|
app::{
|
||||||
|
machine::{fetch_state::FetchState, input::Input, App, AppInner, AppMachine},
|
||||||
|
AlbumMatches, AppPublicState, AppState, ArtistMatches, IAppInteractMatch, ListOption,
|
||||||
|
MatchOption, MatchStateInfo, MatchStatePublic, WidgetState,
|
||||||
|
},
|
||||||
|
lib::interface::musicbrainz::api::{Lookup, Match},
|
||||||
|
};
|
||||||
|
|
||||||
|
trait GetInfoMeta {
|
||||||
|
type InfoType;
|
||||||
|
}
|
||||||
|
impl GetInfoMeta for ArtistMeta {
|
||||||
|
type InfoType = ArtistInfo;
|
||||||
|
}
|
||||||
|
impl GetInfoMeta for AlbumMeta {
|
||||||
|
type InfoType = AlbumInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait GetInfo {
|
||||||
|
type InfoType;
|
||||||
|
fn into_info(self, info: Self::InfoType) -> InfoOption<Self::InfoType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InfoOption<T> {
|
||||||
|
Info(T),
|
||||||
|
NeedInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_match_option_artist_into_info {
|
||||||
|
($holder:ident) => {
|
||||||
|
impl GetInfo for MatchOption<$holder<ArtistMeta>> {
|
||||||
|
type InfoType = ArtistInfo;
|
||||||
|
|
||||||
|
fn into_info(self, mut info: Self::InfoType) -> InfoOption<Self::InfoType> {
|
||||||
|
match self {
|
||||||
|
MatchOption::Some(option) => info.musicbrainz = option.item.info.musicbrainz,
|
||||||
|
MatchOption::CannotHaveMbid => info.musicbrainz = MbRefOption::CannotHaveMbid,
|
||||||
|
MatchOption::ManualInputMbid => return InfoOption::NeedInput,
|
||||||
|
}
|
||||||
|
InfoOption::Info(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_match_option_artist_into_info!(Match);
|
||||||
|
impl_match_option_artist_into_info!(Lookup);
|
||||||
|
|
||||||
|
macro_rules! impl_match_option_album_into_info {
|
||||||
|
($holder:ident) => {
|
||||||
|
impl GetInfo for MatchOption<$holder<AlbumMeta>> {
|
||||||
|
type InfoType = AlbumInfo;
|
||||||
|
|
||||||
|
fn into_info(self, mut info: Self::InfoType) -> InfoOption<Self::InfoType> {
|
||||||
|
match self {
|
||||||
|
MatchOption::Some(option) => info = option.item.info,
|
||||||
|
MatchOption::CannotHaveMbid => info.musicbrainz = MbRefOption::CannotHaveMbid,
|
||||||
|
MatchOption::ManualInputMbid => return InfoOption::NeedInput,
|
||||||
|
}
|
||||||
|
InfoOption::Info(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_match_option_album_into_info!(Match);
|
||||||
|
impl_match_option_album_into_info!(Lookup);
|
||||||
|
|
||||||
|
impl<T> ListOption<T> {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
ListOption::Lookup(list) => list.len(),
|
ListOption::Lookup(list) => list.len(),
|
||||||
@ -18,26 +85,35 @@ impl<T: PartialEq> ListOption<T> {
|
|||||||
|
|
||||||
fn push_cannot_have_mbid(&mut self) {
|
fn push_cannot_have_mbid(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
ListOption::Lookup(list) => list.push(LookupOption::None(MissOption::CannotHaveMbid)),
|
ListOption::Lookup(list) => list.push(MatchOption::CannotHaveMbid),
|
||||||
ListOption::Search(list) => list.push(SearchOption::None(MissOption::CannotHaveMbid)),
|
ListOption::Search(list) => list.push(MatchOption::CannotHaveMbid),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_manual_input_mbid(&mut self) {
|
fn push_manual_input_mbid(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
ListOption::Lookup(list) => list.push(LookupOption::None(MissOption::ManualInputMbid)),
|
ListOption::Lookup(list) => list.push(MatchOption::ManualInputMbid),
|
||||||
ListOption::Search(list) => list.push(SearchOption::None(MissOption::ManualInputMbid)),
|
ListOption::Search(list) => list.push(MatchOption::ManualInputMbid),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_manual_input_mbid(&self, index: usize) -> bool {
|
trait ExtractInfo {
|
||||||
|
type InfoType;
|
||||||
|
fn extract_info(&mut self, index: usize, info: Self::InfoType) -> InfoOption<Self::InfoType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: GetInfoMeta> ExtractInfo for ListOption<T>
|
||||||
|
where
|
||||||
|
MatchOption<Match<T>>: GetInfo<InfoType = T::InfoType>,
|
||||||
|
MatchOption<Lookup<T>>: GetInfo<InfoType = T::InfoType>,
|
||||||
|
{
|
||||||
|
type InfoType = T::InfoType;
|
||||||
|
|
||||||
|
fn extract_info(&mut self, index: usize, info: Self::InfoType) -> InfoOption<Self::InfoType> {
|
||||||
match self {
|
match self {
|
||||||
ListOption::Lookup(list) => {
|
ListOption::Lookup(ref mut list) => list.swap_remove(index).into_info(info),
|
||||||
list.get(index) == Some(&LookupOption::None(MissOption::ManualInputMbid))
|
ListOption::Search(ref mut list) => list.swap_remove(index).into_info(info),
|
||||||
}
|
|
||||||
ListOption::Search(list) => {
|
|
||||||
list.get(index) == Some(&SearchOption::None(MissOption::ManualInputMbid))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,10 +130,6 @@ impl ArtistMatches {
|
|||||||
fn push_manual_input_mbid(&mut self) {
|
fn push_manual_input_mbid(&mut self) {
|
||||||
self.list.push_manual_input_mbid();
|
self.list.push_manual_input_mbid();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_manual_input_mbid(&self, index: usize) -> bool {
|
|
||||||
self.list.is_manual_input_mbid(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlbumMatches {
|
impl AlbumMatches {
|
||||||
@ -72,10 +144,6 @@ impl AlbumMatches {
|
|||||||
fn push_manual_input_mbid(&mut self) {
|
fn push_manual_input_mbid(&mut self) {
|
||||||
self.list.push_manual_input_mbid();
|
self.list.push_manual_input_mbid();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_manual_input_mbid(&self, index: usize) -> bool {
|
|
||||||
self.list.is_manual_input_mbid(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatchStateInfo {
|
impl MatchStateInfo {
|
||||||
@ -99,13 +167,6 @@ impl MatchStateInfo {
|
|||||||
Self::Album(a) => a.push_manual_input_mbid(),
|
Self::Album(a) => a.push_manual_input_mbid(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_manual_input_mbid(&self, index: usize) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Artist(a) => a.is_manual_input_mbid(index),
|
|
||||||
Self::Album(a) => a.is_manual_input_mbid(index),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MatchState {
|
pub struct MatchState {
|
||||||
@ -146,11 +207,23 @@ impl AppMachine<MatchState> {
|
|||||||
AppMachine::app_lookup_artist(self.inner, self.state.fetch, matching, mbid)
|
AppMachine::app_lookup_artist(self.inner, self.state.fetch, matching, mbid)
|
||||||
}
|
}
|
||||||
MatchStateInfo::Album(album_matches) => {
|
MatchStateInfo::Album(album_matches) => {
|
||||||
|
let artist_id = &album_matches.artist;
|
||||||
let matching = &album_matches.matching;
|
let matching = &album_matches.matching;
|
||||||
AppMachine::app_lookup_album(self.inner, self.state.fetch, matching, mbid)
|
AppMachine::app_lookup_album(
|
||||||
|
self.inner,
|
||||||
|
self.state.fetch,
|
||||||
|
artist_id,
|
||||||
|
matching,
|
||||||
|
mbid,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_input(mut self) -> App {
|
||||||
|
self.input.replace(Input::default());
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<MatchState>> for App {
|
impl From<AppMachine<MatchState>> for App {
|
||||||
@ -197,17 +270,32 @@ impl IAppInteractMatch for AppMachine<MatchState> {
|
|||||||
fn select(mut self) -> Self::APP {
|
fn select(mut self) -> Self::APP {
|
||||||
if let Some(index) = self.state.state.list.selected() {
|
if let Some(index) = self.state.state.list.selected() {
|
||||||
// selected() implies current exists
|
// selected() implies current exists
|
||||||
if self
|
|
||||||
.state
|
let mh = &mut self.inner.music_hoard;
|
||||||
.current
|
let result = match self.state.current.as_mut().unwrap() {
|
||||||
.as_ref()
|
MatchStateInfo::Artist(ref mut matches) => {
|
||||||
.unwrap()
|
let info: ArtistInfo = matches.matching.info.clone();
|
||||||
.is_manual_input_mbid(index)
|
match matches.list.extract_info(index, info) {
|
||||||
{
|
InfoOption::Info(info) => mh.set_artist_info(&matches.matching.id, info),
|
||||||
self.input.replace(Input::default());
|
InfoOption::NeedInput => return self.get_input(),
|
||||||
return self.into();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MatchStateInfo::Album(matches) => {
|
||||||
|
let info: AlbumInfo = matches.matching.info.clone();
|
||||||
|
match matches.list.extract_info(index, info) {
|
||||||
|
InfoOption::Info(info) => {
|
||||||
|
mh.set_album_info(&matches.artist, &matches.matching.id, info)
|
||||||
|
}
|
||||||
|
InfoOption::NeedInput => return self.get_input(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = result {
|
||||||
|
return AppMachine::error_state(self.inner, err.to_string()).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AppMachine::app_fetch_next(self.inner, self.state.fetch)
|
AppMachine::app_fetch_next(self.inner, self.state.fetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,9 +308,9 @@ impl IAppInteractMatch for AppMachine<MatchState> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::{collections::VecDeque, sync::mpsc};
|
use std::{collections::VecDeque, sync::mpsc};
|
||||||
|
|
||||||
use mockall::predicate;
|
use mockall::predicate::{self, eq};
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
|
album::{AlbumDate, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
|
||||||
artist::{ArtistId, ArtistMeta},
|
artist::{ArtistId, ArtistMeta},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -258,8 +346,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mbid() -> Mbid {
|
||||||
|
"00000000-0000-0000-0000-000000000000".try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_meta() -> ArtistMeta {
|
||||||
|
let mut meta = ArtistMeta::new(ArtistId::new("Artist"));
|
||||||
|
meta.info.musicbrainz = MbRefOption::Some(mbid().into());
|
||||||
|
meta
|
||||||
|
}
|
||||||
|
|
||||||
fn artist_match() -> MatchStateInfo {
|
fn artist_match() -> MatchStateInfo {
|
||||||
let artist = ArtistMeta::new(ArtistId::new("Artist"));
|
let artist = artist_meta();
|
||||||
|
|
||||||
let artist_1 = artist.clone();
|
let artist_1 = artist.clone();
|
||||||
let artist_match_1 = Match::new(100, artist_1);
|
let artist_match_1 = Match::new(100, artist_1);
|
||||||
@ -273,40 +371,44 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn artist_lookup() -> MatchStateInfo {
|
fn artist_lookup() -> MatchStateInfo {
|
||||||
let artist = ArtistMeta::new(ArtistId::new("Artist"));
|
let artist = artist_meta();
|
||||||
let lookup = Lookup::new(artist.clone());
|
let lookup = Lookup::new(artist.clone());
|
||||||
MatchStateInfo::artist_lookup(artist, lookup)
|
MatchStateInfo::artist_lookup(artist, lookup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_match() -> MatchStateInfo {
|
fn album_meta() -> AlbumMeta {
|
||||||
let album = AlbumMeta::new(
|
AlbumMeta::new(
|
||||||
AlbumId::new("Album"),
|
AlbumId::new("Album"),
|
||||||
AlbumDate::new(Some(1990), Some(5), None),
|
AlbumDate::new(Some(1990), Some(5), None),
|
||||||
|
AlbumInfo::new(
|
||||||
|
MbRefOption::Some(mbid().into()),
|
||||||
Some(AlbumPrimaryType::Album),
|
Some(AlbumPrimaryType::Album),
|
||||||
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
|
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
|
||||||
);
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn album_match() -> MatchStateInfo {
|
||||||
|
let artist_id = ArtistId::new("Artist");
|
||||||
|
let album = album_meta();
|
||||||
|
|
||||||
let album_1 = album.clone();
|
let album_1 = album.clone();
|
||||||
let album_match_1 = Match::new(100, album_1);
|
let album_match_1 = Match::new(100, album_1);
|
||||||
|
|
||||||
let mut album_2 = album.clone();
|
let mut album_2 = album.clone();
|
||||||
album_2.id.title.push_str(" extra title part");
|
album_2.id.title.push_str(" extra title part");
|
||||||
album_2.secondary_types.pop();
|
album_2.info.secondary_types.pop();
|
||||||
let album_match_2 = Match::new(100, album_2);
|
let album_match_2 = Match::new(100, album_2);
|
||||||
|
|
||||||
let list = vec![album_match_1.clone(), album_match_2.clone()];
|
let list = vec![album_match_1.clone(), album_match_2.clone()];
|
||||||
MatchStateInfo::album_search(album, list)
|
MatchStateInfo::album_search(artist_id, album, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_lookup() -> MatchStateInfo {
|
fn album_lookup() -> MatchStateInfo {
|
||||||
let album = AlbumMeta::new(
|
let artist_id = ArtistId::new("Artist");
|
||||||
AlbumId::new("Album"),
|
let album = album_meta();
|
||||||
AlbumDate::new(Some(1990), Some(5), None),
|
|
||||||
Some(AlbumPrimaryType::Album),
|
|
||||||
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
|
|
||||||
);
|
|
||||||
let lookup = Lookup::new(album.clone());
|
let lookup = Lookup::new(album.clone());
|
||||||
MatchStateInfo::album_lookup(album, lookup)
|
MatchStateInfo::album_lookup(artist_id, album, lookup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_state() -> FetchState {
|
fn fetch_state() -> FetchState {
|
||||||
@ -361,11 +463,34 @@ mod tests {
|
|||||||
|
|
||||||
fn match_state_flow(mut matches_info: MatchStateInfo, len: usize) {
|
fn match_state_flow(mut matches_info: MatchStateInfo, len: usize) {
|
||||||
// tx must exist for rx to return Empty rather than Disconnected.
|
// tx must exist for rx to return Empty rather than Disconnected.
|
||||||
#[allow(unused_variables)]
|
let (_tx, rx) = mpsc::channel();
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx));
|
let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx));
|
||||||
|
|
||||||
let matches = AppMachine::match_state(inner(music_hoard(vec![])), app_matches);
|
let mut music_hoard = music_hoard(vec![]);
|
||||||
|
let artist_id = ArtistId::new("Artist");
|
||||||
|
match matches_info {
|
||||||
|
MatchStateInfo::Album(_) => {
|
||||||
|
let album_id = AlbumId::new("Album");
|
||||||
|
let mut info = album_meta().info;
|
||||||
|
info.musicbrainz = MbRefOption::CannotHaveMbid;
|
||||||
|
music_hoard
|
||||||
|
.expect_set_album_info()
|
||||||
|
.with(eq(artist_id.clone()), eq(album_id.clone()), eq(info))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_, _, _| Ok(()));
|
||||||
|
}
|
||||||
|
MatchStateInfo::Artist(_) => {
|
||||||
|
let mut info = artist_meta().info;
|
||||||
|
info.musicbrainz = MbRefOption::CannotHaveMbid;
|
||||||
|
music_hoard
|
||||||
|
.expect_set_artist_info()
|
||||||
|
.with(eq(artist_id.clone()), eq(info))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_, _| Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let matches = AppMachine::match_state(inner(music_hoard), app_matches);
|
||||||
matches_info.push_cannot_have_mbid();
|
matches_info.push_cannot_have_mbid();
|
||||||
matches_info.push_manual_input_mbid();
|
matches_info.push_manual_input_mbid();
|
||||||
|
|
||||||
@ -430,6 +555,75 @@ mod tests {
|
|||||||
match_state_flow(album_lookup(), 1);
|
match_state_flow(album_lookup(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_artist_info() {
|
||||||
|
let matches_info = artist_match();
|
||||||
|
|
||||||
|
let (_tx, rx) = mpsc::channel();
|
||||||
|
let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx));
|
||||||
|
|
||||||
|
let mut music_hoard = music_hoard(vec![]);
|
||||||
|
match matches_info {
|
||||||
|
MatchStateInfo::Album(_) => panic!(),
|
||||||
|
MatchStateInfo::Artist(_) => {
|
||||||
|
let meta = artist_meta();
|
||||||
|
music_hoard
|
||||||
|
.expect_set_artist_info()
|
||||||
|
.with(eq(meta.id), eq(meta.info))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_, _| Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let matches = AppMachine::match_state(inner(music_hoard), app_matches);
|
||||||
|
matches.select().unwrap_fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_album_info() {
|
||||||
|
let matches_info = album_match();
|
||||||
|
|
||||||
|
let (_tx, rx) = mpsc::channel();
|
||||||
|
let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx));
|
||||||
|
|
||||||
|
let mut music_hoard = music_hoard(vec![]);
|
||||||
|
match matches_info {
|
||||||
|
MatchStateInfo::Artist(_) => panic!(),
|
||||||
|
MatchStateInfo::Album(matches) => {
|
||||||
|
let meta = album_meta();
|
||||||
|
music_hoard
|
||||||
|
.expect_set_album_info()
|
||||||
|
.with(eq(matches.artist), eq(meta.id), eq(meta.info))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_, _, _| Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let matches = AppMachine::match_state(inner(music_hoard), app_matches);
|
||||||
|
matches.select().unwrap_fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_info_error() {
|
||||||
|
let matches_info = artist_match();
|
||||||
|
|
||||||
|
let (_tx, rx) = mpsc::channel();
|
||||||
|
let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx));
|
||||||
|
|
||||||
|
let mut music_hoard = music_hoard(vec![]);
|
||||||
|
match matches_info {
|
||||||
|
MatchStateInfo::Album(_) => panic!(),
|
||||||
|
MatchStateInfo::Artist(_) => {
|
||||||
|
music_hoard.expect_set_artist_info().return_once(|_, _| {
|
||||||
|
Err(musichoard::Error::DatabaseError(String::from("error")))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let matches = AppMachine::match_state(inner(music_hoard), app_matches);
|
||||||
|
matches.select().unwrap_error();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn abort() {
|
fn abort() {
|
||||||
let mut album_match = album_match();
|
let mut album_match = album_match();
|
||||||
@ -451,10 +645,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_empty() {
|
fn select_empty() {
|
||||||
// Note that what really matters in this test is actually that the transmit channel has
|
// This test will become obsolete with #203 so it just needs to work well enough for
|
||||||
// disconnected and so the receive within FetchState concludes there are no more matches.
|
// coverage. We expect the error state, because after selecting, fetch will be invoked, but
|
||||||
|
// with an empty collection, an error will be raised.
|
||||||
let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None));
|
let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None));
|
||||||
matches.select().unwrap_browse();
|
matches.select().unwrap_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -474,10 +669,6 @@ mod tests {
|
|||||||
input.confirm().unwrap_error();
|
input.confirm().unwrap_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mbid() -> Mbid {
|
|
||||||
"00000000-0000-0000-0000-000000000000".try_into().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn input_mbid(mut app: App) -> App {
|
fn input_mbid(mut app: App) -> App {
|
||||||
let mbid = mbid().uuid().to_string();
|
let mbid = mbid().uuid().to_string();
|
||||||
for c in mbid.chars() {
|
for c in mbid.chars() {
|
||||||
@ -518,15 +709,21 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn select_manual_input_album() {
|
fn select_manual_input_album() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
let album = AlbumMeta::new("Album", 1990, None, vec![]);
|
let artist_id = ArtistId::new("Artist");
|
||||||
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid())]);
|
let album = AlbumMeta::new("Album", 1990, AlbumInfo::default());
|
||||||
|
let requests = VecDeque::from([MbParams::lookup_release_group(
|
||||||
|
artist_id.clone(),
|
||||||
|
album.clone(),
|
||||||
|
mbid(),
|
||||||
|
)]);
|
||||||
mb_job_sender
|
mb_job_sender
|
||||||
.expect_submit_foreground_job()
|
.expect_submit_foreground_job()
|
||||||
.with(predicate::always(), predicate::eq(requests))
|
.with(predicate::always(), predicate::eq(requests))
|
||||||
.return_once(|_, _| Ok(()));
|
.return_once(|_, _| Ok(()));
|
||||||
|
|
||||||
let matches_vec: Vec<Match<AlbumMeta>> = vec![];
|
let matches_vec: Vec<Match<AlbumMeta>> = vec![];
|
||||||
let album_match = MatchStateInfo::album_search(album.clone(), matches_vec);
|
let album_match =
|
||||||
|
MatchStateInfo::album_search(artist_id.clone(), album.clone(), matches_vec);
|
||||||
let matches = AppMachine::match_state(
|
let matches = AppMachine::match_state(
|
||||||
inner_with_mb(music_hoard(vec![]), mb_job_sender),
|
inner_with_mb(music_hoard(vec![]), mb_job_sender),
|
||||||
match_state(Some(album_match)),
|
match_state(Some(album_match)),
|
||||||
|
@ -182,7 +182,7 @@ impl IAppInteractSearchPrivate for AppMachine<SearchState> {
|
|||||||
|
|
||||||
if let Some(ref probe_sort) = probe.meta.sort {
|
if let Some(ref probe_sort) = probe.meta.sort {
|
||||||
if !result {
|
if !result {
|
||||||
let name = Self::normalize_search(&probe_sort.name, !case_sens, !char_sens);
|
let name = Self::normalize_search(probe_sort, !case_sens, !char_sens);
|
||||||
result = name.starts_with(search);
|
result = name.starts_with(search);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,11 @@ mod selection;
|
|||||||
pub use machine::App;
|
pub use machine::App;
|
||||||
pub use selection::{Category, Delta, Selection, WidgetState};
|
pub use selection::{Category, Delta, Selection, WidgetState};
|
||||||
|
|
||||||
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, Collection};
|
use musichoard::collection::{
|
||||||
|
album::AlbumMeta,
|
||||||
|
artist::{ArtistId, ArtistMeta},
|
||||||
|
Collection,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::tui::lib::interface::musicbrainz::api::Match;
|
use crate::tui::lib::interface::musicbrainz::api::Match;
|
||||||
|
|
||||||
@ -177,38 +181,21 @@ pub struct AppPublicInner<'app> {
|
|||||||
pub type InputPublic<'app> = &'app tui_input::Input;
|
pub type InputPublic<'app> = &'app tui_input::Input;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MissOption {
|
pub enum MatchOption<T> {
|
||||||
|
Some(T),
|
||||||
CannotHaveMbid,
|
CannotHaveMbid,
|
||||||
ManualInputMbid,
|
ManualInputMbid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum SearchOption<T> {
|
|
||||||
Match(Match<T>),
|
|
||||||
None(MissOption),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum LookupOption<T> {
|
|
||||||
Match(Lookup<T>),
|
|
||||||
None(MissOption),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ListOption<T> {
|
pub enum ListOption<T> {
|
||||||
Search(Vec<SearchOption<T>>),
|
Search(Vec<MatchOption<Match<T>>>),
|
||||||
Lookup(Vec<LookupOption<T>>),
|
Lookup(Vec<MatchOption<Lookup<T>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<Match<T>> for SearchOption<T> {
|
impl<T> From<T> for MatchOption<T> {
|
||||||
fn from(value: Match<T>) -> Self {
|
fn from(value: T) -> Self {
|
||||||
SearchOption::Match(value)
|
MatchOption::Some(value)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<Lookup<T>> for LookupOption<T> {
|
|
||||||
fn from(value: Lookup<T>) -> Self {
|
|
||||||
LookupOption::Match(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +207,7 @@ pub struct ArtistMatches {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AlbumMatches {
|
pub struct AlbumMatches {
|
||||||
|
pub artist: ArtistId,
|
||||||
pub matching: AlbumMeta,
|
pub matching: AlbumMeta,
|
||||||
pub list: ListOption<AlbumMeta>,
|
pub list: ListOption<AlbumMeta>,
|
||||||
}
|
}
|
||||||
@ -231,7 +219,7 @@ pub enum MatchStateInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MatchStateInfo {
|
impl MatchStateInfo {
|
||||||
pub fn artist_search<M: Into<SearchOption<ArtistMeta>>>(
|
pub fn artist_search<M: Into<MatchOption<Match<ArtistMeta>>>>(
|
||||||
matching: ArtistMeta,
|
matching: ArtistMeta,
|
||||||
list: Vec<M>,
|
list: Vec<M>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -239,22 +227,38 @@ impl MatchStateInfo {
|
|||||||
MatchStateInfo::Artist(ArtistMatches { matching, list })
|
MatchStateInfo::Artist(ArtistMatches { matching, list })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn album_search<M: Into<SearchOption<AlbumMeta>>>(
|
pub fn album_search<M: Into<MatchOption<Match<AlbumMeta>>>>(
|
||||||
|
artist: ArtistId,
|
||||||
matching: AlbumMeta,
|
matching: AlbumMeta,
|
||||||
list: Vec<M>,
|
list: Vec<M>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let list = ListOption::Search(list.into_iter().map(Into::into).collect());
|
let list = ListOption::Search(list.into_iter().map(Into::into).collect());
|
||||||
MatchStateInfo::Album(AlbumMatches { matching, list })
|
MatchStateInfo::Album(AlbumMatches {
|
||||||
|
artist,
|
||||||
|
matching,
|
||||||
|
list,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn artist_lookup<M: Into<LookupOption<ArtistMeta>>>(matching: ArtistMeta, item: M) -> Self {
|
pub fn artist_lookup<M: Into<MatchOption<Lookup<ArtistMeta>>>>(
|
||||||
|
matching: ArtistMeta,
|
||||||
|
item: M,
|
||||||
|
) -> Self {
|
||||||
let list = ListOption::Lookup(vec![item.into()]);
|
let list = ListOption::Lookup(vec![item.into()]);
|
||||||
MatchStateInfo::Artist(ArtistMatches { matching, list })
|
MatchStateInfo::Artist(ArtistMatches { matching, list })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn album_lookup<M: Into<LookupOption<AlbumMeta>>>(matching: AlbumMeta, item: M) -> Self {
|
pub fn album_lookup<M: Into<MatchOption<Lookup<AlbumMeta>>>>(
|
||||||
|
artist: ArtistId,
|
||||||
|
matching: AlbumMeta,
|
||||||
|
item: M,
|
||||||
|
) -> Self {
|
||||||
let list = ListOption::Lookup(vec![item.into()]);
|
let list = ListOption::Lookup(vec![item.into()]);
|
||||||
MatchStateInfo::Album(AlbumMatches { matching, list })
|
MatchStateInfo::Album(AlbumMatches {
|
||||||
|
artist,
|
||||||
|
matching,
|
||||||
|
list,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,14 +205,14 @@ impl KeySelectArtist {
|
|||||||
let artist = &artists[index];
|
let artist = &artists[index];
|
||||||
let key = artist.meta.get_sort_key();
|
let key = artist.meta.get_sort_key();
|
||||||
KeySelectArtist {
|
KeySelectArtist {
|
||||||
key: (key.0.to_owned(),),
|
key: (key.0.into(),),
|
||||||
album: KeySelectAlbum::get(&artist.albums, &selection.album),
|
album: KeySelectAlbum::get(&artist.albums, &selection.album),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sort_key(&self) -> (&ArtistId,) {
|
pub fn get_sort_key(&self) -> (&str,) {
|
||||||
(&self.key.0,)
|
(&self.key.0.name,)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
src/tui/lib/external/musicbrainz/api/mod.rs
vendored
28
src/tui/lib/external/musicbrainz/api/mod.rs
vendored
@ -4,8 +4,8 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{AlbumDate, AlbumMeta, AlbumSeq},
|
album::{AlbumDate, AlbumInfo, AlbumMeta, AlbumSeq},
|
||||||
artist::{ArtistId, ArtistMeta},
|
artist::{ArtistInfo, ArtistMeta},
|
||||||
musicbrainz::{MbRefOption, Mbid},
|
musicbrainz::{MbRefOption, Mbid},
|
||||||
},
|
},
|
||||||
external::musicbrainz::{
|
external::musicbrainz::{
|
||||||
@ -93,16 +93,16 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn from_lookup_artist_response(entity: LookupArtistResponse) -> Lookup<ArtistMeta> {
|
fn from_lookup_artist_response(entity: LookupArtistResponse) -> Lookup<ArtistMeta> {
|
||||||
let sort: Option<ArtistId> = Some(entity.meta.sort_name)
|
let sort = Some(entity.meta.sort_name).filter(|s| s != &entity.meta.name);
|
||||||
.filter(|s| s != &entity.meta.name)
|
|
||||||
.map(Into::into);
|
|
||||||
Lookup {
|
Lookup {
|
||||||
item: ArtistMeta {
|
item: ArtistMeta {
|
||||||
id: entity.meta.name,
|
id: entity.meta.name.into(),
|
||||||
sort,
|
sort,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
|
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
disambiguation: entity.meta.disambiguation,
|
disambiguation: entity.meta.disambiguation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,29 +110,31 @@ fn from_lookup_artist_response(entity: LookupArtistResponse) -> Lookup<ArtistMet
|
|||||||
fn from_lookup_release_group_response(entity: LookupReleaseGroupResponse) -> Lookup<AlbumMeta> {
|
fn from_lookup_release_group_response(entity: LookupReleaseGroupResponse) -> Lookup<AlbumMeta> {
|
||||||
Lookup {
|
Lookup {
|
||||||
item: AlbumMeta {
|
item: AlbumMeta {
|
||||||
id: entity.meta.title,
|
id: entity.meta.title.into(),
|
||||||
date: entity.meta.first_release_date,
|
date: entity.meta.first_release_date,
|
||||||
seq: AlbumSeq::default(),
|
seq: AlbumSeq::default(),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
|
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
|
||||||
primary_type: Some(entity.meta.primary_type),
|
primary_type: Some(entity.meta.primary_type),
|
||||||
secondary_types: entity.meta.secondary_types.unwrap_or_default(),
|
secondary_types: entity.meta.secondary_types.unwrap_or_default(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
disambiguation: None,
|
disambiguation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<ArtistMeta> {
|
fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<ArtistMeta> {
|
||||||
let sort: Option<ArtistId> = Some(entity.meta.sort_name)
|
let sort = Some(entity.meta.sort_name).filter(|s| s != &entity.meta.name);
|
||||||
.filter(|s| s != &entity.meta.name)
|
|
||||||
.map(Into::into);
|
|
||||||
Match {
|
Match {
|
||||||
score: entity.score,
|
score: entity.score,
|
||||||
item: ArtistMeta {
|
item: ArtistMeta {
|
||||||
id: entity.meta.name,
|
id: entity.meta.name.into(),
|
||||||
sort,
|
sort,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
|
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
disambiguation: entity.meta.disambiguation,
|
disambiguation: entity.meta.disambiguation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,13 +145,15 @@ fn from_search_release_group_response_release_group(
|
|||||||
Match {
|
Match {
|
||||||
score: entity.score,
|
score: entity.score,
|
||||||
item: AlbumMeta {
|
item: AlbumMeta {
|
||||||
id: entity.meta.title,
|
id: entity.meta.title.into(),
|
||||||
date: entity.meta.first_release_date,
|
date: entity.meta.first_release_date,
|
||||||
seq: AlbumSeq::default(),
|
seq: AlbumSeq::default(),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
|
musicbrainz: MbRefOption::Some(entity.meta.id.into()),
|
||||||
primary_type: Some(entity.meta.primary_type),
|
primary_type: Some(entity.meta.primary_type),
|
||||||
secondary_types: entity.meta.secondary_types.unwrap_or_default(),
|
secondary_types: entity.meta.secondary_types.unwrap_or_default(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
disambiguation: None,
|
disambiguation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
52
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
@ -245,15 +245,15 @@ impl JobInstance {
|
|||||||
.map(|rv| MatchStateInfo::artist_lookup(params.artist, rv)),
|
.map(|rv| MatchStateInfo::artist_lookup(params.artist, rv)),
|
||||||
LookupParams::ReleaseGroup(params) => musicbrainz
|
LookupParams::ReleaseGroup(params) => musicbrainz
|
||||||
.lookup_release_group(¶ms.mbid)
|
.lookup_release_group(¶ms.mbid)
|
||||||
.map(|rv| MatchStateInfo::album_lookup(params.album, rv)),
|
.map(|rv| MatchStateInfo::album_lookup(params.artist_id, params.album, rv)),
|
||||||
},
|
},
|
||||||
MbParams::Search(search) => match search {
|
MbParams::Search(search) => match search {
|
||||||
SearchParams::Artist(params) => musicbrainz
|
SearchParams::Artist(params) => musicbrainz
|
||||||
.search_artist(¶ms.artist)
|
.search_artist(¶ms.artist)
|
||||||
.map(|rv| MatchStateInfo::artist_search(params.artist, rv)),
|
.map(|rv| MatchStateInfo::artist_search(params.artist, rv)),
|
||||||
SearchParams::ReleaseGroup(params) => musicbrainz
|
SearchParams::ReleaseGroup(params) => musicbrainz
|
||||||
.search_release_group(¶ms.arid, ¶ms.album)
|
.search_release_group(¶ms.artist_mbid, ¶ms.album)
|
||||||
.map(|rv| MatchStateInfo::album_search(params.album, rv)),
|
.map(|rv| MatchStateInfo::album_search(params.artist_id, params.album, rv)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
self.return_result(event_sender, result)
|
self.return_result(event_sender, result)
|
||||||
@ -315,7 +315,7 @@ mod tests {
|
|||||||
use mockall::{predicate, Sequence};
|
use mockall::{predicate, Sequence};
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::AlbumMeta,
|
album::AlbumMeta,
|
||||||
artist::ArtistMeta,
|
artist::{ArtistId, ArtistMeta},
|
||||||
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
|
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -397,9 +397,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_release_group_requests() -> VecDeque<MbParams> {
|
fn lookup_release_group_requests() -> VecDeque<MbParams> {
|
||||||
|
let artist_id = COLLECTION[1].meta.id.clone();
|
||||||
let album = COLLECTION[1].albums[0].meta.clone();
|
let album = COLLECTION[1].albums[0].meta.clone();
|
||||||
let mbid = mbid();
|
let mbid = mbid();
|
||||||
VecDeque::from([MbParams::lookup_release_group(album, mbid)])
|
VecDeque::from([MbParams::lookup_release_group(artist_id, album, mbid)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist_requests() -> VecDeque<MbParams> {
|
fn search_artist_requests() -> VecDeque<MbParams> {
|
||||||
@ -418,20 +419,25 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_albums_requests() -> VecDeque<MbParams> {
|
fn search_albums_requests() -> VecDeque<MbParams> {
|
||||||
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.musicbrainz);
|
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.info.musicbrainz);
|
||||||
let arid = mb_ref_opt_unwrap(mbref).mbid().clone();
|
let arid = mb_ref_opt_unwrap(mbref).mbid().clone();
|
||||||
|
|
||||||
|
let artist_id = COLLECTION[1].meta.id.clone();
|
||||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||||
|
|
||||||
VecDeque::from([
|
VecDeque::from([
|
||||||
MbParams::search_release_group(arid.clone(), album_1),
|
MbParams::search_release_group(artist_id.clone(), arid.clone(), album_1),
|
||||||
MbParams::search_release_group(arid.clone(), album_4),
|
MbParams::search_release_group(artist_id.clone(), arid.clone(), album_4),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn album_artist_id() -> ArtistId {
|
||||||
|
COLLECTION[1].meta.id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn album_arid_expectation() -> Mbid {
|
fn album_arid_expectation() -> Mbid {
|
||||||
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.musicbrainz);
|
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.info.musicbrainz);
|
||||||
mb_ref_opt_unwrap(mbref).mbid().clone()
|
mb_ref_opt_unwrap(mbref).mbid().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,7 +617,11 @@ mod tests {
|
|||||||
assert_eq!(result, Ok(()));
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
let result = result_receiver.try_recv().unwrap();
|
let result = result_receiver.try_recv().unwrap();
|
||||||
assert_eq!(result, Ok(MatchStateInfo::album_lookup(album, lookup)));
|
let artist_id = album_artist_id();
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Ok(MatchStateInfo::album_lookup(artist_id, album, lookup))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist_expectation(
|
fn search_artist_expectation(
|
||||||
@ -701,11 +711,27 @@ mod tests {
|
|||||||
let result = daemon.execute_next_job();
|
let result = daemon.execute_next_job();
|
||||||
assert_eq!(result, Ok(()));
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
let result = result_receiver.try_recv().unwrap();
|
let artist_id = album_artist_id();
|
||||||
assert_eq!(result, Ok(MatchStateInfo::album_search(album_1, matches_1)));
|
|
||||||
|
|
||||||
let result = result_receiver.try_recv().unwrap();
|
let result = result_receiver.try_recv().unwrap();
|
||||||
assert_eq!(result, Ok(MatchStateInfo::album_search(album_4, matches_4)));
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Ok(MatchStateInfo::album_search(
|
||||||
|
artist_id.clone(),
|
||||||
|
album_1,
|
||||||
|
matches_1
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = result_receiver.try_recv().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Ok(MatchStateInfo::album_search(
|
||||||
|
artist_id.clone(),
|
||||||
|
album_4,
|
||||||
|
matches_4
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
use std::{collections::VecDeque, fmt, sync::mpsc};
|
use std::{collections::VecDeque, fmt, sync::mpsc};
|
||||||
|
|
||||||
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid};
|
use musichoard::collection::{
|
||||||
|
album::AlbumMeta,
|
||||||
|
artist::{ArtistId, ArtistMeta},
|
||||||
|
musicbrainz::Mbid,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as MbApiError};
|
use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as MbApiError};
|
||||||
|
|
||||||
@ -60,6 +64,7 @@ pub struct LookupArtistParams {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct LookupReleaseGroupParams {
|
pub struct LookupReleaseGroupParams {
|
||||||
|
pub artist_id: ArtistId,
|
||||||
pub album: AlbumMeta,
|
pub album: AlbumMeta,
|
||||||
pub mbid: Mbid,
|
pub mbid: Mbid,
|
||||||
}
|
}
|
||||||
@ -77,7 +82,8 @@ pub struct SearchArtistParams {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SearchReleaseGroupParams {
|
pub struct SearchReleaseGroupParams {
|
||||||
pub arid: Mbid,
|
pub artist_id: ArtistId,
|
||||||
|
pub artist_mbid: Mbid,
|
||||||
pub album: AlbumMeta,
|
pub album: AlbumMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +92,9 @@ impl MbParams {
|
|||||||
MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid }))
|
MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_release_group(album: AlbumMeta, mbid: Mbid) -> Self {
|
pub fn lookup_release_group(artist_id: ArtistId, album: AlbumMeta, mbid: Mbid) -> Self {
|
||||||
MbParams::Lookup(LookupParams::ReleaseGroup(LookupReleaseGroupParams {
|
MbParams::Lookup(LookupParams::ReleaseGroup(LookupReleaseGroupParams {
|
||||||
|
artist_id,
|
||||||
album,
|
album,
|
||||||
mbid,
|
mbid,
|
||||||
}))
|
}))
|
||||||
@ -97,9 +104,10 @@ impl MbParams {
|
|||||||
MbParams::Search(SearchParams::Artist(SearchArtistParams { artist }))
|
MbParams::Search(SearchParams::Artist(SearchArtistParams { artist }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_release_group(arid: Mbid, album: AlbumMeta) -> Self {
|
pub fn search_release_group(artist_id: ArtistId, artist_mbid: Mbid, album: AlbumMeta) -> Self {
|
||||||
MbParams::Search(SearchParams::ReleaseGroup(SearchReleaseGroupParams {
|
MbParams::Search(SearchParams::ReleaseGroup(SearchReleaseGroupParams {
|
||||||
arid,
|
artist_id,
|
||||||
|
artist_mbid,
|
||||||
album,
|
album,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,11 @@ pub mod external;
|
|||||||
pub mod interface;
|
pub mod interface;
|
||||||
|
|
||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::Collection,
|
collection::{
|
||||||
|
album::{AlbumId, AlbumInfo},
|
||||||
|
artist::{ArtistId, ArtistInfo},
|
||||||
|
Collection,
|
||||||
|
},
|
||||||
interface::{database::IDatabase, library::ILibrary},
|
interface::{database::IDatabase, library::ILibrary},
|
||||||
IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, MusicHoard,
|
IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, MusicHoard,
|
||||||
};
|
};
|
||||||
@ -15,6 +19,15 @@ pub trait IMusicHoard {
|
|||||||
fn rescan_library(&mut self) -> Result<(), musichoard::Error>;
|
fn rescan_library(&mut self) -> Result<(), musichoard::Error>;
|
||||||
fn reload_database(&mut self) -> Result<(), musichoard::Error>;
|
fn reload_database(&mut self) -> Result<(), musichoard::Error>;
|
||||||
fn get_collection(&self) -> &Collection;
|
fn get_collection(&self) -> &Collection;
|
||||||
|
|
||||||
|
fn set_artist_info(&mut self, id: &ArtistId, info: ArtistInfo)
|
||||||
|
-> Result<(), musichoard::Error>;
|
||||||
|
fn set_album_info(
|
||||||
|
&mut self,
|
||||||
|
artist_id: &ArtistId,
|
||||||
|
album_id: &AlbumId,
|
||||||
|
info: AlbumInfo,
|
||||||
|
) -> Result<(), musichoard::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GRCOV_EXCL_START
|
// GRCOV_EXCL_START
|
||||||
@ -30,5 +43,22 @@ impl<Database: IDatabase, Library: ILibrary> IMusicHoard for MusicHoard<Database
|
|||||||
fn get_collection(&self) -> &Collection {
|
fn get_collection(&self) -> &Collection {
|
||||||
<Self as IMusicHoardBase>::get_collection(self)
|
<Self as IMusicHoardBase>::get_collection(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_artist_info(
|
||||||
|
&mut self,
|
||||||
|
id: &ArtistId,
|
||||||
|
info: ArtistInfo,
|
||||||
|
) -> Result<(), musichoard::Error> {
|
||||||
|
<Self as IMusicHoardDatabase>::set_artist_info(self, id, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_album_info(
|
||||||
|
&mut self,
|
||||||
|
artist_id: &ArtistId,
|
||||||
|
album_id: &AlbumId,
|
||||||
|
info: AlbumInfo,
|
||||||
|
) -> Result<(), musichoard::Error> {
|
||||||
|
<Self as IMusicHoardDatabase>::set_album_info(self, artist_id, album_id, info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// GRCOV_EXCL_STOP
|
// GRCOV_EXCL_STOP
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{Album, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSeq},
|
album::{Album, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSeq},
|
||||||
artist::{Artist, ArtistId, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistInfo, ArtistMeta},
|
||||||
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption},
|
musicbrainz::{MbAlbumRef, MbArtistRef, MbRefOption},
|
||||||
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
||||||
};
|
};
|
||||||
|
@ -169,7 +169,10 @@ impl<'a, 'b> AlbumState<'a, 'b> {
|
|||||||
.map(|a| UiDisplay::display_date(&a.meta.date, &a.meta.seq))
|
.map(|a| UiDisplay::display_date(&a.meta.date, &a.meta.seq))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
album
|
album
|
||||||
.map(|a| UiDisplay::display_type(&a.meta.primary_type, &a.meta.secondary_types))
|
.map(|a| UiDisplay::display_type(
|
||||||
|
&a.meta.info.primary_type,
|
||||||
|
&a.meta.info.secondary_types
|
||||||
|
))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
album
|
album
|
||||||
.map(|a| UiDisplay::display_album_status(&a.get_status()))
|
.map(|a| UiDisplay::display_album_status(&a.get_status()))
|
||||||
|
@ -5,7 +5,10 @@ use musichoard::collection::{
|
|||||||
track::{TrackFormat, TrackQuality},
|
track::{TrackFormat, TrackQuality},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::app::{LookupOption, MatchStateInfo, MissOption, SearchOption};
|
use crate::tui::{
|
||||||
|
app::{MatchOption, MatchStateInfo},
|
||||||
|
lib::interface::musicbrainz::api::{Lookup, Match},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct UiDisplay;
|
pub struct UiDisplay;
|
||||||
|
|
||||||
@ -133,23 +136,25 @@ impl UiDisplay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_search_option_artist(match_option: &SearchOption<ArtistMeta>) -> String {
|
pub fn display_search_option_artist(match_option: &MatchOption<Match<ArtistMeta>>) -> String {
|
||||||
match match_option {
|
match match_option {
|
||||||
SearchOption::Match(match_artist) => format!(
|
MatchOption::Some(match_artist) => format!(
|
||||||
"{} ({}%)",
|
"{} ({}%)",
|
||||||
Self::display_option_artist(&match_artist.item, &match_artist.disambiguation),
|
Self::display_option_artist(&match_artist.item, &match_artist.disambiguation),
|
||||||
match_artist.score,
|
match_artist.score,
|
||||||
),
|
),
|
||||||
SearchOption::None(miss) => Self::display_miss_option(miss).to_string(),
|
MatchOption::CannotHaveMbid => Self::display_cannot_have_mbid().to_string(),
|
||||||
|
MatchOption::ManualInputMbid => Self::display_manual_input_mbid().to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_lookup_option_artist(lookup_option: &LookupOption<ArtistMeta>) -> String {
|
pub fn display_lookup_option_artist(lookup_option: &MatchOption<Lookup<ArtistMeta>>) -> String {
|
||||||
match lookup_option {
|
match lookup_option {
|
||||||
LookupOption::Match(match_artist) => {
|
MatchOption::Some(match_artist) => {
|
||||||
Self::display_option_artist(&match_artist.item, &match_artist.disambiguation)
|
Self::display_option_artist(&match_artist.item, &match_artist.disambiguation)
|
||||||
}
|
}
|
||||||
LookupOption::None(miss) => Self::display_miss_option(miss).to_string(),
|
MatchOption::CannotHaveMbid => Self::display_cannot_have_mbid().to_string(),
|
||||||
|
MatchOption::ManualInputMbid => Self::display_manual_input_mbid().to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,21 +170,23 @@ impl UiDisplay {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_search_option_album(match_option: &SearchOption<AlbumMeta>) -> String {
|
pub fn display_search_option_album(match_option: &MatchOption<Match<AlbumMeta>>) -> String {
|
||||||
match match_option {
|
match match_option {
|
||||||
SearchOption::Match(match_album) => format!(
|
MatchOption::Some(match_album) => format!(
|
||||||
"{} ({}%)",
|
"{} ({}%)",
|
||||||
Self::display_option_album(&match_album.item),
|
Self::display_option_album(&match_album.item),
|
||||||
match_album.score,
|
match_album.score,
|
||||||
),
|
),
|
||||||
SearchOption::None(miss) => Self::display_miss_option(miss).to_string(),
|
MatchOption::CannotHaveMbid => Self::display_cannot_have_mbid().to_string(),
|
||||||
|
MatchOption::ManualInputMbid => Self::display_manual_input_mbid().to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_lookup_option_album(lookup_option: &LookupOption<AlbumMeta>) -> String {
|
pub fn display_lookup_option_album(lookup_option: &MatchOption<Lookup<AlbumMeta>>) -> String {
|
||||||
match lookup_option {
|
match lookup_option {
|
||||||
LookupOption::Match(match_album) => Self::display_option_album(&match_album.item),
|
MatchOption::Some(match_album) => Self::display_option_album(&match_album.item),
|
||||||
LookupOption::None(miss) => Self::display_miss_option(miss).to_string(),
|
MatchOption::CannotHaveMbid => Self::display_cannot_have_mbid().to_string(),
|
||||||
|
MatchOption::ManualInputMbid => Self::display_manual_input_mbid().to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,17 +195,10 @@ impl UiDisplay {
|
|||||||
"{:010} | {} [{}]",
|
"{:010} | {} [{}]",
|
||||||
UiDisplay::display_album_date(&album.date),
|
UiDisplay::display_album_date(&album.date),
|
||||||
album.id.title,
|
album.id.title,
|
||||||
UiDisplay::display_type(&album.primary_type, &album.secondary_types),
|
UiDisplay::display_type(&album.info.primary_type, &album.info.secondary_types),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_miss_option(miss_option: &MissOption) -> &'static str {
|
|
||||||
match miss_option {
|
|
||||||
MissOption::CannotHaveMbid => Self::display_cannot_have_mbid(),
|
|
||||||
MissOption::ManualInputMbid => Self::display_manual_input_mbid(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_cannot_have_mbid() -> &'static str {
|
fn display_cannot_have_mbid() -> &'static str {
|
||||||
"-- Cannot have a MusicBrainz Identifier --"
|
"-- Cannot have a MusicBrainz Identifier --"
|
||||||
}
|
}
|
||||||
|
@ -76,10 +76,10 @@ impl<'a> ArtistOverlay<'a> {
|
|||||||
Properties: {}",
|
Properties: {}",
|
||||||
artist.map(|a| a.meta.id.name.as_str()).unwrap_or(""),
|
artist.map(|a| a.meta.id.name.as_str()).unwrap_or(""),
|
||||||
artist
|
artist
|
||||||
.map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.musicbrainz))
|
.map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.info.musicbrainz))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
Self::opt_hashmap_to_string(
|
Self::opt_hashmap_to_string(
|
||||||
artist.map(|a| &a.meta.properties),
|
artist.map(|a| &a.meta.info.properties),
|
||||||
&double_item_indent,
|
&double_item_indent,
|
||||||
&double_list_indent
|
&double_list_indent
|
||||||
),
|
),
|
||||||
@ -104,7 +104,7 @@ impl<'a> AlbumOverlay<'a> {
|
|||||||
MusicBrainz: {}",
|
MusicBrainz: {}",
|
||||||
album.map(|a| a.meta.id.title.as_str()).unwrap_or(""),
|
album.map(|a| a.meta.id.title.as_str()).unwrap_or(""),
|
||||||
album
|
album
|
||||||
.map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.musicbrainz))
|
.map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.info.musicbrainz))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -201,8 +201,9 @@ impl IUi for Ui {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
|
album::{AlbumDate, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
|
||||||
artist::{Artist, ArtistId, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistMeta},
|
||||||
|
musicbrainz::MbRefOption,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
@ -373,31 +374,40 @@ mod tests {
|
|||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn album_artist_id() -> ArtistId {
|
||||||
|
ArtistId::new("Artist")
|
||||||
|
}
|
||||||
|
|
||||||
fn album_meta() -> AlbumMeta {
|
fn album_meta() -> AlbumMeta {
|
||||||
AlbumMeta::new(
|
AlbumMeta::new(
|
||||||
AlbumId::new("An Album"),
|
AlbumId::new("An Album"),
|
||||||
AlbumDate::new(Some(1990), Some(5), None),
|
AlbumDate::new(Some(1990), Some(5), None),
|
||||||
|
AlbumInfo::new(
|
||||||
|
MbRefOption::None,
|
||||||
Some(AlbumPrimaryType::Album),
|
Some(AlbumPrimaryType::Album),
|
||||||
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
|
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_matches() -> MatchStateInfo {
|
fn album_matches() -> MatchStateInfo {
|
||||||
|
let artist_id = album_artist_id();
|
||||||
let album = album_meta();
|
let album = album_meta();
|
||||||
let album_match = Match::new(80, album.clone());
|
let album_match = Match::new(80, album.clone());
|
||||||
let list = vec![album_match.clone(), album_match.clone()];
|
let list = vec![album_match.clone(), album_match.clone()];
|
||||||
|
|
||||||
let mut info = MatchStateInfo::album_search(album, list);
|
let mut info = MatchStateInfo::album_search(artist_id, album, list);
|
||||||
info.push_cannot_have_mbid();
|
info.push_cannot_have_mbid();
|
||||||
info.push_manual_input_mbid();
|
info.push_manual_input_mbid();
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_lookup() -> MatchStateInfo {
|
fn album_lookup() -> MatchStateInfo {
|
||||||
|
let artist_id = album_artist_id();
|
||||||
let album = album_meta();
|
let album = album_meta();
|
||||||
let album_lookup = Lookup::new(album.clone());
|
let album_lookup = Lookup::new(album.clone());
|
||||||
|
|
||||||
let mut info = MatchStateInfo::album_lookup(album, album_lookup);
|
let mut info = MatchStateInfo::album_lookup(artist_id, album, album_lookup);
|
||||||
info.push_cannot_have_mbid();
|
info.push_cannot_have_mbid();
|
||||||
info.push_manual_input_mbid();
|
info.push_manual_input_mbid();
|
||||||
info
|
info
|
||||||
|
@ -2,8 +2,8 @@ use once_cell::sync::Lazy;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{Album, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq},
|
album::{Album, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq},
|
||||||
artist::{Artist, ArtistId, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistInfo, ArtistMeta},
|
||||||
musicbrainz::{MbArtistRef, MbRefOption},
|
musicbrainz::{MbArtistRef, MbRefOption},
|
||||||
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
||||||
Collection,
|
Collection,
|
||||||
@ -16,9 +16,8 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
id: ArtistId {
|
id: ArtistId {
|
||||||
name: String::from("Аркона"),
|
name: String::from("Аркона"),
|
||||||
},
|
},
|
||||||
sort: Some(ArtistId{
|
sort: Some(String::from("Arkona")),
|
||||||
name: String::from("Arkona")
|
info: ArtistInfo {
|
||||||
}),
|
|
||||||
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
||||||
"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212"
|
"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
@ -34,6 +33,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
)]),
|
)]),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![Album {
|
albums: vec![Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
@ -41,10 +41,12 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
},
|
},
|
||||||
date: 2011.into(),
|
date: 2011.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -209,6 +211,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Eluveitie"),
|
name: String::from("Eluveitie"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
||||||
"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38"
|
"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
@ -221,6 +224,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
)]),
|
)]),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -229,10 +233,12 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
},
|
},
|
||||||
date: 2004.into(),
|
date: 2004.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Ep),
|
primary_type: Some(AlbumPrimaryType::Ep),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -309,10 +315,12 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
},
|
},
|
||||||
date: 2008.into(),
|
date: 2008.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -456,6 +464,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Frontside"),
|
name: String::from("Frontside"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
||||||
"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490"
|
"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
@ -468,6 +477,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
)]),
|
)]),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![Album {
|
albums: vec![Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
@ -475,10 +485,12 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
},
|
},
|
||||||
date: 2001.into(),
|
date: 2001.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -609,9 +621,8 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
id: ArtistId {
|
id: ArtistId {
|
||||||
name: String::from("Heaven’s Basement"),
|
name: String::from("Heaven’s Basement"),
|
||||||
},
|
},
|
||||||
sort: Some(ArtistId {
|
sort: Some(String::from("Heaven’s Basement")),
|
||||||
name: String::from("Heaven’s Basement"),
|
info: ArtistInfo {
|
||||||
}),
|
|
||||||
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
||||||
"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc"
|
"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
@ -624,6 +635,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
)]),
|
)]),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![Album {
|
albums: vec![Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
@ -631,9 +643,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
},
|
},
|
||||||
date: 2011.into(),
|
date: 2011.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
musicbrainz: MbRefOption::None,
|
info: AlbumInfo::default(),
|
||||||
primary_type: None,
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -655,10 +665,12 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
},
|
},
|
||||||
date: 2011.into(),
|
date: 2011.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -746,6 +758,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Metallica"),
|
name: String::from("Metallica"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
musicbrainz: MbRefOption::Some(MbArtistRef::from_url_str(
|
||||||
"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab"
|
"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
@ -758,6 +771,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
)]),
|
)]),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
meta: AlbumMeta {
|
meta: AlbumMeta {
|
||||||
@ -766,10 +780,12 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
},
|
},
|
||||||
date: 1984.into(),
|
date: 1984.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
secondary_types: vec![],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
@ -868,10 +884,12 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
},
|
},
|
||||||
date: 1999.into(),
|
date: 1999.into(),
|
||||||
seq: AlbumSeq(0),
|
seq: AlbumSeq(0),
|
||||||
|
info: AlbumInfo {
|
||||||
musicbrainz: MbRefOption::None,
|
musicbrainz: MbRefOption::None,
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
primary_type: Some(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![AlbumSecondaryType::Live],
|
secondary_types: vec![AlbumSecondaryType::Live],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
id: TrackId {
|
id: TrackId {
|
||||||
|
Loading…
Reference in New Issue
Block a user