WIP: Refactor the IDatabase calls to write directly to the database #271
@ -22,11 +22,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: cargo build --no-default-features --all-targets
|
- run: cargo build --no-default-features --all-targets
|
||||||
- run: cargo test --no-default-features --all-targets --no-fail-fast
|
- run: cargo test --no-default-features --all-targets --no-fail-fast -- --include-ignored
|
||||||
- run: cargo build --all-targets
|
- run: cargo build --all-targets
|
||||||
- run: cargo test --all-targets --no-fail-fast
|
- run: cargo test --all-targets --no-fail-fast -- --include-ignored
|
||||||
- run: cargo build --all-features --all-targets
|
- run: cargo build --all-features --all-targets
|
||||||
- run: cargo test --all-features --all-targets --no-fail-fast
|
- run: cargo test --all-features --all-targets --no-fail-fast -- --include-ignored
|
||||||
- run: >-
|
- run: >-
|
||||||
grcov target/debug/profraw
|
grcov target/debug/profraw
|
||||||
--binary-path target/debug/
|
--binary-path target/debug/
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
This feature requires the `sqlite` library.
|
This feature requires the `sqlite` library.
|
||||||
|
|
||||||
Either install system libraries: with
|
Either install system libraries with:
|
||||||
|
|
||||||
On Fedora:
|
On Fedora:
|
||||||
``` sh
|
``` sh
|
||||||
@ -17,6 +17,13 @@ sudo dnf install sqlite-devel
|
|||||||
|
|
||||||
Or use a bundled version by enabling the `database-sqlite-bundled` feature.
|
Or use a bundled version by enabling the `database-sqlite-bundled` feature.
|
||||||
|
|
||||||
|
To run the tests you will also need `sqldiff`.
|
||||||
|
|
||||||
|
On Fedora:
|
||||||
|
``` sh
|
||||||
|
sudo dnf install sqlite-tools
|
||||||
|
```
|
||||||
|
|
||||||
#### musicbrainz-api
|
#### musicbrainz-api
|
||||||
|
|
||||||
This feature requires the `openssl` system library.
|
This feature requires the `openssl` system library.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use crate::core::collection::{
|
use crate::core::collection::{
|
||||||
merge::{Merge, MergeSorted},
|
merge::{IntoId, Merge, MergeSorted, WithId},
|
||||||
musicbrainz::{MbAlbumRef, MbRefOption},
|
musicbrainz::{MbAlbumRef, MbRefOption},
|
||||||
track::{Track, TrackFormat},
|
track::{Track, TrackFormat},
|
||||||
};
|
};
|
||||||
@ -19,14 +19,14 @@ pub struct Album {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AlbumMeta {
|
pub struct AlbumMeta {
|
||||||
pub id: AlbumId,
|
pub id: AlbumId,
|
||||||
pub date: AlbumDate,
|
|
||||||
pub seq: AlbumSeq,
|
|
||||||
pub info: AlbumInfo,
|
pub info: AlbumInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Album non-identifier metadata.
|
/// Album non-identifier metadata.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct AlbumInfo {
|
pub struct AlbumInfo {
|
||||||
|
pub date: AlbumDate,
|
||||||
|
pub seq: AlbumSeq,
|
||||||
pub primary_type: Option<AlbumPrimaryType>,
|
pub primary_type: Option<AlbumPrimaryType>,
|
||||||
pub secondary_types: Vec<AlbumSecondaryType>,
|
pub secondary_types: Vec<AlbumSecondaryType>,
|
||||||
}
|
}
|
||||||
@ -93,6 +93,12 @@ impl From<(u32, u8, u8)> for AlbumDate {
|
|||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||||
pub struct AlbumSeq(pub u8);
|
pub struct AlbumSeq(pub u8);
|
||||||
|
|
||||||
|
impl From<u8> for AlbumSeq {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
AlbumSeq(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Based on [MusicBrainz types](https://musicbrainz.org/doc/Release_Group/Type).
|
/// Based on [MusicBrainz types](https://musicbrainz.org/doc/Release_Group/Type).
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum AlbumPrimaryType {
|
pub enum AlbumPrimaryType {
|
||||||
@ -181,7 +187,7 @@ impl Album {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_date<Date: Into<AlbumDate>>(mut self, date: Date) -> Self {
|
pub fn with_date<Date: Into<AlbumDate>>(mut self, date: Date) -> Self {
|
||||||
self.meta.date = date.into();
|
self.meta.info.date = date.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,6 +208,23 @@ impl Ord for Album {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WithId for Album {
|
||||||
|
type Id = AlbumId;
|
||||||
|
|
||||||
|
fn id(&self) -> &Self::Id {
|
||||||
|
&self.meta.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoId for Album {
|
||||||
|
type Id = AlbumId;
|
||||||
|
type IdSelf = Album;
|
||||||
|
|
||||||
|
fn into_id(self, _: &Self::Id) -> Self::IdSelf {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Merge for Album {
|
impl Merge for Album {
|
||||||
fn merge_in_place(&mut self, other: Self) {
|
fn merge_in_place(&mut self, other: Self) {
|
||||||
self.meta.merge_in_place(other.meta);
|
self.meta.merge_in_place(other.meta);
|
||||||
@ -214,14 +237,12 @@ impl AlbumMeta {
|
|||||||
pub fn new<Id: Into<AlbumId>>(id: Id) -> Self {
|
pub fn new<Id: Into<AlbumId>>(id: Id) -> Self {
|
||||||
AlbumMeta {
|
AlbumMeta {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
date: AlbumDate::default(),
|
|
||||||
seq: AlbumSeq::default(),
|
|
||||||
info: AlbumInfo::default(),
|
info: AlbumInfo::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_date<Date: Into<AlbumDate>>(mut self, date: Date) -> Self {
|
pub fn with_date<Date: Into<AlbumDate>>(mut self, date: Date) -> Self {
|
||||||
self.date = date.into();
|
self.info.date = date.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,8 +253,8 @@ impl AlbumMeta {
|
|||||||
|
|
||||||
pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &str, &Option<AlbumPrimaryType>) {
|
pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &str, &Option<AlbumPrimaryType>) {
|
||||||
(
|
(
|
||||||
&self.date,
|
&self.info.date,
|
||||||
&self.seq,
|
&self.info.seq,
|
||||||
&self.id.title,
|
&self.id.title,
|
||||||
&self.info.primary_type,
|
&self.info.primary_type,
|
||||||
)
|
)
|
||||||
@ -247,6 +268,36 @@ impl AlbumMeta {
|
|||||||
self.id.clear_mb_ref();
|
self.id.clear_mb_ref();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_seq(&mut self, seq: AlbumSeq) {
|
||||||
|
self.info.set_seq(seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_seq(&mut self) {
|
||||||
|
self.info.clear_seq();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlbumInfo {
|
||||||
|
pub fn with_date<Date: Into<AlbumDate>>(mut self, date: Date) -> Self {
|
||||||
|
self.date = date.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_seq<Seq: Into<AlbumSeq>>(mut self, seq: Seq) -> Self {
|
||||||
|
self.seq = seq.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_primary_type(mut self, primary_type: AlbumPrimaryType) -> Self {
|
||||||
|
self.primary_type = Some(primary_type);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_secondary_types(mut self, secondary_types: Vec<AlbumSecondaryType>) -> Self {
|
||||||
|
self.secondary_types = secondary_types;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_seq(&mut self, seq: AlbumSeq) {
|
pub fn set_seq(&mut self, seq: AlbumSeq) {
|
||||||
self.seq = seq;
|
self.seq = seq;
|
||||||
}
|
}
|
||||||
@ -256,18 +307,6 @@ impl AlbumMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlbumInfo {
|
|
||||||
pub fn new(
|
|
||||||
primary_type: Option<AlbumPrimaryType>,
|
|
||||||
secondary_types: Vec<AlbumSecondaryType>,
|
|
||||||
) -> Self {
|
|
||||||
AlbumInfo {
|
|
||||||
primary_type,
|
|
||||||
secondary_types,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for AlbumMeta {
|
impl PartialOrd for AlbumMeta {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
@ -284,16 +323,16 @@ impl Merge for AlbumMeta {
|
|||||||
fn merge_in_place(&mut self, other: Self) {
|
fn merge_in_place(&mut self, other: Self) {
|
||||||
assert!(self.id.compatible(&other.id));
|
assert!(self.id.compatible(&other.id));
|
||||||
self.id.mb_ref = self.id.mb_ref.take().or(other.id.mb_ref);
|
self.id.mb_ref = self.id.mb_ref.take().or(other.id.mb_ref);
|
||||||
if self.date.year.is_none() && other.date.year.is_some() {
|
|
||||||
self.date = other.date;
|
|
||||||
}
|
|
||||||
self.seq = std::cmp::max(self.seq, other.seq);
|
|
||||||
self.info.merge_in_place(other.info);
|
self.info.merge_in_place(other.info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Merge for AlbumInfo {
|
impl Merge for AlbumInfo {
|
||||||
fn merge_in_place(&mut self, other: Self) {
|
fn merge_in_place(&mut self, other: Self) {
|
||||||
|
if self.date.year.is_none() && other.date.year.is_some() {
|
||||||
|
self.date = other.date;
|
||||||
|
}
|
||||||
|
self.seq = std::cmp::max(self.seq, other.seq);
|
||||||
self.primary_type = self.primary_type.take().or(other.primary_type);
|
self.primary_type = self.primary_type.take().or(other.primary_type);
|
||||||
if self.secondary_types.is_empty() {
|
if self.secondary_types.is_empty() {
|
||||||
self.secondary_types = other.secondary_types;
|
self.secondary_types = other.secondary_types;
|
||||||
@ -385,21 +424,21 @@ mod tests {
|
|||||||
fn set_clear_seq() {
|
fn set_clear_seq() {
|
||||||
let mut album = Album::new("An album");
|
let mut album = Album::new("An album");
|
||||||
|
|
||||||
assert_eq!(album.meta.seq, AlbumSeq(0));
|
assert_eq!(album.meta.info.seq, AlbumSeq(0));
|
||||||
|
|
||||||
// Setting a seq on an album.
|
// Setting a seq on an album.
|
||||||
album.meta.set_seq(AlbumSeq(6));
|
album.meta.set_seq(AlbumSeq(6));
|
||||||
assert_eq!(album.meta.seq, AlbumSeq(6));
|
assert_eq!(album.meta.info.seq, AlbumSeq(6));
|
||||||
|
|
||||||
album.meta.set_seq(AlbumSeq(6));
|
album.meta.set_seq(AlbumSeq(6));
|
||||||
assert_eq!(album.meta.seq, AlbumSeq(6));
|
assert_eq!(album.meta.info.seq, AlbumSeq(6));
|
||||||
|
|
||||||
album.meta.set_seq(AlbumSeq(8));
|
album.meta.set_seq(AlbumSeq(8));
|
||||||
assert_eq!(album.meta.seq, AlbumSeq(8));
|
assert_eq!(album.meta.info.seq, AlbumSeq(8));
|
||||||
|
|
||||||
// Clearing seq.
|
// Clearing seq.
|
||||||
album.meta.clear_seq();
|
album.meta.clear_seq();
|
||||||
assert_eq!(album.meta.seq, AlbumSeq(0));
|
assert_eq!(album.meta.info.seq, AlbumSeq(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -439,7 +478,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_album_dates() {
|
fn merge_album_dates() {
|
||||||
let meta = AlbumMeta::new(AlbumId::new("An album"));
|
let meta = AlbumInfo::default();
|
||||||
|
|
||||||
// No merge if years are different.
|
// No merge if years are different.
|
||||||
let left = meta.clone().with_date((2000, 1, 6));
|
let left = meta.clone().with_date((2000, 1, 6));
|
||||||
|
@ -11,56 +11,57 @@ use crate::core::collection::{
|
|||||||
string::{self, NormalString},
|
string::{self, NormalString},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::merge::WithId;
|
||||||
|
|
||||||
/// An artist.
|
/// An artist.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Artist {
|
pub struct Artist {
|
||||||
|
pub id: ArtistId,
|
||||||
pub meta: ArtistMeta,
|
pub meta: ArtistMeta,
|
||||||
pub albums: Vec<Album>,
|
pub albums: Vec<Album>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Artist metadata.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ArtistMeta {
|
pub struct ArtistMeta {
|
||||||
pub id: ArtistId,
|
pub name: ArtistName,
|
||||||
pub sort: Option<String>,
|
pub sort: Option<ArtistName>,
|
||||||
pub info: ArtistInfo,
|
pub info: ArtistInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Artist non-identifier metadata.
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct ArtistInfo {
|
pub struct ArtistInfo {
|
||||||
|
pub mb_ref: ArtistMbRef,
|
||||||
pub properties: HashMap<String, Vec<String>>,
|
pub properties: HashMap<String, Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The artist identifier.
|
/// The artist identifier.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ArtistId {
|
pub struct ArtistId(pub usize);
|
||||||
pub name: String,
|
|
||||||
pub mb_ref: ArtistMbRef,
|
impl From<usize> for ArtistId {
|
||||||
|
fn from(value: usize) -> Self {
|
||||||
|
ArtistId(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<ArtistId> for ArtistId {
|
||||||
|
fn as_ref(&self) -> &ArtistId {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ArtistId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The artist name.
|
||||||
|
pub type ArtistName = String;
|
||||||
|
|
||||||
/// Unique database identifier. Use MBID for this purpose.
|
/// Unique database identifier. Use MBID for this purpose.
|
||||||
pub type ArtistMbRef = MbRefOption<MbArtistRef>;
|
pub type ArtistMbRef = MbRefOption<MbArtistRef>;
|
||||||
|
|
||||||
impl PartialOrd for Artist {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for Artist {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.meta.cmp(&other.meta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Merge for Artist {
|
|
||||||
fn merge_in_place(&mut self, other: Self) {
|
|
||||||
self.meta.merge_in_place(other.meta);
|
|
||||||
self.albums = MergeAlbums::merge_albums(mem::take(&mut self.albums), other.albums);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct MergeAlbums {
|
struct MergeAlbums {
|
||||||
primary_by_lib_id: HashMap<u32, Album>,
|
primary_by_lib_id: HashMap<u32, Album>,
|
||||||
@ -74,10 +75,10 @@ impl MergeAlbums {
|
|||||||
fn merge_albums(primary_albums: Vec<Album>, secondary_albums: Vec<Album>) -> Vec<Album> {
|
fn merge_albums(primary_albums: Vec<Album>, secondary_albums: Vec<Album>) -> Vec<Album> {
|
||||||
let mut cache = MergeAlbums::new(primary_albums);
|
let mut cache = MergeAlbums::new(primary_albums);
|
||||||
cache.merge_albums_by_lib_id(secondary_albums);
|
cache.merge_albums_by_lib_id(secondary_albums);
|
||||||
cache.merged.extend(MergeCollections::merge_by_name(
|
let (merged, left) =
|
||||||
cache.primary_by_title,
|
MergeCollections::merge_by_name(cache.primary_by_title, cache.secondary_by_title);
|
||||||
cache.secondary_by_title,
|
cache.merged.extend(merged);
|
||||||
));
|
cache.merged.extend(left);
|
||||||
cache.merged.sort_unstable();
|
cache.merged.sort_unstable();
|
||||||
cache.merged
|
cache.merged
|
||||||
}
|
}
|
||||||
@ -140,49 +141,65 @@ impl MergeAlbums {
|
|||||||
|
|
||||||
impl Artist {
|
impl Artist {
|
||||||
/// Create new [`Artist`] with the given [`ArtistId`].
|
/// Create new [`Artist`] with the given [`ArtistId`].
|
||||||
pub fn new<Id: Into<ArtistId>>(id: Id) -> Self {
|
pub fn new<Id: Into<ArtistId>, Name: Into<ArtistName>>(id: Id, name: Name) -> Self {
|
||||||
Artist {
|
Artist {
|
||||||
meta: ArtistMeta::new(id),
|
id: id.into(),
|
||||||
|
meta: ArtistMeta::new(name),
|
||||||
albums: vec![],
|
albums: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArtistMeta {
|
impl ArtistMeta {
|
||||||
pub fn new<Id: Into<ArtistId>>(id: Id) -> Self {
|
pub fn new<Name: Into<ArtistName>>(name: Name) -> Self {
|
||||||
ArtistMeta {
|
ArtistMeta {
|
||||||
id: id.into(),
|
name: name.into(),
|
||||||
sort: None,
|
sort: None,
|
||||||
info: ArtistInfo::default(),
|
info: ArtistInfo::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mb_ref(&mut self, mb_ref: ArtistMbRef) {
|
pub fn compatible(&self, other: &ArtistMeta) -> bool {
|
||||||
self.id.set_mb_ref(mb_ref);
|
let names_compatible =
|
||||||
}
|
string::normalize_string(&self.name) == string::normalize_string(&other.name);
|
||||||
|
let mb_ref_compatible = self.info.mb_ref.is_none()
|
||||||
pub fn clear_mb_ref(&mut self) {
|
|| other.info.mb_ref.is_none()
|
||||||
self.id.clear_mb_ref();
|
|| (self.info.mb_ref == other.info.mb_ref);
|
||||||
|
names_compatible && mb_ref_compatible
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sort_key(&self) -> (&str,) {
|
pub fn get_sort_key(&self) -> (&str,) {
|
||||||
(self.sort.as_ref().unwrap_or(&self.id.name),)
|
(self.sort.as_ref().unwrap_or(&self.name),)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sort_key<S: Into<String>>(&mut self, sort: S) {
|
pub fn with_sort<S: Into<String>>(mut self, name: S) -> Self {
|
||||||
self.sort = Some(sort.into());
|
self.sort = Some(name.into());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_sort_key(&mut self) {
|
pub fn with_mb_ref(mut self, mb_ref: ArtistMbRef) -> Self {
|
||||||
self.sort.take();
|
self.info.set_mb_ref(mb_ref);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArtistInfo {
|
impl ArtistInfo {
|
||||||
|
pub fn with_mb_ref(mut self, mb_ref: ArtistMbRef) -> Self {
|
||||||
|
self.set_mb_ref(mb_ref);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_mb_ref(&mut self, mb_ref: ArtistMbRef) {
|
||||||
|
self.mb_ref = mb_ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_mb_ref(&mut self) {
|
||||||
|
self.mb_ref.take();
|
||||||
|
}
|
||||||
|
|
||||||
// In the functions below, it would be better to use `contains` instead of `iter().any`, but for
|
// In the functions below, it would be better to use `contains` instead of `iter().any`, but for
|
||||||
// type reasons that does not work:
|
// type reasons that does not work:
|
||||||
// https://stackoverflow.com/questions/48985924/why-does-a-str-not-coerce-to-a-string-when-using-veccontains
|
// https://stackoverflow.com/questions/48985924/why-does-a-str-not-coerce-to-a-string-when-using-veccontains
|
||||||
|
|
||||||
pub fn add_to_property<S: AsRef<str> + Into<String>>(&mut self, property: S, values: Vec<S>) {
|
pub fn add_to_property<S: AsRef<str> + Into<String>>(&mut self, property: S, values: Vec<S>) {
|
||||||
match self.properties.get_mut(property.as_ref()) {
|
match self.properties.get_mut(property.as_ref()) {
|
||||||
Some(container) => {
|
Some(container) => {
|
||||||
@ -224,6 +241,49 @@ impl ArtistInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WithId for Artist {
|
||||||
|
type Id = ArtistId;
|
||||||
|
|
||||||
|
fn id(&self) -> &Self::Id {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Merge for Artist {
|
||||||
|
fn merge_in_place(&mut self, other: Self) {
|
||||||
|
assert_eq!(self.id, other.id);
|
||||||
|
self.meta.merge_in_place(other.meta);
|
||||||
|
self.albums = MergeAlbums::merge_albums(mem::take(&mut self.albums), other.albums);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Merge for ArtistMeta {
|
||||||
|
fn merge_in_place(&mut self, other: Self) {
|
||||||
|
assert!(self.compatible(&other));
|
||||||
|
// No merge for name - always keep original.
|
||||||
|
self.info.merge_in_place(other.info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Merge for ArtistInfo {
|
||||||
|
fn merge_in_place(&mut self, other: Self) {
|
||||||
|
self.mb_ref = self.mb_ref.take().or(other.mb_ref);
|
||||||
|
self.properties.merge_in_place(other.properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Artist {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Artist {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.meta.cmp(&other.meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialOrd for ArtistMeta {
|
impl PartialOrd for ArtistMeta {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
@ -236,69 +296,6 @@ impl Ord for ArtistMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Merge for ArtistMeta {
|
|
||||||
fn merge_in_place(&mut self, other: Self) {
|
|
||||||
assert!(self.id.compatible(&other.id));
|
|
||||||
self.id.mb_ref = self.id.mb_ref.take().or(other.id.mb_ref);
|
|
||||||
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.properties.merge_in_place(other.properties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Into<String>> From<S> for ArtistId {
|
|
||||||
fn from(value: S) -> Self {
|
|
||||||
ArtistId::new(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<ArtistId> for ArtistId {
|
|
||||||
fn as_ref(&self) -> &ArtistId {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArtistId {
|
|
||||||
pub fn new<S: Into<String>>(name: S) -> ArtistId {
|
|
||||||
ArtistId {
|
|
||||||
name: name.into(),
|
|
||||||
mb_ref: ArtistMbRef::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_mb_ref(mut self, mb_ref: ArtistMbRef) -> Self {
|
|
||||||
self.mb_ref = mb_ref;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_mb_ref(&mut self, mb_ref: ArtistMbRef) {
|
|
||||||
self.mb_ref = mb_ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_mb_ref(&mut self) {
|
|
||||||
self.mb_ref.take();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compatible(&self, other: &ArtistId) -> bool {
|
|
||||||
let names_compatible =
|
|
||||||
string::normalize_string(&self.name) == string::normalize_string(&other.name);
|
|
||||||
let mb_ref_compatible =
|
|
||||||
self.mb_ref.is_none() || other.mb_ref.is_none() || (self.mb_ref == other.mb_ref);
|
|
||||||
names_compatible && mb_ref_compatible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ArtistId {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -318,89 +315,41 @@ mod tests {
|
|||||||
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/";
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn artist_sort_set_clear() {
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
|
||||||
let sort_id_1 = String::from("sort id 1");
|
|
||||||
let sort_id_2 = String::from("sort id 2");
|
|
||||||
|
|
||||||
let mut artist = Artist::new(&artist_id.name);
|
|
||||||
|
|
||||||
assert_eq!(artist.meta.id, artist_id);
|
|
||||||
assert_eq!(artist.meta.sort, None);
|
|
||||||
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_2.clone()));
|
|
||||||
assert!(artist < Artist::new(sort_id_1.clone()));
|
|
||||||
assert!(artist < Artist::new(sort_id_2.clone()));
|
|
||||||
|
|
||||||
artist.meta.set_sort_key(sort_id_1.clone());
|
|
||||||
|
|
||||||
assert_eq!(artist.meta.id, artist_id);
|
|
||||||
assert_eq!(artist.meta.sort.as_ref(), Some(&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(sort_id_2.clone()));
|
|
||||||
assert!(artist > Artist::new(artist_id.clone()));
|
|
||||||
assert!(artist < Artist::new(sort_id_2.clone()));
|
|
||||||
|
|
||||||
artist.meta.set_sort_key(sort_id_2.clone());
|
|
||||||
|
|
||||||
assert_eq!(artist.meta.id, artist_id);
|
|
||||||
assert_eq!(artist.meta.sort.as_ref(), Some(&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(sort_id_1.clone()));
|
|
||||||
assert!(artist > Artist::new(artist_id.clone()));
|
|
||||||
assert!(artist > Artist::new(sort_id_1.clone()));
|
|
||||||
|
|
||||||
artist.meta.clear_sort_key();
|
|
||||||
|
|
||||||
assert_eq!(artist.meta.id, artist_id);
|
|
||||||
assert_eq!(artist.meta.sort, None);
|
|
||||||
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_2.clone()));
|
|
||||||
assert!(artist < Artist::new(sort_id_1.clone()));
|
|
||||||
assert!(artist < Artist::new(sort_id_2.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_musicbrainz_url() {
|
fn set_clear_musicbrainz_url() {
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut info = ArtistInfo::default();
|
||||||
|
|
||||||
let mut expected: MbRefOption<MbArtistRef> = MbRefOption::None;
|
let mut expected: MbRefOption<MbArtistRef> = MbRefOption::None;
|
||||||
assert_eq!(artist.meta.id.mb_ref, expected);
|
assert_eq!(info.mb_ref, expected);
|
||||||
|
|
||||||
// Setting a URL on an artist.
|
// Setting a URL on an info.
|
||||||
artist.meta.id.set_mb_ref(MbRefOption::Some(
|
info.set_mb_ref(MbRefOption::Some(
|
||||||
MbArtistRef::from_url_str(MUSICBRAINZ).unwrap(),
|
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.id.mb_ref, expected);
|
assert_eq!(info.mb_ref, expected);
|
||||||
|
|
||||||
artist.meta.id.set_mb_ref(MbRefOption::Some(
|
info.set_mb_ref(MbRefOption::Some(
|
||||||
MbArtistRef::from_url_str(MUSICBRAINZ).unwrap(),
|
MbArtistRef::from_url_str(MUSICBRAINZ).unwrap(),
|
||||||
));
|
));
|
||||||
assert_eq!(artist.meta.id.mb_ref, expected);
|
assert_eq!(info.mb_ref, expected);
|
||||||
|
|
||||||
artist.meta.id.set_mb_ref(MbRefOption::Some(
|
info.set_mb_ref(MbRefOption::Some(
|
||||||
MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap(),
|
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.id.mb_ref, expected);
|
assert_eq!(info.mb_ref, expected);
|
||||||
|
|
||||||
// Clearing URLs.
|
// Clearing URLs.
|
||||||
artist.meta.id.clear_mb_ref();
|
info.clear_mb_ref();
|
||||||
expected.take();
|
expected.take();
|
||||||
assert_eq!(artist.meta.id.mb_ref, expected);
|
assert_eq!(info.mb_ref, 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 info = ArtistInfo::default();
|
||||||
|
|
||||||
let info = &mut artist.meta.info;
|
|
||||||
let mut expected: Vec<String> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert!(info.properties.is_empty());
|
assert!(info.properties.is_empty());
|
||||||
|
|
||||||
@ -470,9 +419,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_musicbutler_urls() {
|
fn set_clear_musicbutler_urls() {
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut info = ArtistInfo::default();
|
||||||
|
|
||||||
let info = &mut artist.meta.info;
|
|
||||||
let mut expected: Vec<String> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert!(info.properties.is_empty());
|
assert!(info.properties.is_empty());
|
||||||
|
|
||||||
@ -508,8 +456,9 @@ mod tests {
|
|||||||
fn merge_artist_no_overlap() {
|
fn merge_artist_no_overlap() {
|
||||||
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.id = left.id;
|
||||||
right.meta.id.mb_ref = MbRefOption::None;
|
right.meta.name = left.meta.name.clone();
|
||||||
|
right.meta.info.mb_ref = MbRefOption::None;
|
||||||
right.meta.info.properties = HashMap::new();
|
right.meta.info.properties = HashMap::new();
|
||||||
|
|
||||||
let mut expected = left.clone();
|
let mut expected = left.clone();
|
||||||
@ -545,7 +494,9 @@ mod tests {
|
|||||||
fn merge_artist_overlap() {
|
fn merge_artist_overlap() {
|
||||||
let mut left = FULL_COLLECTION[0].to_owned();
|
let mut 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.id = left.id;
|
||||||
|
right.meta.name = left.meta.name.clone();
|
||||||
|
right.meta.info.mb_ref = left.meta.info.mb_ref.clone();
|
||||||
|
|
||||||
// The right collection needs more albums than we modify to make sure some do not overlap.
|
// The right collection needs more albums than we modify to make sure some do not overlap.
|
||||||
assert!(right.albums.len() > 2);
|
assert!(right.albums.len() > 2);
|
||||||
@ -581,7 +532,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "multiple secondaries unsupported")]
|
#[should_panic(expected = "multiple secondaries unsupported")]
|
||||||
fn merge_two_db_albums_to_one_lib_album() {
|
fn merge_two_db_albums_to_one_lib_album() {
|
||||||
let mut left = Artist::new(ArtistId::new("Artist"));
|
let mut left = Artist::new(0, "Artist");
|
||||||
let mut right = left.clone();
|
let mut right = left.clone();
|
||||||
|
|
||||||
let album = Album::new(AlbumId::new("Album"));
|
let album = Album::new(AlbumId::new("Album"));
|
||||||
@ -598,7 +549,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "multiple primaries unsupported")]
|
#[should_panic(expected = "multiple primaries unsupported")]
|
||||||
fn merge_one_db_album_to_two_lib_albums() {
|
fn merge_one_db_album_to_two_lib_albums() {
|
||||||
let mut left = Artist::new(ArtistId::new("Artist"));
|
let mut left = Artist::new(0, "Artist");
|
||||||
let mut right = left.clone();
|
let mut right = left.clone();
|
||||||
|
|
||||||
let album = Album::new(AlbumId::new("Album"));
|
let album = Album::new(AlbumId::new("Album"));
|
||||||
@ -615,7 +566,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_normalized_album_titles() {
|
fn merge_normalized_album_titles() {
|
||||||
let mut left = Artist::new(ArtistId::new("Artist"));
|
let mut left = Artist::new(0, "Artist");
|
||||||
let mut right = left.clone();
|
let mut right = left.clone();
|
||||||
|
|
||||||
left.albums
|
left.albums
|
||||||
@ -640,7 +591,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_multiple_singletons() {
|
fn merge_multiple_singletons() {
|
||||||
let mut left = Artist::new(ArtistId::new("Artist"));
|
let mut left = Artist::new(0, "Artist");
|
||||||
let mut right = left.clone();
|
let mut right = left.clone();
|
||||||
|
|
||||||
left.albums.push(Album::new(AlbumId::new("Singleton 1")));
|
left.albums.push(Album::new(AlbumId::new("Singleton 1")));
|
||||||
@ -666,7 +617,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_two_db_albums_to_one_lib_album_with_ids() {
|
fn merge_two_db_albums_to_one_lib_album_with_ids() {
|
||||||
let mut left = Artist::new(ArtistId::new("Artist"));
|
let mut left = Artist::new(0, "Artist");
|
||||||
let mut right = left.clone();
|
let mut right = left.clone();
|
||||||
|
|
||||||
let album = Album::new(AlbumId::new("Album"));
|
let album = Album::new(AlbumId::new("Album"));
|
||||||
|
@ -105,10 +105,26 @@ impl<T> NormalMap<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait WithId {
|
||||||
|
type Id;
|
||||||
|
|
||||||
|
fn id(&self) -> &Self::Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoId {
|
||||||
|
type Id;
|
||||||
|
type IdSelf;
|
||||||
|
|
||||||
|
fn into_id(self, id: &Self::Id) -> Self::IdSelf;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MergeCollections;
|
pub struct MergeCollections;
|
||||||
|
|
||||||
impl MergeCollections {
|
impl MergeCollections {
|
||||||
pub fn merge_by_name<T: Merge>(mut primary: NormalMap<T>, secondary: NormalMap<T>) -> Vec<T> {
|
pub fn merge_by_name<Id, T1: Merge + WithId<Id = Id>, T2: IntoId<Id = Id, IdSelf = T1>>(
|
||||||
|
mut primary: NormalMap<T2>,
|
||||||
|
secondary: NormalMap<T1>,
|
||||||
|
) -> (Vec<T1>, Vec<T2>) {
|
||||||
let mut merged = vec![];
|
let mut merged = vec![];
|
||||||
for (title, mut secondary_items) in secondary.0.into_iter() {
|
for (title, mut secondary_items) in secondary.0.into_iter() {
|
||||||
match primary.remove(&title) {
|
match primary.remove(&title) {
|
||||||
@ -117,14 +133,17 @@ impl MergeCollections {
|
|||||||
// added once encountered in the wild.
|
// added once encountered in the wild.
|
||||||
assert_eq!(primary_items.len(), 1, "multiple primaries unsupported");
|
assert_eq!(primary_items.len(), 1, "multiple primaries unsupported");
|
||||||
assert_eq!(secondary_items.len(), 1, "multiple secondaries unsupported");
|
assert_eq!(secondary_items.len(), 1, "multiple secondaries unsupported");
|
||||||
let mut primary_item = primary_items.pop().unwrap();
|
|
||||||
primary_item.merge_in_place(secondary_items.pop().unwrap());
|
let secondary_item = secondary_items.pop().unwrap();
|
||||||
|
let id = secondary_item.id();
|
||||||
|
|
||||||
|
let mut primary_item = primary_items.pop().unwrap().into_id(id);
|
||||||
|
primary_item.merge_in_place(secondary_item);
|
||||||
merged.push(primary_item);
|
merged.push(primary_item);
|
||||||
}
|
}
|
||||||
None => merged.extend(secondary_items),
|
None => merged.extend(secondary_items),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
merged.extend(primary.0.into_values().flatten());
|
(merged, primary.0.into_values().flatten().collect())
|
||||||
merged
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,12 @@ pub enum MbRefOption<T> {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Default for MbRefOption<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
MbRefOption::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> MbRefOption<T> {
|
impl<T> MbRefOption<T> {
|
||||||
pub fn is_some(&self) -> bool {
|
pub fn is_some(&self) -> bool {
|
||||||
matches!(self, MbRefOption::Some(_))
|
matches!(self, MbRefOption::Some(_))
|
||||||
|
@ -5,22 +5,32 @@ use std::fmt;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
|
|
||||||
use crate::core::collection::{self, Collection};
|
use crate::{collection::artist::{ArtistId, ArtistMeta}, core::collection::{self, Collection}};
|
||||||
|
|
||||||
/// Trait for interacting with the database.
|
/// Trait for interacting with the database.
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IDatabase {
|
pub trait IDatabase {
|
||||||
|
/// Reset all content.
|
||||||
|
fn reset(&mut self) -> Result<(), SaveError>;
|
||||||
|
|
||||||
/// Load collection from the database.
|
/// Load collection from the database.
|
||||||
fn load(&mut self) -> Result<Collection, LoadError>;
|
fn load(&mut self) -> Result<Collection, LoadError>;
|
||||||
|
|
||||||
/// Save collection to the database.
|
/// Save collection to the database.
|
||||||
fn save(&mut self, collection: &Collection) -> Result<(), SaveError>;
|
fn save(&mut self, collection: &Collection) -> Result<(), SaveError>;
|
||||||
|
|
||||||
|
/// Insert an artist into the database and return its assigned ID.
|
||||||
|
fn insert_artist(&mut self, artist: &ArtistMeta) -> Result<ArtistId, SaveError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Null database implementation of [`IDatabase`].
|
/// Null database implementation of [`IDatabase`].
|
||||||
pub struct NullDatabase;
|
pub struct NullDatabase;
|
||||||
|
|
||||||
impl IDatabase for NullDatabase {
|
impl IDatabase for NullDatabase {
|
||||||
|
fn reset(&mut self) -> Result<(), SaveError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn load(&mut self) -> Result<Collection, LoadError> {
|
fn load(&mut self) -> Result<Collection, LoadError> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
@ -28,6 +38,10 @@ impl IDatabase for NullDatabase {
|
|||||||
fn save(&mut self, _collection: &Collection) -> Result<(), SaveError> {
|
fn save(&mut self, _collection: &Collection) -> Result<(), SaveError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn insert_artist(&mut self, _: &ArtistMeta) -> Result<ArtistId,SaveError> {
|
||||||
|
Ok(ArtistId(0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error type for database calls.
|
/// Error type for database calls.
|
||||||
@ -98,6 +112,12 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_database_reset() {
|
||||||
|
let mut database = NullDatabase;
|
||||||
|
assert!(database.reset().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn null_database_load() {
|
fn null_database_load() {
|
||||||
let mut database = NullDatabase;
|
let mut database = NullDatabase;
|
||||||
|
@ -2,8 +2,7 @@ use crate::core::{
|
|||||||
collection::{
|
collection::{
|
||||||
album::{Album, AlbumId},
|
album::{Album, AlbumId},
|
||||||
artist::{Artist, ArtistId},
|
artist::{Artist, ArtistId},
|
||||||
merge::{MergeCollections, NormalMap},
|
Collection,
|
||||||
string, Collection,
|
|
||||||
},
|
},
|
||||||
musichoard::{filter::CollectionFilter, Error, MusicHoard},
|
musichoard::{filter::CollectionFilter, Error, MusicHoard},
|
||||||
};
|
};
|
||||||
@ -30,14 +29,11 @@ impl<Database, Library> IMusicHoardBase for MusicHoard<Database, Library> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait IMusicHoardBasePrivate {
|
pub trait IMusicHoardBasePrivate {
|
||||||
fn sort_artists(collection: &mut [Artist]);
|
fn sort_albums_and_tracks<'a, COL: Iterator<Item = &'a mut Vec<Album>>>(collection: COL);
|
||||||
fn sort_albums_and_tracks<'a, C: Iterator<Item = &'a mut Artist>>(collection: C);
|
|
||||||
|
|
||||||
fn merge_collections<It: IntoIterator<Item = Artist>>(&self, database: It) -> Collection;
|
|
||||||
fn filter_collection(&self) -> Collection;
|
fn filter_collection(&self) -> Collection;
|
||||||
fn filter_artist(&self, artist: &Artist) -> Option<Artist>;
|
fn filter_artist(&self, artist: &Artist) -> Option<Artist>;
|
||||||
|
|
||||||
fn get_artist<'a>(collection: &'a Collection, artist_id: &ArtistId) -> Option<&'a Artist>;
|
|
||||||
fn get_artist_mut<'a>(
|
fn get_artist_mut<'a>(
|
||||||
collection: &'a mut Collection,
|
collection: &'a mut Collection,
|
||||||
artist_id: &ArtistId,
|
artist_id: &ArtistId,
|
||||||
@ -56,37 +52,15 @@ pub trait IMusicHoardBasePrivate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library> {
|
impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library> {
|
||||||
fn sort_artists(collection: &mut [Artist]) {
|
fn sort_albums_and_tracks<'a, COL: Iterator<Item = &'a mut Vec<Album>>>(collection: COL) {
|
||||||
collection.sort_unstable();
|
for albums in collection {
|
||||||
}
|
albums.sort_unstable();
|
||||||
|
for album in albums.iter_mut() {
|
||||||
fn sort_albums_and_tracks<'a, COL: Iterator<Item = &'a mut Artist>>(collection: COL) {
|
|
||||||
for artist in collection {
|
|
||||||
artist.albums.sort_unstable();
|
|
||||||
for album in artist.albums.iter_mut() {
|
|
||||||
album.tracks.sort_unstable();
|
album.tracks.sort_unstable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_collections<It: IntoIterator<Item = Artist>>(&self, database: It) -> Collection {
|
|
||||||
let mut primary = NormalMap::<Artist>::new();
|
|
||||||
let mut secondary = NormalMap::<Artist>::new();
|
|
||||||
|
|
||||||
for artist in self.library_cache.iter().cloned() {
|
|
||||||
primary.insert(string::normalize_string(&artist.meta.id.name), artist);
|
|
||||||
}
|
|
||||||
|
|
||||||
for artist in database.into_iter() {
|
|
||||||
secondary.insert(string::normalize_string(&artist.meta.id.name), artist);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut collection = MergeCollections::merge_by_name(primary, secondary);
|
|
||||||
collection.sort_unstable();
|
|
||||||
|
|
||||||
collection
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter_collection(&self) -> Collection {
|
fn filter_collection(&self) -> Collection {
|
||||||
let iter = self.collection.iter();
|
let iter = self.collection.iter();
|
||||||
iter.flat_map(|a| self.filter_artist(a)).collect()
|
iter.flat_map(|a| self.filter_artist(a)).collect()
|
||||||
@ -101,19 +75,18 @@ impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library>
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let meta = artist.meta.clone();
|
Some(Artist {
|
||||||
Some(Artist { meta, albums })
|
id: artist.id,
|
||||||
}
|
meta: artist.meta.clone(),
|
||||||
|
albums,
|
||||||
fn get_artist<'a>(collection: &'a Collection, artist_id: &ArtistId) -> Option<&'a Artist> {
|
})
|
||||||
collection.iter().find(|a| &a.meta.id == artist_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_artist_mut<'a>(
|
fn get_artist_mut<'a>(
|
||||||
collection: &'a mut Collection,
|
collection: &'a mut Collection,
|
||||||
artist_id: &ArtistId,
|
artist_id: &ArtistId,
|
||||||
) -> Option<&'a mut Artist> {
|
) -> Option<&'a mut Artist> {
|
||||||
collection.iter_mut().find(|a| &a.meta.id == artist_id)
|
collection.iter_mut().find(|a| &a.id == artist_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_artist_mut_or_err<'a>(
|
fn get_artist_mut_or_err<'a>(
|
||||||
@ -148,180 +121,224 @@ impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library>
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collection::{album::AlbumPrimaryType, artist::ArtistMeta},
|
collection::{
|
||||||
core::testmod::FULL_COLLECTION,
|
album::AlbumPrimaryType,
|
||||||
|
artist::{ArtistMeta, ArtistName},
|
||||||
|
},
|
||||||
|
core::{musichoard::LibArtist, testmod::FULL_COLLECTION},
|
||||||
filter::AlbumField,
|
filter::AlbumField,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
// TODO: figure out how to do a merge
|
||||||
fn merge_collection_no_overlap() {
|
fn merge_collection_no_overlap() {
|
||||||
let half: usize = FULL_COLLECTION.len() / 2;
|
// let half: usize = FULL_COLLECTION.len() / 2;
|
||||||
|
|
||||||
let left = FULL_COLLECTION[..half].to_owned();
|
// let left = FULL_COLLECTION[..half].to_owned();
|
||||||
let right = FULL_COLLECTION[half..].to_owned();
|
// let right = FULL_COLLECTION[half..].to_owned();
|
||||||
|
|
||||||
let mut expected = FULL_COLLECTION.to_owned();
|
// let mut expected = FULL_COLLECTION.to_owned();
|
||||||
expected.sort_unstable();
|
// expected.sort_unstable();
|
||||||
|
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: left.clone(),
|
// library_cache: left.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(right.clone());
|
// mh.collection = mh.merge_collections(right.clone());
|
||||||
assert_eq!(expected, mh.collection);
|
// assert_eq!(expected, mh.collection);
|
||||||
|
|
||||||
// The merge is completely non-overlapping so it should be commutative.
|
// // The merge is completely non-overlapping so it should be commutative.
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: right.clone(),
|
// library_cache: right.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(left.clone());
|
// mh.collection = mh.merge_collections(left.clone());
|
||||||
assert_eq!(expected, mh.collection);
|
// assert_eq!(expected, mh.collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
// TODO: figure out how to do a merge
|
||||||
fn merge_collection_overlap() {
|
fn merge_collection_overlap() {
|
||||||
let half: usize = FULL_COLLECTION.len() / 2;
|
// let half: usize = FULL_COLLECTION.len() / 2;
|
||||||
|
|
||||||
let left = FULL_COLLECTION[..(half + 1)].to_owned();
|
// let left = FULL_COLLECTION[..(half + 1)].to_owned();
|
||||||
let right = FULL_COLLECTION[half..].to_owned();
|
// let right = FULL_COLLECTION[half..].to_owned();
|
||||||
|
|
||||||
let mut expected = FULL_COLLECTION.to_owned();
|
// let mut expected = FULL_COLLECTION.to_owned();
|
||||||
expected.sort_unstable();
|
// expected.sort_unstable();
|
||||||
|
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: left.clone(),
|
// library_cache: left.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(right.clone());
|
// mh.collection = mh.merge_collections(right.clone());
|
||||||
assert_eq!(expected, mh.collection);
|
// assert_eq!(expected, mh.collection);
|
||||||
|
|
||||||
// The merge does not overwrite any data so it should be commutative.
|
// // The merge does not overwrite any data so it should be commutative.
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: right.clone(),
|
// library_cache: right.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(left.clone());
|
// mh.collection = mh.merge_collections(left.clone());
|
||||||
assert_eq!(expected, mh.collection);
|
// assert_eq!(expected, mh.collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
// TODO: figure out how to do a merge
|
||||||
fn merge_collection_incompatible_sorting() {
|
fn merge_collection_incompatible_sorting() {
|
||||||
// It may be that the same artist in one collection has a "sort" field defined while the
|
// // It may be that the same artist in one collection has a "sort" field defined while the
|
||||||
// same artist in the other collection does not. This means that the two collections are not
|
// // same artist in the other collection does not. This means that the two collections are not
|
||||||
// sorted consistently. If the merge assumes they are sorted consistently this will lead to
|
// // sorted consistently. If the merge assumes they are sorted consistently this will lead to
|
||||||
// the same artist appearing twice in the final list. This should not be the case.
|
// // the same artist appearing twice in the final list. This should not be the case.
|
||||||
|
|
||||||
// We will mimic this situation by taking the last artist from FULL_COLLECTION and giving it
|
// // We will mimic this situation by taking the last artist from FULL_COLLECTION and giving it
|
||||||
// a sorting name that would place it in the beginning.
|
// // a sorting name that would place it in the beginning.
|
||||||
let left = FULL_COLLECTION.to_owned();
|
// let left = FULL_COLLECTION.to_owned();
|
||||||
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(String::from("Album_Artist 0"));
|
// let artist_sort = Some(String::from("Album_Artist 0"));
|
||||||
right[0].meta.sort = artist_sort.clone();
|
// right[0].meta.info.sort = artist_sort.clone();
|
||||||
assert!(right.first().unwrap() < left.first().unwrap());
|
// assert!(right.first().unwrap() < left.first().unwrap());
|
||||||
|
|
||||||
// The result of the merge should be the same list of artists, but with the last artist now
|
// // The result of the merge should be the same list of artists, but with the last artist now
|
||||||
// in first place.
|
// // in first place.
|
||||||
let mut expected = left.to_owned();
|
// let mut expected = left.to_owned();
|
||||||
expected.last_mut().as_mut().unwrap().meta.sort = artist_sort.clone();
|
// expected.last_mut().as_mut().unwrap().meta.info.sort = artist_sort.clone();
|
||||||
expected.rotate_right(1);
|
// expected.rotate_right(1);
|
||||||
|
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: left.clone(),
|
// library_cache: left.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(right.clone());
|
// mh.collection = mh.merge_collections(right.clone());
|
||||||
assert_eq!(expected, mh.collection);
|
// assert_eq!(expected, mh.collection);
|
||||||
|
|
||||||
// The merge overwrites the sort data, but no data is erased so it should be commutative.
|
// // The merge overwrites the sort data, but no data is erased so it should be commutative.
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: right.clone(),
|
// library_cache: right.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(left.clone());
|
// mh.collection = mh.merge_collections(left.clone());
|
||||||
assert_eq!(expected, mh.collection);
|
// assert_eq!(expected, mh.collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
// TODO: figure out how to do a merge
|
||||||
#[should_panic(expected = "multiple secondaries unsupported")]
|
#[should_panic(expected = "multiple secondaries unsupported")]
|
||||||
fn merge_two_db_artists_to_one_lib_artist() {
|
fn merge_two_db_artists_to_one_lib_artist() {
|
||||||
let mut left = Collection::new();
|
// let mut left = HashMap::<String, LibArtist>::new();
|
||||||
let mut right = Collection::new();
|
// let mut right = Collection::new();
|
||||||
|
|
||||||
let artist = Artist::new(ArtistId::new("Artist"));
|
// let name = ArtistName::new("Artist");
|
||||||
|
|
||||||
left.push(artist.clone());
|
// left.insert(
|
||||||
right.push(artist.clone());
|
// name.official.clone(),
|
||||||
right.push(artist.clone());
|
// LibArtist {
|
||||||
|
// meta: ArtistMeta::new(name.clone()),
|
||||||
|
// albums: vec![],
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// right.push(Artist::new(1, name.clone()));
|
||||||
|
// right.push(Artist::new(2, name.clone()));
|
||||||
|
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: left.clone(),
|
// library_cache: left.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(right.clone());
|
// mh.collection = mh.merge_collections(right.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
// TODO: change to albums - primary clash is not possible for artists since without a lib id
|
||||||
#[should_panic(expected = "multiple primaries unsupported")]
|
#[should_panic(expected = "multiple primaries unsupported")]
|
||||||
fn merge_one_db_artist_to_two_lib_artists() {
|
fn merge_one_db_artist_to_two_lib_artists() {
|
||||||
let mut left = Collection::new();
|
// let mut left = Collection::new();
|
||||||
let mut right = Collection::new();
|
// let mut right = Collection::new();
|
||||||
|
|
||||||
let artist = Artist::new(ArtistId::new("Artist"));
|
// let artist = Artist::new(ArtistId::new("Artist"));
|
||||||
|
|
||||||
left.push(artist.clone());
|
// left.insert(
|
||||||
left.push(artist.clone());
|
// name.official.clone(),
|
||||||
right.push(artist.clone());
|
// LibArtist {
|
||||||
|
// name: name.clone(),
|
||||||
|
// meta: ArtistMeta::default(),
|
||||||
|
// albums: vec![],
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// left.insert(
|
||||||
|
// name.official.clone(),
|
||||||
|
// LibArtist {
|
||||||
|
// name: name.clone(),
|
||||||
|
// meta: ArtistMeta::default(),
|
||||||
|
// albums: vec![],
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// right.push(artist.clone());
|
||||||
|
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: left.clone(),
|
// library_cache: left.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(right.clone());
|
// mh.collection = mh.merge_collections(right.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
// TODO: figue out how to do a merge
|
||||||
fn merge_normalized_artist_names() {
|
fn merge_normalized_artist_names() {
|
||||||
let mut left = Collection::new();
|
// let mut left = HashMap::<String, LibArtist>::new();
|
||||||
let mut right = Collection::new();
|
// let mut right = Collection::new();
|
||||||
|
|
||||||
left.push(Artist::new(ArtistId::new("Artist‐Name ‘Name’")));
|
// let left_name = "Artist‐Name ‘Name’";
|
||||||
|
// left.insert(
|
||||||
|
// String::from(left_name),
|
||||||
|
// LibArtist {
|
||||||
|
// meta: ArtistMeta::new(left_name.into()),
|
||||||
|
// albums: vec![],
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
right.push(Artist::new(ArtistId::new("arTist—naMe 'name’")));
|
// right.push(Artist::new(1, "arTist—naMe 'name’"));
|
||||||
right.push(Artist::new(ArtistId::new("Artist‐Name “Name”")));
|
// right.push(Artist::new(2, "Artist‐Name “Name”"));
|
||||||
|
|
||||||
// The first artist will be merged, the second will be added.
|
// // The first artist will be merged preserving the name, the second will be added.
|
||||||
let mut expected = left.clone();
|
// let mut expected = right.clone();
|
||||||
expected.push(right.last().unwrap().clone());
|
// expected.first_mut().unwrap().meta.name = left["Artist‐Name ‘Name’"].meta.name.clone();
|
||||||
expected.sort_unstable();
|
|
||||||
|
|
||||||
let mut mh = MusicHoard {
|
// let mut mh = MusicHoard {
|
||||||
library_cache: left.clone(),
|
// library_cache: left.clone(),
|
||||||
..Default::default()
|
// ..Default::default()
|
||||||
};
|
// };
|
||||||
|
|
||||||
mh.collection = mh.merge_collections(right.clone());
|
// mh.collection = mh.merge_collections(right.clone());
|
||||||
assert_eq!(expected, mh.collection);
|
// assert_eq!(expected, mh.collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn filtered() {
|
fn filtered() {
|
||||||
let mut mh = MusicHoard {
|
let mut mh = MusicHoard {
|
||||||
collection: vec![Artist {
|
collection: vec![Artist {
|
||||||
meta: ArtistMeta::new(ArtistId::new("Artist")),
|
id: 0.into(),
|
||||||
|
meta: ArtistMeta::new("Artist"),
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album::new(AlbumId::new("Album 1")),
|
Album::new(AlbumId::new("Album 1")),
|
||||||
Album::new(AlbumId::new("Album 2")),
|
Album::new(AlbumId::new("Album 2")),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
interface::{database::IDatabase, library::ILibrary},
|
interface::{database::IDatabase, library::ILibrary},
|
||||||
musichoard::{CollectionFilter, MusicHoard, NoDatabase, NoLibrary},
|
musichoard::{CollectionFilter, MusicHoard, NoDatabase, NoLibrary},
|
||||||
@ -65,10 +67,9 @@ impl MusicHoard<NoDatabase, NoLibrary> {
|
|||||||
filter: CollectionFilter::default(),
|
filter: CollectionFilter::default(),
|
||||||
filtered: vec![],
|
filtered: vec![],
|
||||||
collection: vec![],
|
collection: vec![],
|
||||||
pre_commit: vec![],
|
|
||||||
database: NoDatabase,
|
database: NoDatabase,
|
||||||
library: NoLibrary,
|
library: NoLibrary,
|
||||||
library_cache: vec![],
|
library_cache: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,10 +88,9 @@ impl<Library: ILibrary> MusicHoard<NoDatabase, Library> {
|
|||||||
filter: CollectionFilter::default(),
|
filter: CollectionFilter::default(),
|
||||||
filtered: vec![],
|
filtered: vec![],
|
||||||
collection: vec![],
|
collection: vec![],
|
||||||
pre_commit: vec![],
|
|
||||||
database: NoDatabase,
|
database: NoDatabase,
|
||||||
library,
|
library,
|
||||||
library_cache: vec![],
|
library_cache: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,10 +109,9 @@ impl<Database: IDatabase> MusicHoard<Database, NoLibrary> {
|
|||||||
filter: CollectionFilter::default(),
|
filter: CollectionFilter::default(),
|
||||||
filtered: vec![],
|
filtered: vec![],
|
||||||
collection: vec![],
|
collection: vec![],
|
||||||
pre_commit: vec![],
|
|
||||||
database,
|
database,
|
||||||
library: NoLibrary,
|
library: NoLibrary,
|
||||||
library_cache: vec![],
|
library_cache: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,10 +130,9 @@ impl<Database: IDatabase, Library: ILibrary> MusicHoard<Database, Library> {
|
|||||||
filter: CollectionFilter::default(),
|
filter: CollectionFilter::default(),
|
||||||
filtered: vec![],
|
filtered: vec![],
|
||||||
collection: vec![],
|
collection: vec![],
|
||||||
pre_commit: vec![],
|
|
||||||
database,
|
database,
|
||||||
library,
|
library,
|
||||||
library_cache: vec![],
|
library_cache: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,40 +3,28 @@ use std::mem;
|
|||||||
use crate::{
|
use crate::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{AlbumInfo, AlbumMbRef, AlbumMeta},
|
album::{AlbumInfo, AlbumMbRef, AlbumMeta},
|
||||||
artist::{ArtistInfo, ArtistMbRef},
|
artist::ArtistInfo,
|
||||||
merge::Merge,
|
merge::{Merge, MergeCollections, NormalMap},
|
||||||
|
string,
|
||||||
},
|
},
|
||||||
core::{
|
core::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{Album, AlbumId, AlbumSeq},
|
album::{Album, AlbumId},
|
||||||
artist::{Artist, ArtistId},
|
artist::{Artist, ArtistId},
|
||||||
Collection,
|
Collection,
|
||||||
},
|
},
|
||||||
interface::database::IDatabase,
|
interface::database::IDatabase,
|
||||||
musichoard::{base::IMusicHoardBasePrivate, Error, MusicHoard, NoDatabase},
|
musichoard::{
|
||||||
|
base::IMusicHoardBasePrivate, Error, IntoId, LibArtist, MusicHoard, NoDatabase,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait IMusicHoardDatabase {
|
pub trait IMusicHoardDatabase {
|
||||||
|
fn merge_collections(&mut self) -> Result<Collection, Error>;
|
||||||
|
|
||||||
fn reload_database(&mut self) -> Result<(), Error>;
|
fn reload_database(&mut self) -> 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 set_artist_mb_ref<Id: AsRef<ArtistId>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
mb_ref: ArtistMbRef,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
fn clear_artist_mb_ref<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
|
||||||
|
|
||||||
fn set_artist_sort<Id: AsRef<ArtistId>, S: Into<String>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
artist_sort: S,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
fn clear_artist_sort<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
|
||||||
|
|
||||||
fn merge_artist_info<Id: AsRef<ArtistId>>(
|
fn merge_artist_info<Id: AsRef<ArtistId>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: Id,
|
artist_id: Id,
|
||||||
@ -44,30 +32,6 @@ pub trait IMusicHoardDatabase {
|
|||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
fn clear_artist_info<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
fn clear_artist_info<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error>;
|
||||||
|
|
||||||
fn add_to_artist_property<Id: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
property: S,
|
|
||||||
values: Vec<S>,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
fn remove_from_artist_property<Id: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
property: S,
|
|
||||||
values: Vec<S>,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
fn set_artist_property<Id: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
property: S,
|
|
||||||
values: Vec<S>,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
fn clear_artist_property<Id: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
property: S,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
|
|
||||||
fn add_album<ArtistIdRef: AsRef<ArtistId>>(
|
fn add_album<ArtistIdRef: AsRef<ArtistId>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: ArtistIdRef,
|
artist_id: ArtistIdRef,
|
||||||
@ -90,17 +54,7 @@ pub trait IMusicHoardDatabase {
|
|||||||
artist_id: ArtistIdRef,
|
artist_id: ArtistIdRef,
|
||||||
album_id: AlbumIdRef,
|
album_id: AlbumIdRef,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
fn set_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ArtistIdRef,
|
|
||||||
album_id: AlbumIdRef,
|
|
||||||
seq: u8,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
fn clear_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ArtistIdRef,
|
|
||||||
album_id: AlbumIdRef,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
fn merge_album_info<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
fn merge_album_info<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: ArtistIdRef,
|
artist_id: ArtistIdRef,
|
||||||
@ -115,80 +69,39 @@ pub trait IMusicHoardDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database, Library> {
|
impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database, Library> {
|
||||||
fn reload_database(&mut self) -> Result<(), Error> {
|
fn merge_collections(&mut self) -> Result<Collection, Error> {
|
||||||
let mut database_cache = self.database.load()?;
|
let mut database = self.database.load()?;
|
||||||
Self::sort_albums_and_tracks(database_cache.iter_mut());
|
Self::sort_albums_and_tracks(database.iter_mut().map(|a| &mut a.albums));
|
||||||
|
|
||||||
self.collection = self.merge_collections(database_cache);
|
let mut primary = NormalMap::<LibArtist>::new();
|
||||||
|
for (normal_name, artist) in self.library_cache.clone().into_iter() {
|
||||||
|
primary.insert(normal_name, artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut secondary = NormalMap::<Artist>::new();
|
||||||
|
for artist in database.into_iter() {
|
||||||
|
secondary.insert(string::normalize_string(&artist.meta.name), artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut collection, lib_artists) = MergeCollections::merge_by_name(primary, secondary);
|
||||||
|
|
||||||
|
for lib_artist in lib_artists.into_iter() {
|
||||||
|
let id = self.database.insert_artist(&lib_artist.meta)?;
|
||||||
|
collection.push(lib_artist.into_id(&id));
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.sort_unstable();
|
||||||
|
|
||||||
|
Ok(collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_database(&mut self) -> Result<(), Error> {
|
||||||
|
self.collection = self.merge_collections()?;
|
||||||
self.filtered = self.filter_collection();
|
self.filtered = self.filter_collection();
|
||||||
|
|
||||||
self.pre_commit = self.collection.clone();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_artist<IntoId: Into<ArtistId>>(&mut self, artist_id: IntoId) -> Result<(), Error> {
|
|
||||||
let artist_id: ArtistId = artist_id.into();
|
|
||||||
|
|
||||||
self.update_collection(|collection| {
|
|
||||||
if Self::get_artist(collection, &artist_id).is_none() {
|
|
||||||
collection.push(Artist::new(artist_id));
|
|
||||||
Self::sort_artists(collection);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_artist<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error> {
|
|
||||||
self.update_collection(|collection| {
|
|
||||||
let index_opt = collection
|
|
||||||
.iter()
|
|
||||||
.position(|a| &a.meta.id == artist_id.as_ref());
|
|
||||||
if let Some(index) = index_opt {
|
|
||||||
collection.remove(index);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_artist_mb_ref<Id: AsRef<ArtistId>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
mb_ref: ArtistMbRef,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_artist_and(
|
|
||||||
artist_id.as_ref(),
|
|
||||||
|artist| artist.meta.set_mb_ref(mb_ref),
|
|
||||||
|collection| Self::sort_artists(collection),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_artist_mb_ref<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error> {
|
|
||||||
self.update_artist_and(
|
|
||||||
artist_id.as_ref(),
|
|
||||||
|artist| artist.meta.clear_mb_ref(),
|
|
||||||
|collection| Self::sort_artists(collection),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_artist_sort<Id: AsRef<ArtistId>, S: Into<String>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
artist_sort: S,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_artist_and(
|
|
||||||
artist_id.as_ref(),
|
|
||||||
|artist| artist.meta.set_sort_key(artist_sort),
|
|
||||||
|collection| Self::sort_artists(collection),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_artist_sort<Id: AsRef<ArtistId>>(&mut self, artist_id: Id) -> Result<(), Error> {
|
|
||||||
self.update_artist_and(
|
|
||||||
artist_id.as_ref(),
|
|
||||||
|artist| artist.meta.clear_sort_key(),
|
|
||||||
|collection| Self::sort_artists(collection),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merge_artist_info<Id: AsRef<ArtistId>>(
|
fn merge_artist_info<Id: AsRef<ArtistId>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: Id,
|
artist_id: Id,
|
||||||
@ -206,49 +119,6 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_to_artist_property<Id: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
property: S,
|
|
||||||
values: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
|
||||||
artist.meta.info.add_to_property(property, values)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_from_artist_property<Id: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
property: S,
|
|
||||||
values: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
|
||||||
artist.meta.info.remove_from_property(property, values)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_artist_property<Id: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
property: S,
|
|
||||||
values: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
|
||||||
artist.meta.info.set_property(property, values)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_artist_property<Id: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: Id,
|
|
||||||
property: S,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_artist(artist_id.as_ref(), |artist| {
|
|
||||||
artist.meta.info.clear_property(property)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_album<ArtistIdRef: AsRef<ArtistId>>(
|
fn add_album<ArtistIdRef: AsRef<ArtistId>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: ArtistIdRef,
|
artist_id: ArtistIdRef,
|
||||||
@ -288,12 +158,9 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
album_id: AlbumIdRef,
|
album_id: AlbumIdRef,
|
||||||
mb_ref: AlbumMbRef,
|
mb_ref: AlbumMbRef,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_album_and(
|
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
|
||||||
artist_id.as_ref(),
|
album.meta.set_mb_ref(mb_ref)
|
||||||
album_id.as_ref(),
|
})
|
||||||
|album| album.meta.set_mb_ref(mb_ref),
|
|
||||||
|artist| artist.albums.sort_unstable(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_album_mb_ref<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
fn clear_album_mb_ref<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||||
@ -301,39 +168,9 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
artist_id: ArtistIdRef,
|
artist_id: ArtistIdRef,
|
||||||
album_id: AlbumIdRef,
|
album_id: AlbumIdRef,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_album_and(
|
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
|
||||||
artist_id.as_ref(),
|
album.meta.clear_mb_ref()
|
||||||
album_id.as_ref(),
|
})
|
||||||
|album| album.meta.clear_mb_ref(),
|
|
||||||
|artist| artist.albums.sort_unstable(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ArtistIdRef,
|
|
||||||
album_id: AlbumIdRef,
|
|
||||||
seq: u8,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_album_and(
|
|
||||||
artist_id.as_ref(),
|
|
||||||
album_id.as_ref(),
|
|
||||||
|album| album.meta.set_seq(AlbumSeq(seq)),
|
|
||||||
|artist| artist.albums.sort_unstable(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_album_seq<ArtistIdRef: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ArtistIdRef,
|
|
||||||
album_id: AlbumIdRef,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.update_album_and(
|
|
||||||
artist_id.as_ref(),
|
|
||||||
album_id.as_ref(),
|
|
||||||
|album| album.meta.clear_seq(),
|
|
||||||
|artist| artist.albums.sort_unstable(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_album_info<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
fn merge_album_info<Id: AsRef<ArtistId>, AlbumIdRef: AsRef<AlbumId>>(
|
||||||
@ -342,7 +179,7 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
album_id: AlbumIdRef,
|
album_id: AlbumIdRef,
|
||||||
mut info: AlbumInfo,
|
mut info: AlbumInfo,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
|
self.update_album_and_sort(artist_id.as_ref(), album_id.as_ref(), |album| {
|
||||||
mem::swap(&mut album.meta.info, &mut info);
|
mem::swap(&mut album.meta.info, &mut info);
|
||||||
album.meta.info.merge_in_place(info);
|
album.meta.info.merge_in_place(info);
|
||||||
})
|
})
|
||||||
@ -353,7 +190,7 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
|
|||||||
artist_id: Id,
|
artist_id: Id,
|
||||||
album_id: AlbumIdRef,
|
album_id: AlbumIdRef,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.update_album(artist_id.as_ref(), album_id.as_ref(), |album| {
|
self.update_album_and_sort(artist_id.as_ref(), album_id.as_ref(), |album| {
|
||||||
album.meta.info = AlbumInfo::default()
|
album.meta.info = AlbumInfo::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -365,7 +202,6 @@ pub trait IMusicHoardDatabasePrivate {
|
|||||||
|
|
||||||
impl<Library> IMusicHoardDatabasePrivate for MusicHoard<NoDatabase, Library> {
|
impl<Library> IMusicHoardDatabasePrivate for MusicHoard<NoDatabase, Library> {
|
||||||
fn commit(&mut self) -> Result<(), Error> {
|
fn commit(&mut self) -> Result<(), Error> {
|
||||||
self.collection = self.pre_commit.clone();
|
|
||||||
self.filtered = self.filter_collection();
|
self.filtered = self.filter_collection();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -373,14 +209,11 @@ impl<Library> IMusicHoardDatabasePrivate for MusicHoard<NoDatabase, Library> {
|
|||||||
|
|
||||||
impl<Database: IDatabase, Library> IMusicHoardDatabasePrivate for MusicHoard<Database, Library> {
|
impl<Database: IDatabase, Library> IMusicHoardDatabasePrivate for MusicHoard<Database, Library> {
|
||||||
fn commit(&mut self) -> Result<(), Error> {
|
fn commit(&mut self) -> Result<(), Error> {
|
||||||
if self.collection != self.pre_commit {
|
if let Err(err) = self.database.save(&self.collection) {
|
||||||
if let Err(err) = self.database.save(&self.pre_commit) {
|
self.reload_database()?;
|
||||||
self.pre_commit = self.collection.clone();
|
|
||||||
return Err(err.into());
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
self.collection = self.pre_commit.clone();
|
|
||||||
self.filtered = self.filter_collection();
|
self.filtered = self.filter_collection();
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -390,7 +223,7 @@ impl<Database: IDatabase, Library> MusicHoard<Database, Library> {
|
|||||||
where
|
where
|
||||||
FnColl: FnOnce(&mut Collection),
|
FnColl: FnOnce(&mut Collection),
|
||||||
{
|
{
|
||||||
fn_coll(&mut self.pre_commit);
|
fn_coll(&mut self.collection);
|
||||||
self.commit()
|
self.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +237,7 @@ impl<Database: IDatabase, Library> MusicHoard<Database, Library> {
|
|||||||
FnArtist: FnOnce(&mut Artist),
|
FnArtist: FnOnce(&mut Artist),
|
||||||
FnColl: FnOnce(&mut Collection),
|
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.collection, artist_id)?;
|
||||||
fn_artist(artist);
|
fn_artist(artist);
|
||||||
self.update_collection(fn_coll)
|
self.update_collection(fn_coll)
|
||||||
}
|
}
|
||||||
@ -431,13 +264,27 @@ impl<Database: IDatabase, Library> MusicHoard<Database, Library> {
|
|||||||
FnAlbum: FnOnce(&mut Album),
|
FnAlbum: FnOnce(&mut Album),
|
||||||
FnArtist: FnOnce(&mut Artist),
|
FnArtist: FnOnce(&mut Artist),
|
||||||
{
|
{
|
||||||
let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id)?;
|
let artist = Self::get_artist_mut_or_err(&mut self.collection, 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(|_| {})
|
self.update_collection(|_| {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_album_and_sort<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, |artist| {
|
||||||
|
artist.albums.sort_unstable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn update_album<FnAlbum>(
|
fn update_album<FnAlbum>(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist_id: &ArtistId,
|
artist_id: &ArtistId,
|
||||||
@ -458,12 +305,13 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{AlbumPrimaryType, AlbumSecondaryType},
|
album::{AlbumPrimaryType, AlbumSecondaryType},
|
||||||
|
artist::ArtistMbRef,
|
||||||
musicbrainz::MbArtistRef,
|
musicbrainz::MbArtistRef,
|
||||||
},
|
},
|
||||||
core::{
|
core::{
|
||||||
collection::artist::ArtistId,
|
collection::artist::ArtistId,
|
||||||
interface::database::{self, MockIDatabase},
|
interface::database::{self, MockIDatabase},
|
||||||
musichoard::{base::IMusicHoardBase, NoLibrary},
|
musichoard::base::IMusicHoardBase,
|
||||||
testmod::FULL_COLLECTION,
|
testmod::FULL_COLLECTION,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -471,113 +319,13 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
static MBID: &str = "d368baa8-21ca-4759-9731-0b2753071ad8";
|
static MBID: &str = "d368baa8-21ca-4759-9731-0b2753071ad8";
|
||||||
static MUSICBUTLER: &str = "https://www.musicbutler.io/artist-page/483340948";
|
|
||||||
static MUSICBUTLER_2: &str = "https://www.musicbutler.io/artist-page/658903042/";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn artist_new_delete() {
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
|
||||||
|
|
||||||
let collection = FULL_COLLECTION.to_owned();
|
|
||||||
let mut with_artist = collection.clone();
|
|
||||||
with_artist.push(Artist::new(artist_id.clone()));
|
|
||||||
|
|
||||||
let mut database = MockIDatabase::new();
|
|
||||||
let mut seq = Sequence::new();
|
|
||||||
database
|
|
||||||
.expect_load()
|
|
||||||
.times(1)
|
|
||||||
.times(1)
|
|
||||||
.in_sequence(&mut seq)
|
|
||||||
.returning(|| Ok(FULL_COLLECTION.to_owned()));
|
|
||||||
database
|
|
||||||
.expect_save()
|
|
||||||
.times(1)
|
|
||||||
.in_sequence(&mut seq)
|
|
||||||
.with(predicate::eq(with_artist.clone()))
|
|
||||||
.returning(|_| Ok(()));
|
|
||||||
database
|
|
||||||
.expect_save()
|
|
||||||
.times(1)
|
|
||||||
.in_sequence(&mut seq)
|
|
||||||
.with(predicate::eq(collection.clone()))
|
|
||||||
.returning(|_| Ok(()));
|
|
||||||
|
|
||||||
let mut music_hoard = MusicHoard::database(database);
|
|
||||||
music_hoard.reload_database().unwrap();
|
|
||||||
assert_eq!(music_hoard.collection, collection);
|
|
||||||
|
|
||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
|
||||||
assert_eq!(music_hoard.collection, with_artist);
|
|
||||||
|
|
||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
|
||||||
assert_eq!(music_hoard.collection, with_artist);
|
|
||||||
|
|
||||||
assert!(music_hoard.remove_artist(&artist_id_2).is_ok());
|
|
||||||
assert_eq!(music_hoard.collection, with_artist);
|
|
||||||
|
|
||||||
assert!(music_hoard.remove_artist(&artist_id).is_ok());
|
|
||||||
assert_eq!(music_hoard.collection, collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn artist_sort_set_clear() {
|
|
||||||
let mut database = MockIDatabase::new();
|
|
||||||
database.expect_save().times(4).returning(|_| Ok(()));
|
|
||||||
|
|
||||||
type MH = MusicHoard<MockIDatabase, NoLibrary>;
|
|
||||||
let mut music_hoard: MH = MusicHoard::database(database);
|
|
||||||
|
|
||||||
let artist_1_id = ArtistId::new("the artist");
|
|
||||||
let artist_1_sort = String::from("artist, the");
|
|
||||||
|
|
||||||
// Must be after "artist, the", but before "the artist"
|
|
||||||
let artist_2_id = ArtistId::new("b-artist");
|
|
||||||
|
|
||||||
assert!(artist_1_sort < artist_2_id.name);
|
|
||||||
assert!(artist_2_id < artist_1_id);
|
|
||||||
|
|
||||||
assert!(music_hoard.add_artist(artist_1_id.clone()).is_ok());
|
|
||||||
assert!(music_hoard.add_artist(artist_2_id.clone()).is_ok());
|
|
||||||
|
|
||||||
let artist_1: &Artist = MH::get_artist(&music_hoard.collection, &artist_1_id).unwrap();
|
|
||||||
let artist_2: &Artist = MH::get_artist(&music_hoard.collection, &artist_2_id).unwrap();
|
|
||||||
|
|
||||||
assert!(artist_2 < artist_1);
|
|
||||||
|
|
||||||
assert_eq!(artist_1, &music_hoard.collection[1]);
|
|
||||||
assert_eq!(artist_2, &music_hoard.collection[0]);
|
|
||||||
|
|
||||||
music_hoard
|
|
||||||
.set_artist_sort(artist_1_id.as_ref(), artist_1_sort.clone())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let artist_1: &Artist = MH::get_artist(&music_hoard.collection, &artist_1_id).unwrap();
|
|
||||||
let artist_2: &Artist = MH::get_artist(&music_hoard.collection, &artist_2_id).unwrap();
|
|
||||||
|
|
||||||
assert!(artist_1 < artist_2);
|
|
||||||
|
|
||||||
assert_eq!(artist_1, &music_hoard.collection[0]);
|
|
||||||
assert_eq!(artist_2, &music_hoard.collection[1]);
|
|
||||||
|
|
||||||
music_hoard.clear_artist_sort(artist_1_id.as_ref()).unwrap();
|
|
||||||
|
|
||||||
let artist_1: &Artist = MH::get_artist(&music_hoard.collection, &artist_1_id).unwrap();
|
|
||||||
let artist_2: &Artist = MH::get_artist(&music_hoard.collection, &artist_2_id).unwrap();
|
|
||||||
|
|
||||||
assert!(artist_2 < artist_1);
|
|
||||||
|
|
||||||
assert_eq!(artist_1, &music_hoard.collection[1]);
|
|
||||||
assert_eq!(artist_2, &music_hoard.collection[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn collection_error() {
|
fn collection_error() {
|
||||||
let database = MockIDatabase::new();
|
let database = MockIDatabase::new();
|
||||||
let mut music_hoard = MusicHoard::database(database);
|
let mut music_hoard = MusicHoard::database(database);
|
||||||
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist_id = ArtistId(1);
|
||||||
let actual_err = music_hoard
|
let actual_err = music_hoard
|
||||||
.merge_artist_info(&artist_id, ArtistInfo::default())
|
.merge_artist_info(&artist_id, ArtistInfo::default())
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
@ -587,78 +335,36 @@ mod tests {
|
|||||||
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_clear_artist_mb_ref() {
|
|
||||||
let mut database = MockIDatabase::new();
|
|
||||||
database.expect_save().times(3).returning(|_| Ok(()));
|
|
||||||
|
|
||||||
let mut artist_id = ArtistId::new("an artist");
|
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
|
||||||
let mut music_hoard = MusicHoard::database(database);
|
|
||||||
|
|
||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
|
||||||
|
|
||||||
let mut expected = ArtistMbRef::None;
|
|
||||||
assert_eq!(music_hoard.collection[0].meta.id.mb_ref, expected);
|
|
||||||
|
|
||||||
let mb_ref = ArtistMbRef::Some(MbArtistRef::from_uuid_str(MBID).unwrap());
|
|
||||||
|
|
||||||
// Setting a mb_ref on an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.set_artist_mb_ref(&artist_id_2, mb_ref.clone())
|
|
||||||
.is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].meta.id.mb_ref, expected);
|
|
||||||
|
|
||||||
// Setting a mb_ref on an artist.
|
|
||||||
assert!(music_hoard
|
|
||||||
.set_artist_mb_ref(&artist_id, mb_ref.clone())
|
|
||||||
.is_ok());
|
|
||||||
expected.replace(MbArtistRef::from_uuid_str(MBID).unwrap());
|
|
||||||
assert_eq!(music_hoard.collection[0].meta.id.mb_ref, expected);
|
|
||||||
|
|
||||||
// Clearing mb_ref on an artist that does not exist is an error.
|
|
||||||
assert!(music_hoard.clear_artist_mb_ref(&artist_id_2).is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].meta.id.mb_ref, expected);
|
|
||||||
|
|
||||||
// Clearing mb_ref from an artist without the mb_ref set is an error. Effectively the album
|
|
||||||
// does not exist.
|
|
||||||
assert!(music_hoard.clear_artist_mb_ref(&artist_id).is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].meta.id.mb_ref, expected);
|
|
||||||
|
|
||||||
// Clearing mb_ref.
|
|
||||||
artist_id.set_mb_ref(mb_ref);
|
|
||||||
assert!(music_hoard.clear_artist_mb_ref(&artist_id).is_ok());
|
|
||||||
expected.take();
|
|
||||||
assert_eq!(music_hoard.collection[0].meta.id.mb_ref, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_artist_info() {
|
fn set_clear_artist_info() {
|
||||||
let mut database = MockIDatabase::new();
|
let mut database = MockIDatabase::new();
|
||||||
database.expect_save().times(3).returning(|_| Ok(()));
|
database.expect_save().times(2).returning(|_| Ok(()));
|
||||||
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist = Artist::new(1, "an artist");
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
let artist_2 = Artist::new(2, "another artist");
|
||||||
|
let mb_ref = ArtistMbRef::Some(MbArtistRef::from_uuid_str(MBID).unwrap());
|
||||||
let mut music_hoard = MusicHoard::database(database);
|
let mut music_hoard = MusicHoard::database(database);
|
||||||
|
|
||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
music_hoard.collection.push(artist.clone());
|
||||||
|
music_hoard.collection.sort_unstable();
|
||||||
|
|
||||||
let mut expected = ArtistInfo::default();
|
let mut expected = ArtistInfo::default();
|
||||||
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
||||||
|
|
||||||
let mut info = ArtistInfo::default();
|
let mut info = ArtistInfo::default().with_mb_ref(mb_ref.clone());
|
||||||
info.add_to_property("property", vec!["value-1", "value-2"]);
|
info.add_to_property("property", vec!["value-1", "value-2"]);
|
||||||
|
|
||||||
// Setting info on an artist not in the collection is an error.
|
// Setting info on an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.merge_artist_info(&artist_id_2, info.clone())
|
.merge_artist_info(&artist_2.id, info.clone())
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
||||||
|
|
||||||
// Setting info on an artist.
|
// Setting info on an artist.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.merge_artist_info(&artist_id, info.clone())
|
.merge_artist_info(&artist.id, info.clone())
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
expected.mb_ref = mb_ref.clone();
|
||||||
expected.properties.insert(
|
expected.properties.insert(
|
||||||
String::from("property"),
|
String::from("property"),
|
||||||
vec![String::from("value-1"), String::from("value-2")],
|
vec![String::from("value-1"), String::from("value-2")],
|
||||||
@ -666,106 +372,16 @@ mod tests {
|
|||||||
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
||||||
|
|
||||||
// Clearing info on an artist that does not exist is an error.
|
// Clearing info on an artist that does not exist is an error.
|
||||||
assert!(music_hoard.clear_artist_info(&artist_id_2).is_err());
|
assert!(music_hoard.clear_artist_info(&artist_2.id).is_err());
|
||||||
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
||||||
|
|
||||||
// Clearing info.
|
// Clearing info.
|
||||||
assert!(music_hoard.clear_artist_info(&artist_id).is_ok());
|
assert!(music_hoard.clear_artist_info(&artist.id).is_ok());
|
||||||
|
expected.mb_ref.take();
|
||||||
expected.properties.clear();
|
expected.properties.clear();
|
||||||
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
assert_eq!(music_hoard.collection[0].meta.info, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_to_remove_from_property() {
|
|
||||||
let mut database = MockIDatabase::new();
|
|
||||||
database.expect_save().times(3).returning(|_| Ok(()));
|
|
||||||
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
|
||||||
let mut music_hoard = MusicHoard::database(database);
|
|
||||||
|
|
||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
|
||||||
|
|
||||||
let mut expected: Vec<String> = vec![];
|
|
||||||
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
|
||||||
|
|
||||||
// Adding URLs to an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.add_to_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
|
||||||
.is_err());
|
|
||||||
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
|
||||||
|
|
||||||
// Adding mutliple URLs without clashes.
|
|
||||||
assert!(music_hoard
|
|
||||||
.add_to_artist_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
|
||||||
let info = &music_hoard.collection[0].meta.info;
|
|
||||||
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
|
||||||
|
|
||||||
// Removing URLs from an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.remove_from_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
|
||||||
.is_err());
|
|
||||||
let info = &music_hoard.collection[0].meta.info;
|
|
||||||
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
|
||||||
|
|
||||||
// Removing multiple URLs without clashes.
|
|
||||||
assert!(music_hoard
|
|
||||||
.remove_from_artist_property(
|
|
||||||
&artist_id,
|
|
||||||
"MusicButler",
|
|
||||||
vec![MUSICBUTLER, MUSICBUTLER_2]
|
|
||||||
)
|
|
||||||
.is_ok());
|
|
||||||
expected.clear();
|
|
||||||
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_clear_property() {
|
|
||||||
let mut database = MockIDatabase::new();
|
|
||||||
database.expect_save().times(3).returning(|_| Ok(()));
|
|
||||||
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
|
||||||
let mut music_hoard = MusicHoard::database(database);
|
|
||||||
|
|
||||||
assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
|
|
||||||
|
|
||||||
let mut expected: Vec<String> = vec![];
|
|
||||||
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
|
||||||
|
|
||||||
// Seting URL on an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.set_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
|
||||||
.is_err());
|
|
||||||
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
|
||||||
|
|
||||||
// Set URLs.
|
|
||||||
assert!(music_hoard
|
|
||||||
.set_artist_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.clear();
|
|
||||||
expected.push(MUSICBUTLER.to_owned());
|
|
||||||
expected.push(MUSICBUTLER_2.to_owned());
|
|
||||||
let info = &music_hoard.collection[0].meta.info;
|
|
||||||
assert_eq!(info.properties.get("MusicButler"), Some(&expected));
|
|
||||||
|
|
||||||
// Clearing URLs on an artist that does not exist is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.clear_artist_property(&artist_id_2, "MusicButler")
|
|
||||||
.is_err());
|
|
||||||
|
|
||||||
// Clear URLs.
|
|
||||||
assert!(music_hoard
|
|
||||||
.clear_artist_property(&artist_id, "MusicButler")
|
|
||||||
.is_ok());
|
|
||||||
expected.clear();
|
|
||||||
assert!(music_hoard.collection[0].meta.info.properties.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn album_new_delete() {
|
fn album_new_delete() {
|
||||||
let album_id = AlbumId::new("an album");
|
let album_id = AlbumId::new("an album");
|
||||||
@ -774,7 +390,7 @@ mod tests {
|
|||||||
let album_meta_2 = AlbumMeta::new(album_id_2);
|
let album_meta_2 = AlbumMeta::new(album_id_2);
|
||||||
|
|
||||||
let collection = FULL_COLLECTION.to_owned();
|
let collection = FULL_COLLECTION.to_owned();
|
||||||
let artist_id = collection[0].meta.id.clone();
|
let artist_id = collection[0].id;
|
||||||
let mut with_album = collection.clone();
|
let mut with_album = collection.clone();
|
||||||
with_album[0].albums.push(Album::new(album_id));
|
with_album[0].albums.push(Album::new(album_id));
|
||||||
with_album[0].albums.sort_unstable();
|
with_album[0].albums.sort_unstable();
|
||||||
@ -789,7 +405,7 @@ mod tests {
|
|||||||
.returning(|| Ok(FULL_COLLECTION.to_owned()));
|
.returning(|| Ok(FULL_COLLECTION.to_owned()));
|
||||||
database
|
database
|
||||||
.expect_save()
|
.expect_save()
|
||||||
.times(1)
|
.times(3)
|
||||||
.in_sequence(&mut seq)
|
.in_sequence(&mut seq)
|
||||||
.with(predicate::eq(with_album.clone()))
|
.with(predicate::eq(with_album.clone()))
|
||||||
.returning(|_| Ok(()));
|
.returning(|_| Ok(()));
|
||||||
@ -827,11 +443,11 @@ mod tests {
|
|||||||
fn set_clear_album_mb_ref() {
|
fn set_clear_album_mb_ref() {
|
||||||
let mut database = MockIDatabase::new();
|
let mut database = MockIDatabase::new();
|
||||||
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist = Artist::new(1, "an artist");
|
||||||
let mut album_id = AlbumId::new("an album");
|
let mut album_id = AlbumId::new("an album");
|
||||||
let album_id_2 = AlbumId::new("another album");
|
let album_id_2 = AlbumId::new("another album");
|
||||||
|
|
||||||
let mut database_result = vec![Artist::new(artist_id.clone())];
|
let mut database_result = vec![artist.clone()];
|
||||||
database_result[0].albums.push(Album::new(album_id.clone()));
|
database_result[0].albums.push(Album::new(album_id.clone()));
|
||||||
|
|
||||||
database
|
database
|
||||||
@ -847,27 +463,27 @@ mod tests {
|
|||||||
|
|
||||||
// Seting mb_ref on an album not belonging to the artist is an error.
|
// Seting mb_ref on an album not belonging to the artist is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_album_mb_ref(&artist_id, &album_id_2, AlbumMbRef::CannotHaveMbid)
|
.set_album_mb_ref(&artist.id, &album_id_2, AlbumMbRef::CannotHaveMbid)
|
||||||
.is_err());
|
.is_err());
|
||||||
let album = &music_hoard.collection[0].albums[0];
|
let album = &music_hoard.collection[0].albums[0];
|
||||||
assert_eq!(album.meta.id.mb_ref, AlbumMbRef::None);
|
assert_eq!(album.meta.id.mb_ref, AlbumMbRef::None);
|
||||||
|
|
||||||
// Set mb_ref.
|
// Set mb_ref.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_album_mb_ref(&artist_id, &album_id, AlbumMbRef::CannotHaveMbid)
|
.set_album_mb_ref(&artist.id, &album_id, AlbumMbRef::CannotHaveMbid)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
let album = &music_hoard.collection[0].albums[0];
|
let album = &music_hoard.collection[0].albums[0];
|
||||||
assert_eq!(album.meta.id.mb_ref, AlbumMbRef::CannotHaveMbid);
|
assert_eq!(album.meta.id.mb_ref, AlbumMbRef::CannotHaveMbid);
|
||||||
|
|
||||||
// Clearing mb_ref on an album that does not exist is an error.
|
// Clearing mb_ref on an album that does not exist is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.clear_album_mb_ref(&artist_id, &album_id_2)
|
.clear_album_mb_ref(&artist.id, &album_id_2)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
// Clearing mb_ref from an album without the mb_ref set is an error. Effectively the album
|
// Clearing mb_ref from an album without the mb_ref set is an error. Effectively the album
|
||||||
// does not exist.
|
// does not exist.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.clear_album_mb_ref(&artist_id, &album_id)
|
.clear_album_mb_ref(&artist.id, &album_id)
|
||||||
.is_err());
|
.is_err());
|
||||||
let album = &music_hoard.collection[0].albums[0];
|
let album = &music_hoard.collection[0].albums[0];
|
||||||
assert_eq!(album.meta.id.mb_ref, AlbumMbRef::CannotHaveMbid);
|
assert_eq!(album.meta.id.mb_ref, AlbumMbRef::CannotHaveMbid);
|
||||||
@ -876,62 +492,21 @@ mod tests {
|
|||||||
// album.
|
// album.
|
||||||
album_id.set_mb_ref(AlbumMbRef::CannotHaveMbid);
|
album_id.set_mb_ref(AlbumMbRef::CannotHaveMbid);
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.clear_album_mb_ref(&artist_id, &album_id)
|
.clear_album_mb_ref(&artist.id, &album_id)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
let album = &music_hoard.collection[0].albums[0];
|
let album = &music_hoard.collection[0].albums[0];
|
||||||
assert_eq!(album.meta.id.mb_ref, AlbumMbRef::None);
|
assert_eq!(album.meta.id.mb_ref, AlbumMbRef::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_clear_album_seq() {
|
|
||||||
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()));
|
|
||||||
|
|
||||||
database
|
|
||||||
.expect_load()
|
|
||||||
.times(1)
|
|
||||||
.return_once(|| Ok(database_result));
|
|
||||||
database.expect_save().times(2).returning(|_| Ok(()));
|
|
||||||
|
|
||||||
let mut music_hoard = MusicHoard::database(database);
|
|
||||||
music_hoard.reload_database().unwrap();
|
|
||||||
assert_eq!(music_hoard.collection[0].albums[0].meta.seq, AlbumSeq(0));
|
|
||||||
|
|
||||||
// Seting seq on an album not belonging to the artist is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.set_album_seq(&artist_id, &album_id_2, 6)
|
|
||||||
.is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].albums[0].meta.seq, AlbumSeq(0));
|
|
||||||
|
|
||||||
// Set seq.
|
|
||||||
assert!(music_hoard.set_album_seq(&artist_id, &album_id, 6).is_ok());
|
|
||||||
assert_eq!(music_hoard.collection[0].albums[0].meta.seq, AlbumSeq(6));
|
|
||||||
|
|
||||||
// Clearing seq on an album that does not exist is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.clear_album_seq(&artist_id, &album_id_2)
|
|
||||||
.is_err());
|
|
||||||
|
|
||||||
// Clear seq.
|
|
||||||
assert!(music_hoard.clear_album_seq(&artist_id, &album_id).is_ok());
|
|
||||||
assert_eq!(music_hoard.collection[0].albums[0].meta.seq, AlbumSeq(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_album_info() {
|
fn set_clear_album_info() {
|
||||||
let mut database = MockIDatabase::new();
|
let mut database = MockIDatabase::new();
|
||||||
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist = Artist::new(1, "an artist");
|
||||||
let album_id = AlbumId::new("an album");
|
let album_id = AlbumId::new("an album");
|
||||||
let album_id_2 = AlbumId::new("another album");
|
let album_id_2 = AlbumId::new("another album");
|
||||||
|
|
||||||
let mut database_result = vec![Artist::new(artist_id.clone())];
|
let mut database_result = vec![artist.clone()];
|
||||||
database_result[0].albums.push(Album::new(album_id.clone()));
|
database_result[0].albums.push(Album::new(album_id.clone()));
|
||||||
|
|
||||||
database
|
database
|
||||||
@ -946,32 +521,31 @@ mod tests {
|
|||||||
assert_eq!(meta.info.primary_type, None);
|
assert_eq!(meta.info.primary_type, None);
|
||||||
assert_eq!(meta.info.secondary_types, Vec::new());
|
assert_eq!(meta.info.secondary_types, Vec::new());
|
||||||
|
|
||||||
let info = AlbumInfo::new(
|
let info = AlbumInfo::default()
|
||||||
Some(AlbumPrimaryType::Album),
|
.with_primary_type(AlbumPrimaryType::Album)
|
||||||
vec![AlbumSecondaryType::Live],
|
.with_secondary_types(vec![AlbumSecondaryType::Live]);
|
||||||
);
|
|
||||||
|
|
||||||
// Seting info on an album not belonging to the artist is an error.
|
// Seting info on an album not belonging to the artist is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.merge_album_info(&artist_id, &album_id_2, info.clone())
|
.merge_album_info(&artist.id, &album_id_2, info.clone())
|
||||||
.is_err());
|
.is_err());
|
||||||
let meta = &music_hoard.collection[0].albums[0].meta;
|
let meta = &music_hoard.collection[0].albums[0].meta;
|
||||||
assert_eq!(meta.info, AlbumInfo::default());
|
assert_eq!(meta.info, AlbumInfo::default());
|
||||||
|
|
||||||
// Set info.
|
// Set info.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.merge_album_info(&artist_id, &album_id, info.clone())
|
.merge_album_info(&artist.id, &album_id, info.clone())
|
||||||
.is_ok());
|
.is_ok());
|
||||||
let meta = &music_hoard.collection[0].albums[0].meta;
|
let meta = &music_hoard.collection[0].albums[0].meta;
|
||||||
assert_eq!(meta.info, info);
|
assert_eq!(meta.info, info);
|
||||||
|
|
||||||
// Clearing info on an album that does not exist is an error.
|
// Clearing info on an album that does not exist is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.clear_album_info(&artist_id, &album_id_2)
|
.clear_album_info(&artist.id, &album_id_2)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
// Clear info.
|
// Clear info.
|
||||||
assert!(music_hoard.clear_album_info(&artist_id, &album_id).is_ok());
|
assert!(music_hoard.clear_album_info(&artist.id, &album_id).is_ok());
|
||||||
let meta = &music_hoard.collection[0].albums[0].meta;
|
let meta = &music_hoard.collection[0].albums[0].meta;
|
||||||
assert_eq!(meta.info, AlbumInfo::default());
|
assert_eq!(meta.info, AlbumInfo::default());
|
||||||
}
|
}
|
||||||
@ -1018,17 +592,30 @@ mod tests {
|
|||||||
|
|
||||||
let database_result = Err(database::SaveError::IoError(String::from("I/O error")));
|
let database_result = Err(database::SaveError::IoError(String::from("I/O error")));
|
||||||
|
|
||||||
database.expect_load().return_once(|| Ok(vec![]));
|
let mut seq = Sequence::new();
|
||||||
|
database
|
||||||
|
.expect_load()
|
||||||
|
.times(1)
|
||||||
|
.in_sequence(&mut seq)
|
||||||
|
.return_once(|| Ok(vec![]));
|
||||||
database
|
database
|
||||||
.expect_save()
|
.expect_save()
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_: &Collection| database_result);
|
.return_once(|_: &Collection| database_result);
|
||||||
|
database
|
||||||
|
.expect_load()
|
||||||
|
.times(1)
|
||||||
|
.in_sequence(&mut seq)
|
||||||
|
.return_once(|| Ok(vec![]));
|
||||||
|
|
||||||
let mut music_hoard = MusicHoard::database(database);
|
let mut music_hoard = MusicHoard::database(database);
|
||||||
music_hoard.reload_database().unwrap();
|
music_hoard.reload_database().unwrap();
|
||||||
|
|
||||||
|
let artist = Artist::new(1, "an artist");
|
||||||
|
music_hoard.collection.push(artist.clone());
|
||||||
|
|
||||||
let actual_err = music_hoard
|
let actual_err = music_hoard
|
||||||
.add_artist(ArtistId::new("an artist"))
|
.add_album(artist.id, AlbumMeta::new("an album"))
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
let expected_err = Error::DatabaseError(
|
let expected_err = Error::DatabaseError(
|
||||||
database::SaveError::IoError(String::from("I/O error")).to_string(),
|
database::SaveError::IoError(String::from("I/O error")).to_string(),
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::core::{
|
use crate::{
|
||||||
|
collection::{
|
||||||
|
artist::ArtistId,
|
||||||
|
merge::IntoId,
|
||||||
|
string::{self, NormalString},
|
||||||
|
},
|
||||||
|
core::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{Album, AlbumDate, AlbumId, AlbumMbRef},
|
album::{Album, AlbumDate, AlbumId, AlbumMbRef},
|
||||||
artist::{Artist, ArtistId, ArtistMbRef},
|
|
||||||
track::{Track, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackId, TrackNum, TrackQuality},
|
||||||
Collection,
|
|
||||||
},
|
},
|
||||||
interface::{
|
interface::{
|
||||||
database::IDatabase,
|
database::IDatabase,
|
||||||
library::{ILibrary, Item, Query},
|
library::{ILibrary, Item, Query},
|
||||||
},
|
},
|
||||||
musichoard::{
|
musichoard::{
|
||||||
base::IMusicHoardBasePrivate, database::IMusicHoardDatabasePrivate, Error, MusicHoard,
|
base::IMusicHoardBasePrivate,
|
||||||
NoDatabase,
|
database::{IMusicHoardDatabase, IMusicHoardDatabasePrivate},
|
||||||
|
Error, LibArtist, MusicHoard, NoDatabase,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,40 +29,42 @@ pub trait IMusicHoardLibrary {
|
|||||||
|
|
||||||
impl<Library: ILibrary> IMusicHoardLibrary for MusicHoard<NoDatabase, Library> {
|
impl<Library: ILibrary> IMusicHoardLibrary for MusicHoard<NoDatabase, Library> {
|
||||||
fn rescan_library(&mut self) -> Result<(), Error> {
|
fn rescan_library(&mut self) -> Result<(), Error> {
|
||||||
self.pre_commit = self.rescan_library_inner(vec![])?;
|
self.rescan_library_inner()?;
|
||||||
|
self.collection = self
|
||||||
|
.library_cache
|
||||||
|
.values()
|
||||||
|
.cloned()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ix, la)| la.into_id(&ArtistId(ix)))
|
||||||
|
.collect();
|
||||||
|
self.collection.sort_unstable();
|
||||||
self.commit()
|
self.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Database: IDatabase, Library: ILibrary> IMusicHoardLibrary for MusicHoard<Database, Library> {
|
impl<Database: IDatabase, Library: ILibrary> IMusicHoardLibrary for MusicHoard<Database, Library> {
|
||||||
fn rescan_library(&mut self) -> Result<(), Error> {
|
fn rescan_library(&mut self) -> Result<(), Error> {
|
||||||
let mut database_cache = self.database.load()?;
|
self.rescan_library_inner()?;
|
||||||
Self::sort_albums_and_tracks(database_cache.iter_mut());
|
self.collection = self.merge_collections()?;
|
||||||
|
|
||||||
self.pre_commit = self.rescan_library_inner(database_cache)?;
|
|
||||||
self.commit()
|
self.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Database, Library: ILibrary> MusicHoard<Database, Library> {
|
impl<Database, Library: ILibrary> MusicHoard<Database, Library> {
|
||||||
fn rescan_library_inner(&mut self, database: Collection) -> Result<Collection, Error> {
|
fn rescan_library_inner(&mut self) -> Result<(), Error> {
|
||||||
let items = self.library.list(&Query::new())?;
|
let items = self.library.list(&Query::new())?;
|
||||||
self.library_cache = Self::items_to_artists(items)?;
|
self.library_cache = Self::items_to_artists(items)?;
|
||||||
Self::sort_albums_and_tracks(self.library_cache.iter_mut());
|
Self::sort_albums_and_tracks(self.library_cache.values_mut().map(|la| &mut la.albums));
|
||||||
|
Ok(())
|
||||||
Ok(self.merge_collections(database))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn items_to_artists(items: Vec<Item>) -> Result<Collection, Error> {
|
fn items_to_artists(items: Vec<Item>) -> Result<HashMap<NormalString, LibArtist>, Error> {
|
||||||
let mut collection = HashMap::<ArtistId, Artist>::new();
|
let mut collection = HashMap::<NormalString, LibArtist>::new();
|
||||||
|
|
||||||
for item in items.into_iter() {
|
for item in items.into_iter() {
|
||||||
let artist_id = ArtistId {
|
let artist_name_official = item.album_artist;
|
||||||
name: item.album_artist,
|
let artist_name_sort = item.album_artist_sort;
|
||||||
mb_ref: ArtistMbRef::None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let artist_sort = item.album_artist_sort;
|
|
||||||
|
|
||||||
let album_id = AlbumId {
|
let album_id = AlbumId {
|
||||||
title: item.album_title,
|
title: item.album_title,
|
||||||
@ -85,24 +93,25 @@ impl<Database, Library: ILibrary> MusicHoard<Database, Library> {
|
|||||||
// There are usually many entries per artist. Therefore, we avoid simply calling
|
// There are usually many entries per artist. Therefore, we avoid simply calling
|
||||||
// .entry(artist_id.clone()).or_insert_with(..), because of the clone. The flipside is
|
// .entry(artist_id.clone()).or_insert_with(..), because of the clone. The flipside is
|
||||||
// that insertions will thus do an additional lookup.
|
// that insertions will thus do an additional lookup.
|
||||||
let artist = match collection.get_mut(&artist_id) {
|
let normal_name = string::normalize_string(&artist_name_official);
|
||||||
|
let artist = match collection.get_mut(&normal_name) {
|
||||||
Some(artist) => artist,
|
Some(artist) => artist,
|
||||||
None => collection
|
None => collection
|
||||||
.entry(artist_id.clone())
|
.entry(normal_name)
|
||||||
.or_insert_with(|| Artist::new(artist_id)),
|
.or_insert_with(|| LibArtist::new(artist_name_official)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if artist.meta.sort.is_some() {
|
if artist.meta.sort.is_some() {
|
||||||
if artist_sort.is_some() && (artist.meta.sort != artist_sort) {
|
if artist_name_sort.is_some() && (artist.meta.sort != artist_name_sort) {
|
||||||
return Err(Error::CollectionError(format!(
|
return Err(Error::CollectionError(format!(
|
||||||
"multiple album_artist_sort found for artist '{}': '{}' != '{}'",
|
"multiple album_artist_sort found for artist '{}': '{}' != '{}'",
|
||||||
artist.meta.id,
|
artist.meta.name,
|
||||||
artist.meta.sort.as_ref().unwrap(),
|
artist.meta.sort.as_ref().unwrap(),
|
||||||
artist_sort.as_ref().unwrap()
|
artist_name_sort.as_ref().unwrap()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
} else if artist_sort.is_some() {
|
} else if artist_name_sort.is_some() {
|
||||||
artist.meta.sort = artist_sort;
|
artist.meta.sort = artist_name_sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do a linear search as few artists have more than a handful of albums. Search from the
|
// Do a linear search as few artists have more than a handful of albums. Search from the
|
||||||
@ -122,7 +131,7 @@ impl<Database, Library: ILibrary> MusicHoard<Database, Library> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(collection.into_values().collect())
|
Ok(collection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,10 +12,19 @@ pub use database::IMusicHoardDatabase;
|
|||||||
pub use filter::CollectionFilter;
|
pub use filter::CollectionFilter;
|
||||||
pub use library::IMusicHoardLibrary;
|
pub use library::IMusicHoardLibrary;
|
||||||
|
|
||||||
use std::fmt::{self, Display};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt::{self, Display},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
collection::Collection,
|
collection::{
|
||||||
|
album::Album,
|
||||||
|
artist::{Artist, ArtistId, ArtistMeta, ArtistName},
|
||||||
|
merge::IntoId,
|
||||||
|
string::NormalString,
|
||||||
|
Collection,
|
||||||
|
},
|
||||||
interface::{
|
interface::{
|
||||||
database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError},
|
database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError},
|
||||||
library::Error as LibraryError,
|
library::Error as LibraryError,
|
||||||
@ -24,16 +33,42 @@ use crate::core::{
|
|||||||
|
|
||||||
/// 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
|
||||||
/// database, ensuring its consistent and writing back any changes.
|
/// database, ensuring its consistent and writing back any changes.
|
||||||
// TODO: Split into inner and external/interfaces to facilitate building.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MusicHoard<Database, Library> {
|
pub struct MusicHoard<Database, Library> {
|
||||||
filter: CollectionFilter,
|
filter: CollectionFilter,
|
||||||
filtered: Collection,
|
filtered: Collection,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
pre_commit: Collection,
|
|
||||||
database: Database,
|
database: Database,
|
||||||
library: Library,
|
library: Library,
|
||||||
library_cache: Collection,
|
library_cache: HashMap<NormalString, LibArtist>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct LibArtist {
|
||||||
|
meta: ArtistMeta,
|
||||||
|
albums: Vec<Album>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LibArtist {
|
||||||
|
fn new<Name: Into<ArtistName>>(name: Name) -> Self {
|
||||||
|
LibArtist {
|
||||||
|
meta: ArtistMeta::new(name),
|
||||||
|
albums: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoId for LibArtist {
|
||||||
|
type Id = ArtistId;
|
||||||
|
type IdSelf = Artist;
|
||||||
|
|
||||||
|
fn into_id(self, id: &Self::Id) -> Self::IdSelf {
|
||||||
|
Artist {
|
||||||
|
id: *id,
|
||||||
|
meta: self.meta,
|
||||||
|
albums: self.albums,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phantom type for when a library implementation is not needed.
|
/// Phantom type for when a library implementation is not needed.
|
||||||
|
@ -2,9 +2,7 @@ use once_cell::sync::Lazy;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::core::collection::{
|
use crate::core::collection::{
|
||||||
album::{
|
album::{Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMbRef, AlbumMeta, AlbumPrimaryType},
|
||||||
Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMbRef, AlbumMeta, AlbumPrimaryType, AlbumSeq,
|
|
||||||
},
|
|
||||||
artist::{Artist, ArtistId, ArtistInfo, ArtistMbRef, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistInfo, ArtistMbRef, ArtistMeta},
|
||||||
musicbrainz::{MbAlbumRef, MbArtistRef},
|
musicbrainz::{MbAlbumRef, MbArtistRef},
|
||||||
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
||||||
|
@ -34,6 +34,7 @@ impl From<DeserializeDatabase> for Collection {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct DeserializeArtist {
|
pub struct DeserializeArtist {
|
||||||
|
pub id: usize,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub mb_ref: DeserializeMbRefOption,
|
pub mb_ref: DeserializeMbRefOption,
|
||||||
pub sort: Option<String>,
|
pub sort: Option<String>,
|
||||||
@ -117,13 +118,12 @@ impl From<DeserializeArtist> for Artist {
|
|||||||
let mut albums: Vec<Album> = artist.albums.into_iter().map(Into::into).collect();
|
let mut albums: Vec<Album> = artist.albums.into_iter().map(Into::into).collect();
|
||||||
albums.sort_unstable();
|
albums.sort_unstable();
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(artist.id),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: artist.name,
|
name: artist.name,
|
||||||
mb_ref: artist.mb_ref.into(),
|
|
||||||
},
|
|
||||||
sort: artist.sort,
|
sort: artist.sort,
|
||||||
info: ArtistInfo {
|
info: ArtistInfo {
|
||||||
|
mb_ref: artist.mb_ref.into(),
|
||||||
properties: artist.properties,
|
properties: artist.properties,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -141,9 +141,9 @@ impl From<DeserializeAlbum> for Album {
|
|||||||
lib_id: album.lib_id.into(),
|
lib_id: album.lib_id.into(),
|
||||||
mb_ref: album.mb_ref.into(),
|
mb_ref: album.mb_ref.into(),
|
||||||
},
|
},
|
||||||
|
info: AlbumInfo {
|
||||||
date: album.date.into(),
|
date: album.date.into(),
|
||||||
seq: AlbumSeq(album.seq),
|
seq: AlbumSeq(album.seq),
|
||||||
info: AlbumInfo {
|
|
||||||
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(),
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,12 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collection::musicbrainz::{MbRefOption, Mbid},
|
collection::musicbrainz::{MbRefOption, Mbid},
|
||||||
core::collection::{album::Album, artist::Artist, musicbrainz::IMusicBrainzRef, Collection},
|
core::collection::{
|
||||||
|
album::Album,
|
||||||
|
artist::{Artist, ArtistMeta},
|
||||||
|
musicbrainz::IMusicBrainzRef,
|
||||||
|
Collection,
|
||||||
|
},
|
||||||
external::database::serde::common::{
|
external::database::serde::common::{
|
||||||
MbRefOptionDef, SerdeAlbumDate, SerdeAlbumLibId, SerdeAlbumPrimaryType,
|
MbRefOptionDef, SerdeAlbumDate, SerdeAlbumLibId, SerdeAlbumPrimaryType,
|
||||||
SerdeAlbumSecondaryType,
|
SerdeAlbumSecondaryType,
|
||||||
@ -73,12 +78,20 @@ impl Serialize for SerializeMbid<'_> {
|
|||||||
|
|
||||||
impl<'a> From<&'a Artist> for SerializeArtist<'a> {
|
impl<'a> From<&'a Artist> for SerializeArtist<'a> {
|
||||||
fn from(artist: &'a Artist) -> Self {
|
fn from(artist: &'a Artist) -> Self {
|
||||||
|
let mut sa: SerializeArtist = (&artist.meta).into();
|
||||||
|
sa.albums = artist.albums.iter().map(Into::into).collect();
|
||||||
|
sa
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a ArtistMeta> for SerializeArtist<'a> {
|
||||||
|
fn from(meta: &'a ArtistMeta) -> Self {
|
||||||
SerializeArtist {
|
SerializeArtist {
|
||||||
name: &artist.meta.id.name,
|
name: &meta.name,
|
||||||
mb_ref: (&artist.meta.id.mb_ref).into(),
|
mb_ref: (&meta.info.mb_ref).into(),
|
||||||
sort: &artist.meta.sort,
|
sort: &meta.sort,
|
||||||
properties: &artist.meta.info.properties,
|
properties: &meta.info.properties,
|
||||||
albums: artist.albums.iter().map(Into::into).collect(),
|
albums: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,8 +102,8 @@ impl<'a> From<&'a Album> for SerializeAlbum<'a> {
|
|||||||
title: &album.meta.id.title,
|
title: &album.meta.id.title,
|
||||||
lib_id: album.meta.id.lib_id.into(),
|
lib_id: album.meta.id.lib_id.into(),
|
||||||
mb_ref: (&album.meta.id.mb_ref).into(),
|
mb_ref: (&album.meta.id.mb_ref).into(),
|
||||||
date: album.meta.date.into(),
|
date: album.meta.info.date.into(),
|
||||||
seq: album.meta.seq.0,
|
seq: album.meta.info.seq.0,
|
||||||
primary_type: album.meta.info.primary_type.map(Into::into),
|
primary_type: album.meta.info.primary_type.map(Into::into),
|
||||||
secondary_types: album
|
secondary_types: album
|
||||||
.meta
|
.meta
|
||||||
|
@ -108,7 +108,8 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
mbid JSON NOT NULL DEFAULT '\"None\"',
|
mbid JSON NOT NULL DEFAULT '\"None\"',
|
||||||
sort TEXT NULL,
|
sort TEXT NULL,
|
||||||
properties JSON NOT NULL DEFAULT '{}'
|
properties JSON NOT NULL DEFAULT '{}',
|
||||||
|
UNIQUE(name, mbid)
|
||||||
)",
|
)",
|
||||||
);
|
);
|
||||||
Self::execute(&mut stmt, ())
|
Self::execute(&mut stmt, ())
|
||||||
@ -131,7 +132,8 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
day INT NULL,
|
day INT NULL,
|
||||||
seq INT NOT NULL,
|
seq INT NOT NULL,
|
||||||
primary_type JSON NOT NULL DEFAULT 'null',
|
primary_type JSON NOT NULL DEFAULT 'null',
|
||||||
secondary_types JSON NOT NULL DEFAULT '[]'
|
secondary_types JSON NOT NULL DEFAULT '[]',
|
||||||
|
UNIQUE(title, lib_id, mbid)
|
||||||
)",
|
)",
|
||||||
);
|
);
|
||||||
Self::execute(&mut stmt, ())
|
Self::execute(&mut stmt, ())
|
||||||
@ -145,7 +147,11 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
fn insert_database_version(&self, version: &str) -> Result<(), Error> {
|
fn insert_database_version(&self, version: &str) -> Result<(), Error> {
|
||||||
let mut stmt = self.prepare_cached(
|
let mut stmt = self.prepare_cached(
|
||||||
"INSERT INTO database_metadata (name, value)
|
"INSERT INTO database_metadata (name, value)
|
||||||
VALUES (?1, ?2)",
|
VALUES (?1, ?2)
|
||||||
|
ON CONFLICT(name) DO UPDATE SET value = ?2
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 EXCEPT SELECT 1 WHERE value = ?2
|
||||||
|
)",
|
||||||
);
|
);
|
||||||
Self::execute(&mut stmt, ("version", version))
|
Self::execute(&mut stmt, ("version", version))
|
||||||
}
|
}
|
||||||
@ -160,7 +166,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_artist(&self, artist: &SerializeArtist<'_>) -> Result<(), Error> {
|
fn insert_artist(&self, artist: &SerializeArtist<'_>) -> Result<i64, Error> {
|
||||||
let mut stmt = self.prepare_cached(
|
let mut stmt = self.prepare_cached(
|
||||||
"INSERT INTO artists (name, mbid, sort, properties)
|
"INSERT INTO artists (name, mbid, sort, properties)
|
||||||
VALUES (?1, ?2, ?3, ?4)",
|
VALUES (?1, ?2, ?3, ?4)",
|
||||||
@ -173,20 +179,43 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
artist.sort,
|
artist.sort,
|
||||||
serde_json::to_string(&artist.properties)?,
|
serde_json::to_string(&artist.properties)?,
|
||||||
),
|
),
|
||||||
|
)?;
|
||||||
|
Ok(self.tx.last_insert_rowid())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_artist(&self, oid: i64, artist: &SerializeArtist<'_>) -> Result<(), Error> {
|
||||||
|
let mut stmt = self.prepare_cached(
|
||||||
|
"UPDATE SET name = ?2, mbid = ?3, sort = ?4, properties = ?5
|
||||||
|
WHERE rowid = ?1 EXISTS (
|
||||||
|
SELECT 1 EXCEPT SELECT 1 WHERE
|
||||||
|
name = ?2 AND mbid = ?3 AND sort = ?4 AND properties = ?5
|
||||||
|
)",
|
||||||
|
);
|
||||||
|
Self::execute(
|
||||||
|
&mut stmt,
|
||||||
|
(
|
||||||
|
oid,
|
||||||
|
artist.name,
|
||||||
|
serde_json::to_string(&artist.mb_ref)?,
|
||||||
|
artist.sort,
|
||||||
|
serde_json::to_string(&artist.properties)?,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_all_artists(&self) -> Result<Vec<DeserializeArtist>, Error> {
|
fn select_all_artists(&self) -> Result<Vec<DeserializeArtist>, Error> {
|
||||||
let mut stmt = self.prepare_cached("SELECT name, mbid, sort, properties FROM artists");
|
let mut stmt =
|
||||||
|
self.prepare_cached("SELECT rowid, name, mbid, sort, properties FROM artists");
|
||||||
let mut rows = Self::query(&mut stmt, ())?;
|
let mut rows = Self::query(&mut stmt, ())?;
|
||||||
|
|
||||||
let mut artists = vec![];
|
let mut artists = vec![];
|
||||||
while let Some(row) = Self::next_row(&mut rows)? {
|
while let Some(row) = Self::next_row(&mut rows)? {
|
||||||
artists.push(DeserializeArtist {
|
artists.push(DeserializeArtist {
|
||||||
name: Self::get_value(row, 0)?,
|
id: Self::get_value::<i64>(row, 0)? as usize,
|
||||||
mb_ref: serde_json::from_str(&Self::get_value::<String>(row, 1)?)?,
|
name: Self::get_value(row, 1)?,
|
||||||
sort: Self::get_value(row, 2)?,
|
mb_ref: serde_json::from_str(&Self::get_value::<String>(row, 2)?)?,
|
||||||
properties: serde_json::from_str(&Self::get_value::<String>(row, 3)?)?,
|
sort: Self::get_value(row, 3)?,
|
||||||
|
properties: serde_json::from_str(&Self::get_value::<String>(row, 4)?)?,
|
||||||
albums: vec![],
|
albums: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -198,7 +227,15 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
let mut stmt = self.prepare_cached(
|
let mut stmt = self.prepare_cached(
|
||||||
"INSERT INTO albums (title, lib_id, mbid, artist_name,
|
"INSERT INTO albums (title, lib_id, mbid, artist_name,
|
||||||
year, month, day, seq, primary_type, secondary_types)
|
year, month, day, seq, primary_type, secondary_types)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
|
||||||
|
ON CONFLICT(title, lib_id, mbid) DO UPDATE SET
|
||||||
|
artist_name = ?4, year = ?5, month = ?6, day = ?7, seq = ?8, primary_type = ?9,
|
||||||
|
secondary_types = ?10
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 EXCEPT SELECT 1 WHERE
|
||||||
|
artist_name = ?4 AND year = ?5 AND month = ?6 AND day = ?7 AND seq = ?8 AND
|
||||||
|
primary_type = ?9 AND secondary_types = ?10
|
||||||
|
)",
|
||||||
);
|
);
|
||||||
Self::execute(
|
Self::execute(
|
||||||
&mut stmt,
|
&mut stmt,
|
||||||
|
@ -9,7 +9,10 @@ use mockall::automock;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{
|
core::{
|
||||||
collection::Collection,
|
collection::{
|
||||||
|
artist::{ArtistId, ArtistMeta},
|
||||||
|
Collection,
|
||||||
|
},
|
||||||
interface::database::{IDatabase, LoadError, SaveError},
|
interface::database::{IDatabase, LoadError, SaveError},
|
||||||
},
|
},
|
||||||
external::database::serde::{
|
external::database::serde::{
|
||||||
@ -60,7 +63,11 @@ pub trait ISqlTransactionBackend {
|
|||||||
|
|
||||||
/// Insert an artist into the artist table.
|
/// Insert an artist into the artist table.
|
||||||
#[allow(clippy::needless_lifetimes)] // Conflicts with automock.
|
#[allow(clippy::needless_lifetimes)] // Conflicts with automock.
|
||||||
fn insert_artist<'a>(&self, artist: &SerializeArtist<'a>) -> Result<(), Error>;
|
fn insert_artist<'a>(&self, artist: &SerializeArtist<'a>) -> Result<i64, Error>;
|
||||||
|
|
||||||
|
/// Update an artist in the artist table.
|
||||||
|
#[allow(clippy::needless_lifetimes)] // Conflicts with automock.
|
||||||
|
fn update_artist<'a>(&self, oid: i64, artist: &SerializeArtist<'a>) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Get all artists from the artist table.
|
/// Get all artists from the artist table.
|
||||||
fn select_all_artists(&self) -> Result<Vec<DeserializeArtist>, Error>;
|
fn select_all_artists(&self) -> Result<Vec<DeserializeArtist>, Error>;
|
||||||
@ -152,6 +159,15 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> SqlDatabase<SDB> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB> {
|
impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB> {
|
||||||
|
fn reset(&mut self) -> Result<(), SaveError> {
|
||||||
|
let tx = self.backend.transaction()?;
|
||||||
|
|
||||||
|
Self::drop_tables(&tx)?;
|
||||||
|
Self::create_tables(&tx)?;
|
||||||
|
|
||||||
|
Ok(tx.commit()?)
|
||||||
|
}
|
||||||
|
|
||||||
fn load(&mut self) -> Result<Collection, LoadError> {
|
fn load(&mut self) -> Result<Collection, LoadError> {
|
||||||
let tx = self.backend.transaction()?;
|
let tx = self.backend.transaction()?;
|
||||||
|
|
||||||
@ -178,7 +194,6 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB>
|
|||||||
let database: SerializeDatabase = collection.into();
|
let database: SerializeDatabase = collection.into();
|
||||||
let tx = self.backend.transaction()?;
|
let tx = self.backend.transaction()?;
|
||||||
|
|
||||||
Self::drop_tables(&tx)?;
|
|
||||||
Self::create_tables(&tx)?;
|
Self::create_tables(&tx)?;
|
||||||
|
|
||||||
match database {
|
match database {
|
||||||
@ -196,6 +211,16 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB>
|
|||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn insert_artist(&mut self, artist: &ArtistMeta) -> Result<ArtistId, SaveError> {
|
||||||
|
let tx = self.backend.transaction()?;
|
||||||
|
|
||||||
|
let sa: SerializeArtist = artist.into();
|
||||||
|
let oid = tx.insert_artist(&sa)?;
|
||||||
|
|
||||||
|
tx.commit()?;
|
||||||
|
Ok(ArtistId(oid as usize))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -203,7 +228,7 @@ pub mod testmod;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::VecDeque;
|
use std::{collections::VecDeque, ops::AddAssign};
|
||||||
|
|
||||||
use mockall::{predicate, Sequence};
|
use mockall::{predicate, Sequence};
|
||||||
|
|
||||||
@ -300,21 +325,33 @@ mod tests {
|
|||||||
SqlDatabase::new(backend).unwrap()
|
SqlDatabase::new(backend).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset() {
|
||||||
|
let mut tx = MockISqlTransactionBackend::new();
|
||||||
|
let mut seq = Sequence::new();
|
||||||
|
expect_drop!(tx, seq);
|
||||||
|
expect_create!(tx, seq);
|
||||||
|
then0!(tx, seq, expect_commit);
|
||||||
|
assert!(database(VecDeque::from([tx])).reset().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn save() {
|
fn save() {
|
||||||
let write_data = FULL_COLLECTION.to_owned();
|
let write_data = FULL_COLLECTION.to_owned();
|
||||||
|
|
||||||
let mut tx = MockISqlTransactionBackend::new();
|
let mut tx = MockISqlTransactionBackend::new();
|
||||||
let mut seq = Sequence::new();
|
let mut seq = Sequence::new();
|
||||||
expect_drop!(tx, seq);
|
|
||||||
expect_create!(tx, seq);
|
expect_create!(tx, seq);
|
||||||
then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103));
|
then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103));
|
||||||
|
let mut rowid: i64 = 0;
|
||||||
for artist in write_data.iter() {
|
for artist in write_data.iter() {
|
||||||
let ac = artist.clone();
|
let ac = artist.clone();
|
||||||
then1!(tx, seq, expect_insert_artist)
|
rowid.add_assign(1);
|
||||||
|
then!(tx, seq, expect_insert_artist)
|
||||||
|
.return_once(move |_| Ok(rowid))
|
||||||
.withf(move |a| a == &Into::<SerializeArtist>::into(&ac));
|
.withf(move |a| a == &Into::<SerializeArtist>::into(&ac));
|
||||||
for album in artist.albums.iter() {
|
for album in artist.albums.iter() {
|
||||||
let (nc, ac) = (artist.meta.id.name.clone(), album.clone());
|
let (nc, ac) = (artist.meta.name.clone(), album.clone());
|
||||||
then2!(tx, seq, expect_insert_album)
|
then2!(tx, seq, expect_insert_album)
|
||||||
.withf(move |n, a| n == nc && a == &Into::<SerializeAlbum>::into(&ac));
|
.withf(move |n, a| n == nc && a == &Into::<SerializeAlbum>::into(&ac));
|
||||||
}
|
}
|
||||||
@ -337,9 +374,9 @@ mod tests {
|
|||||||
then!(tx, seq, expect_select_all_artists).return_once(|| Ok(de_artists));
|
then!(tx, seq, expect_select_all_artists).return_once(|| Ok(de_artists));
|
||||||
|
|
||||||
for artist in artists.iter() {
|
for artist in artists.iter() {
|
||||||
let de_albums = DATABASE_SQL_ALBUMS.get(&artist.meta.id.name).unwrap();
|
let de_albums = DATABASE_SQL_ALBUMS.get(&artist.meta.name).unwrap();
|
||||||
then!(tx, seq, expect_select_artist_albums)
|
then!(tx, seq, expect_select_artist_albums)
|
||||||
.with(predicate::eq(artist.meta.id.name.clone()))
|
.with(predicate::eq(artist.meta.name.clone()))
|
||||||
.return_once(|_| Ok(de_albums.to_owned()));
|
.return_once(|_| Ok(de_albums.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +433,6 @@ mod tests {
|
|||||||
fn save_backend_exec_error() {
|
fn save_backend_exec_error() {
|
||||||
let mut tx = MockISqlTransactionBackend::new();
|
let mut tx = MockISqlTransactionBackend::new();
|
||||||
let mut seq = Sequence::new();
|
let mut seq = Sequence::new();
|
||||||
expect_drop!(tx, seq);
|
|
||||||
expect_create!(tx, seq);
|
expect_create!(tx, seq);
|
||||||
then!(tx, seq, expect_insert_database_version)
|
then!(tx, seq, expect_insert_database_version)
|
||||||
.with(predicate::eq(V20250103))
|
.with(predicate::eq(V20250103))
|
||||||
@ -426,7 +462,6 @@ mod tests {
|
|||||||
|
|
||||||
let mut tx = MockISqlTransactionBackend::new();
|
let mut tx = MockISqlTransactionBackend::new();
|
||||||
let mut seq = Sequence::new();
|
let mut seq = Sequence::new();
|
||||||
expect_drop!(tx, seq);
|
|
||||||
expect_create!(tx, seq);
|
expect_create!(tx, seq);
|
||||||
then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103));
|
then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103));
|
||||||
then!(tx, seq, expect_insert_artist)
|
then!(tx, seq, expect_insert_artist)
|
||||||
|
@ -20,6 +20,7 @@ pub static DATABASE_SQL_VERSION: &str = "V20250103";
|
|||||||
pub static DATABASE_SQL_ARTISTS: Lazy<Vec<DeserializeArtist>> = Lazy::new(|| {
|
pub static DATABASE_SQL_ARTISTS: Lazy<Vec<DeserializeArtist>> = Lazy::new(|| {
|
||||||
vec![
|
vec![
|
||||||
DeserializeArtist {
|
DeserializeArtist {
|
||||||
|
id: 1,
|
||||||
name: String::from("Album_Artist ‘A’"),
|
name: String::from("Album_Artist ‘A’"),
|
||||||
mb_ref: DeserializeMbRefOption(MbRefOption::Some(DeserializeMbid(
|
mb_ref: DeserializeMbRefOption(MbRefOption::Some(DeserializeMbid(
|
||||||
"00000000-0000-0000-0000-000000000000".try_into().unwrap(),
|
"00000000-0000-0000-0000-000000000000".try_into().unwrap(),
|
||||||
@ -42,6 +43,7 @@ pub static DATABASE_SQL_ARTISTS: Lazy<Vec<DeserializeArtist>> = Lazy::new(|| {
|
|||||||
albums: vec![],
|
albums: vec![],
|
||||||
},
|
},
|
||||||
DeserializeArtist {
|
DeserializeArtist {
|
||||||
|
id: 2,
|
||||||
name: String::from("Album_Artist ‘B’"),
|
name: String::from("Album_Artist ‘B’"),
|
||||||
mb_ref: DeserializeMbRefOption(MbRefOption::Some(DeserializeMbid(
|
mb_ref: DeserializeMbRefOption(MbRefOption::Some(DeserializeMbid(
|
||||||
"11111111-1111-1111-1111-111111111111".try_into().unwrap(),
|
"11111111-1111-1111-1111-111111111111".try_into().unwrap(),
|
||||||
@ -64,6 +66,7 @@ pub static DATABASE_SQL_ARTISTS: Lazy<Vec<DeserializeArtist>> = Lazy::new(|| {
|
|||||||
albums: vec![],
|
albums: vec![],
|
||||||
},
|
},
|
||||||
DeserializeArtist {
|
DeserializeArtist {
|
||||||
|
id: 3,
|
||||||
name: String::from("The Album_Artist ‘C’"),
|
name: String::from("The Album_Artist ‘C’"),
|
||||||
mb_ref: DeserializeMbRefOption(MbRefOption::CannotHaveMbid),
|
mb_ref: DeserializeMbRefOption(MbRefOption::CannotHaveMbid),
|
||||||
sort: Some(String::from("Album_Artist ‘C’, The")),
|
sort: Some(String::from("Album_Artist ‘C’, The")),
|
||||||
@ -71,6 +74,7 @@ pub static DATABASE_SQL_ARTISTS: Lazy<Vec<DeserializeArtist>> = Lazy::new(|| {
|
|||||||
albums: vec![],
|
albums: vec![],
|
||||||
},
|
},
|
||||||
DeserializeArtist {
|
DeserializeArtist {
|
||||||
|
id: 4,
|
||||||
name: String::from("Album_Artist ‘D’"),
|
name: String::from("Album_Artist ‘D’"),
|
||||||
mb_ref: DeserializeMbRefOption(MbRefOption::None),
|
mb_ref: DeserializeMbRefOption(MbRefOption::None),
|
||||||
sort: None,
|
sort: None,
|
||||||
|
@ -66,7 +66,7 @@ struct DbOpt {
|
|||||||
#[structopt(
|
#[structopt(
|
||||||
long = "database",
|
long = "database",
|
||||||
help = "Database file path",
|
help = "Database file path",
|
||||||
default_value = "database.json"
|
default_value = "database.db"
|
||||||
)]
|
)]
|
||||||
database_file_path: PathBuf,
|
database_file_path: PathBuf,
|
||||||
|
|
||||||
|
@ -2,15 +2,14 @@ macro_rules! full_collection {
|
|||||||
() => {
|
() => {
|
||||||
vec![
|
vec![
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(1),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: "Album_Artist ‘A’".to_string(),
|
name: "Album_Artist ‘A’".to_string(),
|
||||||
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
mb_ref: ArtistMbRef::Some(MbArtistRef::from_url_str(
|
mb_ref: ArtistMbRef::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()),
|
||||||
},
|
|
||||||
sort: None,
|
|
||||||
info: ArtistInfo {
|
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
(String::from("MusicButler"), vec![
|
(String::from("MusicButler"), vec![
|
||||||
String::from("https://www.musicbutler.io/artist-page/000000000"),
|
String::from("https://www.musicbutler.io/artist-page/000000000"),
|
||||||
@ -33,12 +32,10 @@ macro_rules! full_collection {
|
|||||||
"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000"
|
"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
},
|
},
|
||||||
date: 1998.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(1),
|
.with_date(1998)
|
||||||
info: AlbumInfo {
|
.with_seq(1)
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -97,12 +94,10 @@ macro_rules! full_collection {
|
|||||||
lib_id: AlbumLibId::Value(2),
|
lib_id: AlbumLibId::Value(2),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: (2015, 4).into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(1),
|
.with_date((2015, 4))
|
||||||
info: AlbumInfo {
|
.with_seq(1)
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -132,15 +127,14 @@ macro_rules! full_collection {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(2),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: "Album_Artist ‘B’".to_string(),
|
name: "Album_Artist ‘B’".to_string(),
|
||||||
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
mb_ref: ArtistMbRef::Some(MbArtistRef::from_url_str(
|
mb_ref: ArtistMbRef::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()),
|
||||||
},
|
|
||||||
sort: None,
|
|
||||||
info: ArtistInfo {
|
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
(String::from("MusicButler"), vec![
|
(String::from("MusicButler"), vec![
|
||||||
String::from("https://www.musicbutler.io/artist-page/111111111"),
|
String::from("https://www.musicbutler.io/artist-page/111111111"),
|
||||||
@ -165,12 +159,10 @@ macro_rules! full_collection {
|
|||||||
lib_id: AlbumLibId::Value(3),
|
lib_id: AlbumLibId::Value(3),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: (2003, 6, 6).into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(1),
|
.with_date((2003, 6, 6))
|
||||||
info: AlbumInfo {
|
.with_seq(1)
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -209,12 +201,10 @@ macro_rules! full_collection {
|
|||||||
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111"
|
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
},
|
},
|
||||||
date: 2008.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(3),
|
.with_date(2008)
|
||||||
info: AlbumInfo {
|
.with_seq(3)
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -253,12 +243,10 @@ macro_rules! full_collection {
|
|||||||
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112"
|
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112"
|
||||||
).unwrap()),
|
).unwrap()),
|
||||||
},
|
},
|
||||||
date: 2009.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(2),
|
.with_date(2009)
|
||||||
info: AlbumInfo {
|
.with_seq(2)
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -295,12 +283,10 @@ macro_rules! full_collection {
|
|||||||
lib_id: AlbumLibId::Value(6),
|
lib_id: AlbumLibId::Value(6),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2015.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(4),
|
.with_date(2015)
|
||||||
info: AlbumInfo {
|
.with_seq(4)
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -333,13 +319,12 @@ macro_rules! full_collection {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(3),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: "The Album_Artist ‘C’".to_string(),
|
name: "The Album_Artist ‘C’".to_string(),
|
||||||
mb_ref: ArtistMbRef::CannotHaveMbid,
|
|
||||||
},
|
|
||||||
sort: Some("Album_Artist ‘C’, The".to_string()),
|
sort: Some("Album_Artist ‘C’, The".to_string()),
|
||||||
info: ArtistInfo {
|
info: ArtistInfo {
|
||||||
|
mb_ref: ArtistMbRef::CannotHaveMbid,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -351,12 +336,9 @@ macro_rules! full_collection {
|
|||||||
lib_id: AlbumLibId::Value(7),
|
lib_id: AlbumLibId::Value(7),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 1985.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(1985)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -393,12 +375,9 @@ macro_rules! full_collection {
|
|||||||
lib_id: AlbumLibId::Value(8),
|
lib_id: AlbumLibId::Value(8),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2018.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(2018)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -431,13 +410,12 @@ macro_rules! full_collection {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(4),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: "Album_Artist ‘D’".to_string(),
|
name: "Album_Artist ‘D’".to_string(),
|
||||||
mb_ref: ArtistMbRef::None,
|
|
||||||
},
|
|
||||||
sort: None,
|
sort: None,
|
||||||
info: ArtistInfo {
|
info: ArtistInfo {
|
||||||
|
mb_ref: ArtistMbRef::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -449,12 +427,9 @@ macro_rules! full_collection {
|
|||||||
lib_id: AlbumLibId::Value(9),
|
lib_id: AlbumLibId::Value(9),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 1995.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(1995)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -491,12 +466,9 @@ macro_rules! full_collection {
|
|||||||
lib_id: AlbumLibId::Value(10),
|
lib_id: AlbumLibId::Value(10),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2028.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(2028)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
|
@ -3,13 +3,12 @@ macro_rules! library_collection {
|
|||||||
() => {
|
() => {
|
||||||
vec![
|
vec![
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(0),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: "Album_Artist ‘A’".to_string(),
|
name: "Album_Artist ‘A’".to_string(),
|
||||||
mb_ref: ArtistMbRef::None,
|
|
||||||
},
|
|
||||||
sort: None,
|
sort: None,
|
||||||
info: ArtistInfo {
|
info: ArtistInfo {
|
||||||
|
mb_ref: ArtistMbRef::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -21,9 +20,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(1),
|
lib_id: AlbumLibId::Value(1),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 1998.into(),
|
info: AlbumInfo::default().with_date(1998),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -82,9 +79,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(2),
|
lib_id: AlbumLibId::Value(2),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: (2015, 4).into(),
|
info: AlbumInfo::default().with_date((2015, 4)),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -114,13 +109,12 @@ macro_rules! library_collection {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(0),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: "Album_Artist ‘B’".to_string(),
|
name: "Album_Artist ‘B’".to_string(),
|
||||||
mb_ref: ArtistMbRef::None,
|
|
||||||
},
|
|
||||||
sort: None,
|
sort: None,
|
||||||
info: ArtistInfo {
|
info: ArtistInfo {
|
||||||
|
mb_ref: ArtistMbRef::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -132,9 +126,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(3),
|
lib_id: AlbumLibId::Value(3),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: (2003, 6, 6).into(),
|
info: AlbumInfo::default().with_date((2003, 6, 6)),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -171,9 +163,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(4),
|
lib_id: AlbumLibId::Value(4),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2008.into(),
|
info: AlbumInfo::default().with_date(2008),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -210,9 +200,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(5),
|
lib_id: AlbumLibId::Value(5),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2009.into(),
|
info: AlbumInfo::default().with_date(2009),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -249,9 +237,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(6),
|
lib_id: AlbumLibId::Value(6),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2015.into(),
|
info: AlbumInfo::default().with_date(2015),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -284,13 +270,12 @@ macro_rules! library_collection {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(0),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: "The Album_Artist ‘C’".to_string(),
|
name: "The Album_Artist ‘C’".to_string(),
|
||||||
mb_ref: ArtistMbRef::None,
|
|
||||||
},
|
|
||||||
sort: Some("Album_Artist ‘C’, The".to_string()),
|
sort: Some("Album_Artist ‘C’, The".to_string()),
|
||||||
info: ArtistInfo {
|
info: ArtistInfo {
|
||||||
|
mb_ref: ArtistMbRef::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -302,9 +287,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(7),
|
lib_id: AlbumLibId::Value(7),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 1985.into(),
|
info: AlbumInfo::default().with_date(1985),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -341,9 +324,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(8),
|
lib_id: AlbumLibId::Value(8),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2018.into(),
|
info: AlbumInfo::default().with_date(2018),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -376,13 +357,12 @@ macro_rules! library_collection {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(0),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: "Album_Artist ‘D’".to_string(),
|
name: "Album_Artist ‘D’".to_string(),
|
||||||
mb_ref: ArtistMbRef::None,
|
|
||||||
},
|
|
||||||
sort: None,
|
sort: None,
|
||||||
info: ArtistInfo {
|
info: ArtistInfo {
|
||||||
|
mb_ref: ArtistMbRef::None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -394,9 +374,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(9),
|
lib_id: AlbumLibId::Value(9),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 1995.into(),
|
info: AlbumInfo::default().with_date(1995),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -433,9 +411,7 @@ macro_rules! library_collection {
|
|||||||
lib_id: AlbumLibId::Value(10),
|
lib_id: AlbumLibId::Value(10),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2028.into(),
|
info: AlbumInfo::default().with_date(2028),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
|
@ -6,7 +6,7 @@ use std::{
|
|||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{Album, AlbumId, AlbumMeta},
|
album::{Album, AlbumId, AlbumMeta},
|
||||||
artist::{Artist, ArtistId, ArtistMeta},
|
artist::{Artist, ArtistId},
|
||||||
musicbrainz::{IMusicBrainzRef, MbArtistRef, MbRefOption, Mbid},
|
musicbrainz::{IMusicBrainzRef, MbArtistRef, MbRefOption, Mbid},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ use crate::tui::{
|
|||||||
app::{
|
app::{
|
||||||
machine::{match_state::MatchState, App, AppInner, AppMachine},
|
machine::{match_state::MatchState, App, AppInner, AppMachine},
|
||||||
selection::KeySelection,
|
selection::KeySelection,
|
||||||
AppPublicState, AppState, Category, IAppEventFetch, IAppInteractFetch,
|
AppPublicState, AppState, ArtistMatching, Category, IAppEventFetch, IAppInteractFetch,
|
||||||
},
|
},
|
||||||
lib::interface::musicbrainz::daemon::{
|
lib::interface::musicbrainz::daemon::{
|
||||||
EntityList, Error as DaemonError, IMbJobSender, MbApiResult, MbParams, MbReturn,
|
EntityList, Error as DaemonError, IMbJobSender, MbApiResult, MbParams, MbReturn,
|
||||||
@ -115,14 +115,14 @@ impl AppMachine<FetchState> {
|
|||||||
let mut requests = Self::search_artist_job(artist);
|
let mut requests = Self::search_artist_job(artist);
|
||||||
if requests.is_empty() {
|
if requests.is_empty() {
|
||||||
fetch = FetchState::fetch(rx);
|
fetch = FetchState::fetch(rx);
|
||||||
requests = Self::browse_release_group_job(&artist.meta.id.mb_ref);
|
requests = Self::browse_release_group_job(artist.id, &artist.meta.info.mb_ref);
|
||||||
} else {
|
} else {
|
||||||
fetch = FetchState::search(rx);
|
fetch = FetchState::search(rx);
|
||||||
}
|
}
|
||||||
SubmitJob { fetch, requests }
|
SubmitJob { fetch, requests }
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let arid = match artist.meta.id.mb_ref {
|
let arid = match artist.meta.info.mb_ref {
|
||||||
MbRefOption::Some(ref mbref) => mbref,
|
MbRefOption::Some(ref mbref) => mbref,
|
||||||
_ => return Err("cannot fetch album: artist has no MBID"),
|
_ => return Err("cannot fetch album: artist has no MBID"),
|
||||||
};
|
};
|
||||||
@ -130,10 +130,9 @@ impl AppMachine<FetchState> {
|
|||||||
Some(album_state) => &artist.albums[album_state.index],
|
Some(album_state) => &artist.albums[album_state.index],
|
||||||
None => return Err("cannot fetch album: no album selected"),
|
None => return Err("cannot fetch album: no album selected"),
|
||||||
};
|
};
|
||||||
let artist_id = &artist.meta.id;
|
|
||||||
SubmitJob {
|
SubmitJob {
|
||||||
fetch: FetchState::search(rx),
|
fetch: FetchState::search(rx),
|
||||||
requests: Self::search_release_group_job(artist_id, arid, album),
|
requests: Self::search_release_group_job(artist.id, arid, album),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -191,9 +190,9 @@ impl AppMachine<FetchState> {
|
|||||||
let selection = KeySelection::get(coll, &inner.selection);
|
let selection = KeySelection::get(coll, &inner.selection);
|
||||||
|
|
||||||
// Find the artist in the full collection to correctly identify already existing albums.
|
// Find the artist in the full collection to correctly identify already existing albums.
|
||||||
let artist_id = artist.meta.id.clone();
|
let artist_id = artist.id.clone();
|
||||||
let coll = inner.music_hoard.get_collection();
|
let coll = inner.music_hoard.get_collection();
|
||||||
let artist = coll.iter().find(|a| a.meta.id == artist_id).unwrap();
|
let artist = coll.iter().find(|a| a.id == artist_id).unwrap();
|
||||||
|
|
||||||
for new in Self::new_albums(fetch_albums, &artist.albums).into_iter() {
|
for new in Self::new_albums(fetch_albums, &artist.albums).into_iter() {
|
||||||
inner.music_hoard.add_album(&artist_id, new)?;
|
inner.music_hoard.add_album(&artist_id, new)?;
|
||||||
@ -223,11 +222,11 @@ impl AppMachine<FetchState> {
|
|||||||
pub fn app_lookup_artist(
|
pub fn app_lookup_artist(
|
||||||
inner: AppInner,
|
inner: AppInner,
|
||||||
fetch: FetchState,
|
fetch: FetchState,
|
||||||
artist: &ArtistMeta,
|
matching: ArtistMatching,
|
||||||
mbid: Mbid,
|
mbid: Mbid,
|
||||||
) -> App {
|
) -> App {
|
||||||
let f = Self::submit_lookup_artist_job;
|
let f = Self::submit_lookup_artist_job;
|
||||||
Self::app_lookup(f, inner, fetch, artist, mbid)
|
Self::app_lookup(f, inner, fetch, matching, mbid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app_lookup_album(
|
pub fn app_lookup_album(
|
||||||
@ -243,18 +242,18 @@ impl AppMachine<FetchState> {
|
|||||||
Self::app_lookup(f, inner, fetch, album_id, mbid)
|
Self::app_lookup(f, inner, fetch, album_id, mbid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app_lookup<F, Meta>(
|
fn app_lookup<F, Matching>(
|
||||||
submit: F,
|
submit: F,
|
||||||
inner: AppInner,
|
inner: AppInner,
|
||||||
mut fetch: FetchState,
|
mut fetch: FetchState,
|
||||||
meta: Meta,
|
matching: Matching,
|
||||||
mbid: Mbid,
|
mbid: Mbid,
|
||||||
) -> App
|
) -> App
|
||||||
where
|
where
|
||||||
F: FnOnce(&dyn IMbJobSender, ResultSender, Meta, Mbid) -> Result<(), DaemonError>,
|
F: FnOnce(&dyn IMbJobSender, ResultSender, Matching, Mbid) -> Result<(), DaemonError>,
|
||||||
{
|
{
|
||||||
let (lookup_tx, lookup_rx) = mpsc::channel::<MbApiResult>();
|
let (lookup_tx, lookup_rx) = mpsc::channel::<MbApiResult>();
|
||||||
if let Err(err) = submit(&*inner.musicbrainz, lookup_tx, meta, mbid) {
|
if let Err(err) = submit(&*inner.musicbrainz, lookup_tx, matching, mbid) {
|
||||||
return AppMachine::error_state(inner, err.to_string()).into();
|
return AppMachine::error_state(inner, err.to_string()).into();
|
||||||
}
|
}
|
||||||
fetch.lookup_rx.replace(lookup_rx);
|
fetch.lookup_rx.replace(lookup_rx);
|
||||||
@ -262,60 +261,72 @@ impl AppMachine<FetchState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist_job(artist: &Artist) -> VecDeque<MbParams> {
|
fn search_artist_job(artist: &Artist) -> VecDeque<MbParams> {
|
||||||
match artist.meta.id.mb_ref {
|
match artist.meta.info.mb_ref {
|
||||||
MbRefOption::Some(ref arid) => {
|
MbRefOption::Some(ref arid) => {
|
||||||
Self::search_albums_requests(&artist.meta.id, arid, &artist.albums)
|
Self::search_albums_requests(artist.id, arid, &artist.albums)
|
||||||
}
|
}
|
||||||
MbRefOption::CannotHaveMbid => VecDeque::new(),
|
MbRefOption::CannotHaveMbid => VecDeque::new(),
|
||||||
MbRefOption::None => Self::search_artist_request(&artist.meta),
|
MbRefOption::None => Self::search_artist_request(ArtistMatching::new(
|
||||||
|
artist.id,
|
||||||
|
artist.meta.name.clone(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_release_group_job(
|
fn search_release_group_job(
|
||||||
artist_id: &ArtistId,
|
artist_id: ArtistId,
|
||||||
artist_mbid: &MbArtistRef,
|
artist_mbid: &MbArtistRef,
|
||||||
album: &Album,
|
album: &Album,
|
||||||
) -> VecDeque<MbParams> {
|
) -> VecDeque<MbParams> {
|
||||||
Self::search_albums_requests(artist_id, artist_mbid, slice::from_ref(album))
|
Self::search_albums_requests(artist_id, artist_mbid, slice::from_ref(album))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist_request(meta: &ArtistMeta) -> VecDeque<MbParams> {
|
fn search_artist_request(matching: ArtistMatching) -> VecDeque<MbParams> {
|
||||||
VecDeque::from([MbParams::search_artist(meta.clone())])
|
VecDeque::from([MbParams::search_artist(matching)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_albums_requests(
|
fn search_albums_requests(
|
||||||
artist: &ArtistId,
|
artist_id: ArtistId,
|
||||||
arid: &MbArtistRef,
|
artist_mbid: &MbArtistRef,
|
||||||
albums: &[Album],
|
albums: &[Album],
|
||||||
) -> VecDeque<MbParams> {
|
) -> VecDeque<MbParams> {
|
||||||
let arid = arid.mbid();
|
let arid = artist_mbid.mbid();
|
||||||
albums
|
albums
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|album| album.meta.id.mb_ref.is_none())
|
.filter(|album| album.meta.id.mb_ref.is_none())
|
||||||
.map(|album| {
|
.map(|album| {
|
||||||
MbParams::search_release_group(artist.clone(), arid.clone(), album.meta.clone())
|
MbParams::search_release_group(artist_id, arid.clone(), album.meta.clone())
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn browse_release_group_job(mbopt: &MbRefOption<MbArtistRef>) -> VecDeque<MbParams> {
|
fn browse_release_group_job(
|
||||||
|
artist_id: ArtistId,
|
||||||
|
mbopt: &MbRefOption<MbArtistRef>,
|
||||||
|
) -> VecDeque<MbParams> {
|
||||||
match mbopt {
|
match mbopt {
|
||||||
MbRefOption::Some(mbref) => Self::browse_release_group_request(mbref),
|
MbRefOption::Some(mbref) => Self::browse_release_group_request(artist_id, mbref),
|
||||||
_ => VecDeque::new(),
|
_ => VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn browse_release_group_request(mbref: &MbArtistRef) -> VecDeque<MbParams> {
|
fn browse_release_group_request(
|
||||||
VecDeque::from([MbParams::browse_release_group(mbref.mbid().clone())])
|
artist_id: ArtistId,
|
||||||
|
mbref: &MbArtistRef,
|
||||||
|
) -> VecDeque<MbParams> {
|
||||||
|
VecDeque::from([MbParams::browse_release_group(
|
||||||
|
artist_id,
|
||||||
|
mbref.mbid().clone(),
|
||||||
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_lookup_artist_job(
|
fn submit_lookup_artist_job(
|
||||||
musicbrainz: &dyn IMbJobSender,
|
musicbrainz: &dyn IMbJobSender,
|
||||||
result_sender: ResultSender,
|
result_sender: ResultSender,
|
||||||
artist: &ArtistMeta,
|
matching: ArtistMatching,
|
||||||
mbid: Mbid,
|
mbid: Mbid,
|
||||||
) -> Result<(), DaemonError> {
|
) -> Result<(), DaemonError> {
|
||||||
let requests = VecDeque::from([MbParams::lookup_artist(artist.clone(), mbid)]);
|
let requests = VecDeque::from([MbParams::lookup_artist(matching, mbid)]);
|
||||||
musicbrainz.submit_foreground_job(result_sender, requests)
|
musicbrainz.submit_foreground_job(result_sender, requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,16 +406,18 @@ mod tests {
|
|||||||
let mut fetch = FetchState::search(fetch_rx);
|
let mut fetch = FetchState::search(fetch_rx);
|
||||||
fetch.lookup_rx.replace(lookup_rx);
|
fetch.lookup_rx.replace(lookup_rx);
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
|
let meta = COLLECTION[3].meta.clone();
|
||||||
|
let matching = ArtistMatching::new(id, meta.name.clone());
|
||||||
|
|
||||||
let matches: Vec<Entity<ArtistMeta>> = vec![];
|
let matches: Vec<Entity<ArtistMeta>> = vec![];
|
||||||
let fetch_result = MbReturn::Match(EntityMatches::artist_search(artist.clone(), matches));
|
let fetch_result = MbReturn::Match(EntityMatches::artist_search(matching.clone(), matches));
|
||||||
fetch_tx.send(Ok(fetch_result.clone())).unwrap();
|
fetch_tx.send(Ok(fetch_result.clone())).unwrap();
|
||||||
|
|
||||||
assert_eq!(fetch.try_recv(), Err(TryRecvError::Empty));
|
assert_eq!(fetch.try_recv(), Err(TryRecvError::Empty));
|
||||||
|
|
||||||
let lookup = Entity::new(artist.clone());
|
let lookup = Entity::new(meta.clone());
|
||||||
let lookup_result = MbReturn::Match(EntityMatches::artist_lookup(artist.clone(), lookup));
|
let lookup_result = MbReturn::Match(EntityMatches::artist_lookup(matching.clone(), lookup));
|
||||||
lookup_tx.send(Ok(lookup_result.clone())).unwrap();
|
lookup_tx.send(Ok(lookup_result.clone())).unwrap();
|
||||||
|
|
||||||
assert_eq!(fetch.try_recv(), Ok(Ok(lookup_result)));
|
assert_eq!(fetch.try_recv(), Ok(Ok(lookup_result)));
|
||||||
@ -445,7 +458,7 @@ mod tests {
|
|||||||
fn fetch_single_album() {
|
fn fetch_single_album() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
|
|
||||||
let artist_id = COLLECTION[1].meta.id.clone();
|
let artist_id = COLLECTION[1].id;
|
||||||
let artist_mbid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
let artist_mbid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||||
|
|
||||||
let album_meta = COLLECTION[1].albums[0].meta.clone();
|
let album_meta = COLLECTION[1].albums[0].meta.clone();
|
||||||
@ -520,7 +533,7 @@ mod tests {
|
|||||||
fn fetch_albums() {
|
fn fetch_albums() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
|
|
||||||
let artist_id = COLLECTION[1].meta.id.clone();
|
let artist_id = COLLECTION[1].id;
|
||||||
let artist_mbid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
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();
|
||||||
@ -566,7 +579,7 @@ 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 artist_id = COLLECTION[1].id;
|
||||||
let album_id = COLLECTION[1].albums[0].meta.id.clone();
|
let album_id = COLLECTION[1].albums[0].meta.id.clone();
|
||||||
lookup_album_expectation(&mut mb_job_sender, &artist_id, &album_id);
|
lookup_album_expectation(&mut mb_job_sender, &artist_id, &album_id);
|
||||||
|
|
||||||
@ -579,8 +592,8 @@ mod tests {
|
|||||||
AppMachine::app_lookup_album(inner, fetch, &artist_id, &album_id, mbid());
|
AppMachine::app_lookup_album(inner, fetch, &artist_id, &album_id, mbid());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
|
fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: ArtistMatching) {
|
||||||
let requests = VecDeque::from([MbParams::search_artist(artist.clone())]);
|
let requests = VecDeque::from([MbParams::search_artist(artist)]);
|
||||||
job_sender
|
job_sender
|
||||||
.expect_submit_background_job()
|
.expect_submit_background_job()
|
||||||
.with(predicate::always(), predicate::eq(requests))
|
.with(predicate::always(), predicate::eq(requests))
|
||||||
@ -592,8 +605,10 @@ mod tests {
|
|||||||
fn fetch_artist() {
|
fn fetch_artist() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
search_artist_expectation(&mut mb_job_sender, &artist);
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
search_artist_expectation(&mut mb_job_sender, matching);
|
||||||
|
|
||||||
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);
|
||||||
@ -608,8 +623,8 @@ mod tests {
|
|||||||
assert!(matches!(app, AppState::Fetch(_)));
|
assert!(matches!(app, AppState::Fetch(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
|
fn lookup_artist_expectation(job_sender: &mut MockIMbJobSender, artist: ArtistMatching) {
|
||||||
let requests = VecDeque::from([MbParams::lookup_artist(artist.clone(), mbid())]);
|
let requests = VecDeque::from([MbParams::lookup_artist(artist, 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))
|
||||||
@ -621,8 +636,10 @@ mod tests {
|
|||||||
fn lookup_artist() {
|
fn lookup_artist() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
lookup_artist_expectation(&mut mb_job_sender, &artist);
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
lookup_artist_expectation(&mut mb_job_sender, matching.clone());
|
||||||
|
|
||||||
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);
|
||||||
@ -630,7 +647,7 @@ mod tests {
|
|||||||
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
||||||
let fetch = FetchState::search(fetch_rx);
|
let fetch = FetchState::search(fetch_rx);
|
||||||
|
|
||||||
AppMachine::app_lookup_artist(inner, fetch, &artist, mbid());
|
AppMachine::app_lookup_artist(inner, fetch, matching, mbid());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -671,7 +688,9 @@ mod tests {
|
|||||||
.expect_submit_foreground_job()
|
.expect_submit_foreground_job()
|
||||||
.return_once(|_, _| Err(DaemonError::JobChannelDisconnected));
|
.return_once(|_, _| Err(DaemonError::JobChannelDisconnected));
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
|
||||||
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);
|
||||||
@ -679,7 +698,7 @@ mod tests {
|
|||||||
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
let (_fetch_tx, fetch_rx) = mpsc::channel();
|
||||||
let fetch = FetchState::search(fetch_rx);
|
let fetch = FetchState::search(fetch_rx);
|
||||||
|
|
||||||
let app = AppMachine::app_lookup_artist(inner, fetch, &artist, mbid());
|
let app = AppMachine::app_lookup_artist(inner, fetch, matching, mbid());
|
||||||
assert!(matches!(app, AppState::Error(_)));
|
assert!(matches!(app, AppState::Error(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -687,10 +706,12 @@ mod tests {
|
|||||||
fn recv_ok_match_ok() {
|
fn recv_ok_match_ok() {
|
||||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
let artist_match = Entity::with_score(COLLECTION[2].meta.clone(), 80);
|
let artist_match = Entity::with_score(COLLECTION[2].meta.clone(), 80);
|
||||||
let artist_match_info =
|
let artist_match_info =
|
||||||
EntityMatches::artist_search(artist.clone(), vec![artist_match.clone()]);
|
EntityMatches::artist_search(matching.clone(), vec![artist_match.clone()]);
|
||||||
let fetch_result = Ok(MbReturn::Match(artist_match_info));
|
let fetch_result = Ok(MbReturn::Match(artist_match_info));
|
||||||
tx.send(fetch_result).unwrap();
|
tx.send(fetch_result).unwrap();
|
||||||
|
|
||||||
@ -706,7 +727,7 @@ mod tests {
|
|||||||
MatchOption::CannotHaveMbid,
|
MatchOption::CannotHaveMbid,
|
||||||
MatchOption::ManualInputMbid,
|
MatchOption::ManualInputMbid,
|
||||||
];
|
];
|
||||||
let expected = EntityMatches::artist_search(artist, match_options);
|
let expected = EntityMatches::artist_search(matching, match_options);
|
||||||
assert_eq!(match_state.matches, &expected);
|
assert_eq!(match_state.matches, &expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -730,7 +751,7 @@ mod tests {
|
|||||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||||
let fetch = FetchState::fetch(rx);
|
let fetch = FetchState::fetch(rx);
|
||||||
|
|
||||||
let artist_id = collection[0].meta.id.clone();
|
let artist_id = collection[0].id;
|
||||||
let old_album = collection[0].albums[0].meta.clone();
|
let old_album = collection[0].albums[0].meta.clone();
|
||||||
let new_album = AlbumMeta::new(AlbumId::new("some new album"));
|
let new_album = AlbumMeta::new(AlbumId::new("some new album"));
|
||||||
|
|
||||||
@ -764,7 +785,7 @@ mod tests {
|
|||||||
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
let (tx, rx) = mpsc::channel::<MbApiResult>();
|
||||||
let fetch = FetchState::fetch(rx);
|
let fetch = FetchState::fetch(rx);
|
||||||
|
|
||||||
let artist_id = collection[0].meta.id.clone();
|
let artist_id = collection[0].id;
|
||||||
let old_album = collection[0].albums[0].meta.clone();
|
let old_album = collection[0].albums[0].meta.clone();
|
||||||
let new_album = AlbumMeta::new(AlbumId::new("some new album"));
|
let new_album = AlbumMeta::new(AlbumId::new("some new album"));
|
||||||
|
|
||||||
@ -802,7 +823,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn browse_release_group_expectation(artist: &Artist) -> MockIMbJobSender {
|
fn browse_release_group_expectation(artist: &Artist) -> MockIMbJobSender {
|
||||||
let requests = AppMachine::browse_release_group_job(&artist.meta.id.mb_ref);
|
let requests = AppMachine::browse_release_group_job(artist.id, &artist.meta.info.mb_ref);
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
mb_job_sender
|
mb_job_sender
|
||||||
.expect_submit_background_job()
|
.expect_submit_background_job()
|
||||||
@ -858,8 +879,10 @@ mod tests {
|
|||||||
let app = AppMachine::app_fetch_next(inner, fetch);
|
let app = AppMachine::app_fetch_next(inner, fetch);
|
||||||
assert!(matches!(app, AppState::Fetch(_)));
|
assert!(matches!(app, AppState::Fetch(_)));
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
let match_info = EntityMatches::artist_search::<Entity<ArtistMeta>>(artist, vec![]);
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
let match_info = EntityMatches::artist_search::<Entity<ArtistMeta>>(matching, vec![]);
|
||||||
let fetch_result = Ok(MbReturn::Match(match_info));
|
let fetch_result = Ok(MbReturn::Match(match_info));
|
||||||
tx.send(fetch_result).unwrap();
|
tx.send(fetch_result).unwrap();
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use std::cmp;
|
|||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{AlbumInfo, AlbumMbRef, AlbumMeta},
|
album::{AlbumInfo, AlbumMbRef, AlbumMeta},
|
||||||
artist::{ArtistInfo, ArtistMbRef, ArtistMeta},
|
artist::{ArtistInfo, ArtistMeta},
|
||||||
musicbrainz::{MbRefOption, Mbid},
|
musicbrainz::{MbRefOption, Mbid},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -13,11 +13,6 @@ use crate::tui::app::{
|
|||||||
MatchOption, MatchStatePublic, WidgetState,
|
MatchOption, MatchStatePublic, WidgetState,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ArtistInfoTuple {
|
|
||||||
mb_ref: ArtistMbRef,
|
|
||||||
info: ArtistInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AlbumInfoTuple {
|
struct AlbumInfoTuple {
|
||||||
mb_ref: AlbumMbRef,
|
mb_ref: AlbumMbRef,
|
||||||
info: AlbumInfo,
|
info: AlbumInfo,
|
||||||
@ -27,7 +22,7 @@ trait GetInfoMeta {
|
|||||||
type InfoType;
|
type InfoType;
|
||||||
}
|
}
|
||||||
impl GetInfoMeta for ArtistMeta {
|
impl GetInfoMeta for ArtistMeta {
|
||||||
type InfoType = ArtistInfoTuple;
|
type InfoType = ArtistInfo;
|
||||||
}
|
}
|
||||||
impl GetInfoMeta for AlbumMeta {
|
impl GetInfoMeta for AlbumMeta {
|
||||||
type InfoType = AlbumInfoTuple;
|
type InfoType = AlbumInfoTuple;
|
||||||
@ -44,20 +39,18 @@ enum InfoOption<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GetInfo for MatchOption<ArtistMeta> {
|
impl GetInfo for MatchOption<ArtistMeta> {
|
||||||
type InfoType = ArtistInfoTuple;
|
type InfoType = ArtistInfo;
|
||||||
|
|
||||||
fn get_info(&self) -> InfoOption<Self::InfoType> {
|
fn get_info(&self) -> InfoOption<Self::InfoType> {
|
||||||
let mb_ref;
|
|
||||||
let mut info = ArtistInfo::default();
|
let mut info = ArtistInfo::default();
|
||||||
match self {
|
match self {
|
||||||
MatchOption::Some(option) => {
|
MatchOption::Some(option) => {
|
||||||
mb_ref = option.entity.id.mb_ref.clone();
|
|
||||||
info = option.entity.info.clone();
|
info = option.entity.info.clone();
|
||||||
}
|
}
|
||||||
MatchOption::CannotHaveMbid => mb_ref = MbRefOption::CannotHaveMbid,
|
MatchOption::CannotHaveMbid => info.mb_ref = MbRefOption::CannotHaveMbid,
|
||||||
MatchOption::ManualInputMbid => return InfoOption::NeedInput,
|
MatchOption::ManualInputMbid => return InfoOption::NeedInput,
|
||||||
}
|
}
|
||||||
InfoOption::Info(ArtistInfoTuple { mb_ref, info })
|
InfoOption::Info(info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,11 +173,11 @@ impl AppMachine<MatchState> {
|
|||||||
};
|
};
|
||||||
match self.state.current {
|
match self.state.current {
|
||||||
EntityMatches::Artist(artist_matches) => {
|
EntityMatches::Artist(artist_matches) => {
|
||||||
let matching = &artist_matches.matching;
|
let matching = artist_matches.matching;
|
||||||
AppMachine::app_lookup_artist(self.inner, self.state.fetch, matching, mbid)
|
AppMachine::app_lookup_artist(self.inner, self.state.fetch, matching, mbid)
|
||||||
}
|
}
|
||||||
EntityMatches::Album(album_matches) => {
|
EntityMatches::Album(album_matches) => {
|
||||||
let artist_id = &album_matches.artist;
|
let artist_id = &album_matches.artist_id;
|
||||||
let matching = &album_matches.matching;
|
let matching = &album_matches.matching;
|
||||||
AppMachine::app_lookup_album(
|
AppMachine::app_lookup_album(
|
||||||
self.inner,
|
self.inner,
|
||||||
@ -205,11 +198,10 @@ impl AppMachine<MatchState> {
|
|||||||
fn select_artist(
|
fn select_artist(
|
||||||
inner: &mut AppInner,
|
inner: &mut AppInner,
|
||||||
matches: &ArtistMatches,
|
matches: &ArtistMatches,
|
||||||
tuple: ArtistInfoTuple,
|
info: ArtistInfo,
|
||||||
) -> Result<(), musichoard::Error> {
|
) -> Result<(), musichoard::Error> {
|
||||||
let mh = &mut inner.music_hoard;
|
let mh = &mut inner.music_hoard;
|
||||||
mh.merge_artist_info(&matches.matching.id, tuple.info)?;
|
mh.merge_artist_info(&matches.matching.id, info)
|
||||||
mh.set_artist_mb_ref(&matches.matching.id, tuple.mb_ref)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter_mb_ref(left: &AlbumMbRef, right: &AlbumMbRef) -> bool {
|
fn filter_mb_ref(left: &AlbumMbRef, right: &AlbumMbRef) -> bool {
|
||||||
@ -223,7 +215,7 @@ impl AppMachine<MatchState> {
|
|||||||
) -> Result<(), musichoard::Error> {
|
) -> Result<(), musichoard::Error> {
|
||||||
let coll = inner.music_hoard.get_collection();
|
let coll = inner.music_hoard.get_collection();
|
||||||
let mut clashing = vec![];
|
let mut clashing = vec![];
|
||||||
if let Some(artist) = coll.iter().find(|artist| artist.meta.id == matches.artist) {
|
if let Some(artist) = coll.iter().find(|artist| artist.id == matches.artist_id) {
|
||||||
// While we expect only one, there is nothing stopping anybody from having multiple
|
// While we expect only one, there is nothing stopping anybody from having multiple
|
||||||
// different albums with the same MBID.
|
// different albums with the same MBID.
|
||||||
let iter = artist.albums.iter();
|
let iter = artist.albums.iter();
|
||||||
@ -237,15 +229,15 @@ impl AppMachine<MatchState> {
|
|||||||
let coll = inner.music_hoard.get_filtered();
|
let coll = inner.music_hoard.get_filtered();
|
||||||
let selection = KeySelection::get(coll, &inner.selection);
|
let selection = KeySelection::get(coll, &inner.selection);
|
||||||
|
|
||||||
inner.music_hoard.remove_album(&matches.artist, &album)?;
|
inner.music_hoard.remove_album(&matches.artist_id, &album)?;
|
||||||
|
|
||||||
let coll = inner.music_hoard.get_filtered();
|
let coll = inner.music_hoard.get_filtered();
|
||||||
inner.selection.select_by_id(coll, selection);
|
inner.selection.select_by_id(coll, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mh = &mut inner.music_hoard;
|
let mh = &mut inner.music_hoard;
|
||||||
mh.merge_album_info(&matches.artist, &matches.matching, tuple.info)?;
|
mh.merge_album_info(&matches.artist_id, &matches.matching, tuple.info)?;
|
||||||
mh.set_album_mb_ref(&matches.artist, &matches.matching, tuple.mb_ref)
|
mh.set_album_mb_ref(&matches.artist_id, &matches.matching, tuple.mb_ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,11 +285,11 @@ impl IAppInteractMatch for AppMachine<MatchState> {
|
|||||||
let inner = &mut self.inner;
|
let inner = &mut self.inner;
|
||||||
let result = match self.state.current {
|
let result = match self.state.current {
|
||||||
EntityMatches::Artist(ref mut matches) => match matches.list.extract_info(index) {
|
EntityMatches::Artist(ref mut matches) => match matches.list.extract_info(index) {
|
||||||
InfoOption::Info(tuple) => Self::select_artist(inner, matches, tuple),
|
InfoOption::Info(info) => Self::select_artist(inner, matches, info),
|
||||||
InfoOption::NeedInput => return self.get_input(),
|
InfoOption::NeedInput => return self.get_input(),
|
||||||
},
|
},
|
||||||
EntityMatches::Album(ref mut matches) => match matches.list.extract_info(index) {
|
EntityMatches::Album(ref mut matches) => match matches.list.extract_info(index) {
|
||||||
InfoOption::Info(tuple) => Self::select_album(inner, matches, tuple),
|
InfoOption::Info(info) => Self::select_album(inner, matches, info),
|
||||||
InfoOption::NeedInput => return self.get_input(),
|
InfoOption::NeedInput => return self.get_input(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -326,14 +318,14 @@ mod tests {
|
|||||||
album::{
|
album::{
|
||||||
Album, AlbumDate, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType,
|
Album, AlbumDate, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType,
|
||||||
},
|
},
|
||||||
artist::{Artist, ArtistId, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistMbRef, ArtistMeta, ArtistName},
|
||||||
Collection,
|
Collection,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
machine::tests::{inner, inner_with_mb, input_event, music_hoard},
|
machine::tests::{inner, inner_with_mb, input_event, music_hoard},
|
||||||
IApp, IAppAccess, IAppInput,
|
ArtistMatching, IApp, IAppAccess, IAppInput,
|
||||||
},
|
},
|
||||||
lib::{
|
lib::{
|
||||||
interface::musicbrainz::{
|
interface::musicbrainz::{
|
||||||
@ -360,30 +352,41 @@ mod tests {
|
|||||||
"00000000-0000-0000-0000-000000000000".try_into().unwrap()
|
"00000000-0000-0000-0000-000000000000".try_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_meta() -> ArtistMeta {
|
fn artist_id() -> ArtistId {
|
||||||
ArtistMeta::new(ArtistId::new("Artist").with_mb_ref(ArtistMbRef::Some(mbid().into())))
|
ArtistId(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_name() -> ArtistName {
|
||||||
|
"Artist".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_meta<Name: Into<ArtistName>>(name: Name) -> ArtistMeta {
|
||||||
|
ArtistMeta::new(name).with_mb_ref(ArtistMbRef::Some(mbid().into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_match() -> EntityMatches {
|
fn artist_match() -> EntityMatches {
|
||||||
let mut artist = artist_meta();
|
let id = artist_id();
|
||||||
|
let name = artist_name();
|
||||||
|
let meta = artist_meta(name.clone());
|
||||||
|
|
||||||
let artist_1 = artist.clone();
|
let artist_1 = meta.clone();
|
||||||
let artist_match_1 = Entity::with_score(artist_1, 100);
|
let artist_match_1 = Entity::with_score(artist_1, 100);
|
||||||
|
|
||||||
let artist_2 = artist.clone();
|
let artist_2 = meta.clone();
|
||||||
let mut artist_match_2 = Entity::with_score(artist_2, 100);
|
let mut artist_match_2 = Entity::with_score(artist_2, 100);
|
||||||
artist_match_2.disambiguation = Some(String::from("some disambiguation"));
|
artist_match_2.disambiguation = Some(String::from("some disambiguation"));
|
||||||
|
|
||||||
artist.clear_mb_ref();
|
|
||||||
let list = vec![artist_match_1.clone(), artist_match_2.clone()];
|
let list = vec![artist_match_1.clone(), artist_match_2.clone()];
|
||||||
EntityMatches::artist_search(artist, list)
|
EntityMatches::artist_search(ArtistMatching::new(id, name), list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_lookup() -> EntityMatches {
|
fn artist_lookup() -> EntityMatches {
|
||||||
let mut artist = artist_meta();
|
let id = artist_id();
|
||||||
artist.clear_mb_ref();
|
let name = artist_name();
|
||||||
|
let artist = artist_meta(name.clone());
|
||||||
|
|
||||||
let lookup = Entity::new(artist.clone());
|
let lookup = Entity::new(artist.clone());
|
||||||
EntityMatches::artist_lookup(artist, lookup)
|
EntityMatches::artist_lookup(ArtistMatching::new(id, name), lookup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_id() -> AlbumId {
|
fn album_id() -> AlbumId {
|
||||||
@ -393,14 +396,18 @@ mod tests {
|
|||||||
fn album_meta(id: AlbumId) -> AlbumMeta {
|
fn album_meta(id: AlbumId) -> AlbumMeta {
|
||||||
AlbumMeta::new(id)
|
AlbumMeta::new(id)
|
||||||
.with_date(AlbumDate::new(Some(1990), Some(5), None))
|
.with_date(AlbumDate::new(Some(1990), Some(5), None))
|
||||||
.with_info(AlbumInfo::new(
|
.with_info(
|
||||||
Some(AlbumPrimaryType::Album),
|
AlbumInfo::default()
|
||||||
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
|
.with_primary_type(AlbumPrimaryType::Album)
|
||||||
))
|
.with_secondary_types(vec![
|
||||||
|
AlbumSecondaryType::Live,
|
||||||
|
AlbumSecondaryType::Compilation,
|
||||||
|
]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_match() -> EntityMatches {
|
fn album_match() -> EntityMatches {
|
||||||
let artist_id = ArtistId::new("Artist");
|
let artist_id = ArtistId(1);
|
||||||
let mut album_id = album_id();
|
let mut album_id = album_id();
|
||||||
let album_meta = album_meta(album_id.clone());
|
let album_meta = album_meta(album_id.clone());
|
||||||
|
|
||||||
@ -418,7 +425,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn album_lookup() -> EntityMatches {
|
fn album_lookup() -> EntityMatches {
|
||||||
let artist_id = ArtistId::new("Artist");
|
let artist_id = ArtistId(1);
|
||||||
let mut album_id = album_id();
|
let mut album_id = album_id();
|
||||||
let album_meta = album_meta(album_id.clone());
|
let album_meta = album_meta(album_id.clone());
|
||||||
|
|
||||||
@ -464,7 +471,7 @@ mod tests {
|
|||||||
|
|
||||||
let collection = vec![];
|
let collection = vec![];
|
||||||
let mut music_hoard = music_hoard(collection.clone());
|
let mut music_hoard = music_hoard(collection.clone());
|
||||||
let artist_id = ArtistId::new("Artist");
|
let artist_id = ArtistId(0);
|
||||||
match matches_info {
|
match matches_info {
|
||||||
EntityMatches::Album(_) => {
|
EntityMatches::Album(_) => {
|
||||||
let album_id = AlbumId::new("Album");
|
let album_id = AlbumId::new("Album");
|
||||||
@ -491,21 +498,12 @@ mod tests {
|
|||||||
.return_once(|_, _, _| Ok(()));
|
.return_once(|_, _, _| Ok(()));
|
||||||
}
|
}
|
||||||
EntityMatches::Artist(_) => {
|
EntityMatches::Artist(_) => {
|
||||||
let mb_ref = MbRefOption::CannotHaveMbid;
|
let info = ArtistInfo::default().with_mb_ref(MbRefOption::CannotHaveMbid);
|
||||||
let info = ArtistInfo::default();
|
|
||||||
|
|
||||||
let mut seq = Sequence::new();
|
|
||||||
music_hoard
|
music_hoard
|
||||||
.expect_merge_artist_info()
|
.expect_merge_artist_info()
|
||||||
.with(eq(artist_id.clone()), eq(info))
|
.with(eq(artist_id.clone()), eq(info))
|
||||||
.times(1)
|
.times(1)
|
||||||
.in_sequence(&mut seq)
|
|
||||||
.return_once(|_, _| Ok(()));
|
|
||||||
music_hoard
|
|
||||||
.expect_set_artist_mb_ref()
|
|
||||||
.with(eq(artist_id.clone()), eq(mb_ref))
|
|
||||||
.times(1)
|
|
||||||
.in_sequence(&mut seq)
|
|
||||||
.return_once(|_, _| Ok(()));
|
.return_once(|_, _| Ok(()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,22 +583,13 @@ mod tests {
|
|||||||
match matches_info {
|
match matches_info {
|
||||||
EntityMatches::Album(_) => panic!(),
|
EntityMatches::Album(_) => panic!(),
|
||||||
EntityMatches::Artist(_) => {
|
EntityMatches::Artist(_) => {
|
||||||
let mut meta = artist_meta();
|
let id = artist_id();
|
||||||
let mb_ref = meta.id.mb_ref.clone();
|
let meta = artist_meta(artist_name());
|
||||||
meta.clear_mb_ref();
|
|
||||||
|
|
||||||
let mut seq = Sequence::new();
|
|
||||||
music_hoard
|
music_hoard
|
||||||
.expect_merge_artist_info()
|
.expect_merge_artist_info()
|
||||||
.with(eq(meta.id.clone()), eq(meta.info))
|
.with(eq(id), eq(meta.info))
|
||||||
.times(1)
|
.times(1)
|
||||||
.in_sequence(&mut seq)
|
|
||||||
.return_once(|_, _| Ok(()));
|
|
||||||
music_hoard
|
|
||||||
.expect_set_artist_mb_ref()
|
|
||||||
.with(eq(meta.id.clone()), eq(mb_ref))
|
|
||||||
.times(1)
|
|
||||||
.in_sequence(&mut seq)
|
|
||||||
.return_once(|_, _| Ok(()));
|
.return_once(|_, _| Ok(()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -619,7 +608,7 @@ mod tests {
|
|||||||
let meta = album_meta(album_id.clone());
|
let meta = album_meta(album_id.clone());
|
||||||
let mb_ref = album_id.mb_ref.clone();
|
let mb_ref = album_id.mb_ref.clone();
|
||||||
album_id.clear_mb_ref();
|
album_id.clear_mb_ref();
|
||||||
let artist = matches.artist.clone();
|
let artist = matches.artist_id;
|
||||||
|
|
||||||
let mut seq = Sequence::new();
|
let mut seq = Sequence::new();
|
||||||
mh.expect_get_collection()
|
mh.expect_get_collection()
|
||||||
@ -673,7 +662,7 @@ mod tests {
|
|||||||
// matching album_id.
|
// matching album_id.
|
||||||
|
|
||||||
// (1) Same artist as matches_info.
|
// (1) Same artist as matches_info.
|
||||||
let mut artist = Artist::new(ArtistId::new("Artist"));
|
let mut artist = Artist::new(1, "Artist");
|
||||||
|
|
||||||
// (2) An album with the same album_id as the selected one.
|
// (2) An album with the same album_id as the selected one.
|
||||||
artist.albums.push(Album::new(AlbumId::new("Album")));
|
artist.albums.push(Album::new(AlbumId::new("Album")));
|
||||||
@ -841,15 +830,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn select_manual_input_artist() {
|
fn select_manual_input_artist() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
let artist = ArtistMeta::new(ArtistId::new("Artist"));
|
let matching = ArtistMatching::new(artist_id(), artist_name());
|
||||||
let requests = VecDeque::from([MbParams::lookup_artist(artist.clone(), mbid())]);
|
let requests = VecDeque::from([MbParams::lookup_artist(matching.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<Entity<ArtistMeta>> = vec![];
|
let matches_vec: Vec<Entity<ArtistMeta>> = vec![];
|
||||||
let artist_match = EntityMatches::artist_search(artist.clone(), matches_vec);
|
let artist_match = EntityMatches::artist_search(matching.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(artist_match),
|
match_state(artist_match),
|
||||||
@ -869,7 +858,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn select_manual_input_album() {
|
fn select_manual_input_album() {
|
||||||
let mut mb_job_sender = MockIMbJobSender::new();
|
let mut mb_job_sender = MockIMbJobSender::new();
|
||||||
let artist_id = ArtistId::new("Artist");
|
let artist_id = artist_id();
|
||||||
let album = AlbumMeta::new("Album").with_date(1990);
|
let album = AlbumMeta::new("Album").with_date(1990);
|
||||||
let requests = VecDeque::from([MbParams::lookup_release_group(
|
let requests = VecDeque::from([MbParams::lookup_release_group(
|
||||||
artist_id.clone(),
|
artist_id.clone(),
|
||||||
|
@ -225,7 +225,10 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppState, EntityMatches, IApp, IAppInput, IAppInteractBrowse, InputEvent},
|
app::{
|
||||||
|
AppState, ArtistMatching, EntityMatches, IApp, IAppInput, IAppInteractBrowse,
|
||||||
|
InputEvent,
|
||||||
|
},
|
||||||
lib::{
|
lib::{
|
||||||
interface::musicbrainz::{api::Entity, daemon::MockIMbJobSender},
|
interface::musicbrainz::{api::Entity, daemon::MockIMbJobSender},
|
||||||
MockIMusicHoard,
|
MockIMusicHoard,
|
||||||
@ -519,8 +522,13 @@ mod tests {
|
|||||||
|
|
||||||
let (_, rx) = mpsc::channel();
|
let (_, rx) = mpsc::channel();
|
||||||
let fetch = FetchState::search(rx);
|
let fetch = FetchState::search(rx);
|
||||||
let artist = ArtistMeta::new(ArtistId::new("Artist"));
|
|
||||||
let info = EntityMatches::artist_lookup(artist.clone(), Entity::new(artist.clone()));
|
let id = ArtistId(1);
|
||||||
|
let name = String::from("Artist");
|
||||||
|
let meta = ArtistMeta::new(name.clone());
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
|
||||||
|
let info = EntityMatches::artist_lookup(matching, Entity::new(meta));
|
||||||
app =
|
app =
|
||||||
AppMachine::match_state(app.unwrap_browse().inner, MatchState::new(info, fetch)).into();
|
AppMachine::match_state(app.unwrap_browse().inner, MatchState::new(info, fetch)).into();
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ impl IAppInteractSearchPrivate for AppMachine<SearchState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn predicate_artists(case_sens: bool, char_sens: bool, search: &str, probe: &Artist) -> bool {
|
fn predicate_artists(case_sens: bool, char_sens: bool, search: &str, probe: &Artist) -> bool {
|
||||||
let name = string::normalize_string_with(&probe.meta.id.name, !case_sens, !char_sens);
|
let name = string::normalize_string_with(&probe.meta.name, !case_sens, !char_sens);
|
||||||
let mut result = name.string.starts_with(search);
|
let mut result = name.string.starts_with(search);
|
||||||
|
|
||||||
if let Some(ref probe_sort) = probe.meta.sort {
|
if let Some(ref probe_sort) = probe.meta.sort {
|
||||||
|
@ -7,7 +7,7 @@ pub use selection::{Category, Selection};
|
|||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{AlbumId, AlbumMeta},
|
album::{AlbumId, AlbumMeta},
|
||||||
artist::{ArtistId, ArtistMeta},
|
artist::{ArtistId, ArtistMeta, ArtistName},
|
||||||
Collection,
|
Collection,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -228,13 +228,28 @@ impl<T> From<Entity<T>> for MatchOption<T> {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ArtistMatches {
|
pub struct ArtistMatches {
|
||||||
pub matching: ArtistMeta,
|
pub matching: ArtistMatching,
|
||||||
pub list: Vec<MatchOption<ArtistMeta>>,
|
pub list: Vec<MatchOption<ArtistMeta>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ArtistMatching {
|
||||||
|
pub id: ArtistId,
|
||||||
|
pub name: ArtistName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArtistMatching {
|
||||||
|
pub fn new<Name: Into<ArtistName>>(id: ArtistId, name: Name) -> Self {
|
||||||
|
ArtistMatching {
|
||||||
|
id,
|
||||||
|
name: name.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AlbumMatches {
|
pub struct AlbumMatches {
|
||||||
pub artist: ArtistId,
|
pub artist_id: ArtistId,
|
||||||
pub matching: AlbumId,
|
pub matching: AlbumId,
|
||||||
pub list: Vec<MatchOption<AlbumMeta>>,
|
pub list: Vec<MatchOption<AlbumMeta>>,
|
||||||
}
|
}
|
||||||
@ -246,40 +261,43 @@ pub enum EntityMatches {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityMatches {
|
impl EntityMatches {
|
||||||
pub fn artist_search<M: Into<MatchOption<ArtistMeta>>>(
|
pub fn artist_search<M>(matching: ArtistMatching, list: Vec<M>) -> Self
|
||||||
matching: ArtistMeta,
|
where
|
||||||
list: Vec<M>,
|
M: Into<MatchOption<ArtistMeta>>,
|
||||||
) -> Self {
|
{
|
||||||
let list = list.into_iter().map(Into::into).collect();
|
let list = list.into_iter().map(Into::into).collect();
|
||||||
EntityMatches::Artist(ArtistMatches { matching, list })
|
EntityMatches::Artist(ArtistMatches { matching, list })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn album_search<M: Into<MatchOption<AlbumMeta>>>(
|
pub fn album_search<M: Into<MatchOption<AlbumMeta>>>(
|
||||||
artist: ArtistId,
|
artist_id: ArtistId,
|
||||||
matching: AlbumId,
|
matching: AlbumId,
|
||||||
list: Vec<M>,
|
list: Vec<M>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let list = list.into_iter().map(Into::into).collect();
|
let list = list.into_iter().map(Into::into).collect();
|
||||||
EntityMatches::Album(AlbumMatches {
|
EntityMatches::Album(AlbumMatches {
|
||||||
artist,
|
artist_id,
|
||||||
matching,
|
matching,
|
||||||
list,
|
list,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn artist_lookup<M: Into<MatchOption<ArtistMeta>>>(matching: ArtistMeta, item: M) -> Self {
|
pub fn artist_lookup<M>(matching: ArtistMatching, item: M) -> Self
|
||||||
|
where
|
||||||
|
M: Into<MatchOption<ArtistMeta>>,
|
||||||
|
{
|
||||||
let list = vec![item.into()];
|
let list = vec![item.into()];
|
||||||
EntityMatches::Artist(ArtistMatches { matching, list })
|
EntityMatches::Artist(ArtistMatches { matching, list })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn album_lookup<M: Into<MatchOption<AlbumMeta>>>(
|
pub fn album_lookup<M: Into<MatchOption<AlbumMeta>>>(
|
||||||
artist: ArtistId,
|
artist_id: ArtistId,
|
||||||
matching: AlbumId,
|
matching: AlbumId,
|
||||||
item: M,
|
item: M,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let list = vec![item.into()];
|
let list = vec![item.into()];
|
||||||
EntityMatches::Album(AlbumMatches {
|
EntityMatches::Album(AlbumMatches {
|
||||||
artist,
|
artist_id,
|
||||||
matching,
|
matching,
|
||||||
list,
|
list,
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{album::Album, artist::Artist, track::Track};
|
||||||
album::Album,
|
|
||||||
artist::{Artist, ArtistId},
|
|
||||||
track::Track,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::tui::app::{
|
use crate::tui::app::{
|
||||||
selection::{
|
selection::{
|
||||||
@ -198,7 +194,7 @@ impl ArtistSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct KeySelectArtist {
|
pub struct KeySelectArtist {
|
||||||
key: (ArtistId,),
|
key: (String,),
|
||||||
album: Option<KeySelectAlbum>,
|
album: Option<KeySelectAlbum>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +211,7 @@ impl KeySelectArtist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sort_key(&self) -> (&str,) {
|
pub fn get_sort_key(&self) -> (&str,) {
|
||||||
(&self.key.0.name,)
|
(&self.key.0,)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use std::collections::HashMap;
|
|||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{AlbumDate, AlbumId, AlbumInfo, AlbumLibId, AlbumMbRef, AlbumMeta, AlbumSeq},
|
album::{AlbumDate, AlbumId, AlbumInfo, AlbumLibId, AlbumMbRef, AlbumMeta, AlbumSeq},
|
||||||
artist::{ArtistId, ArtistInfo, ArtistMbRef, ArtistMeta},
|
artist::{ArtistInfo, ArtistMbRef, ArtistMeta, ArtistName},
|
||||||
musicbrainz::Mbid,
|
musicbrainz::Mbid,
|
||||||
},
|
},
|
||||||
external::musicbrainz::{
|
external::musicbrainz::{
|
||||||
@ -55,8 +55,8 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
|||||||
Ok(from_lookup_release_group_response(mb_response))
|
Ok(from_lookup_release_group_response(mb_response))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Entity<ArtistMeta>>, Error> {
|
fn search_artist(&mut self, name: &ArtistName) -> Result<Vec<Entity<ArtistMeta>>, Error> {
|
||||||
let query = SearchArtistRequest::new().string(&artist.id.name);
|
let query = SearchArtistRequest::new().string(name);
|
||||||
|
|
||||||
let paging = PageSettings::default();
|
let paging = PageSettings::default();
|
||||||
let mb_response = self.client.search_artist(&query, &paging)?;
|
let mb_response = self.client.search_artist(&query, &paging)?;
|
||||||
@ -70,19 +70,19 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
|||||||
|
|
||||||
fn search_release_group(
|
fn search_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
arid: &Mbid,
|
artist_mbid: &Mbid,
|
||||||
album: &AlbumMeta,
|
meta: &AlbumMeta,
|
||||||
) -> Result<Vec<Entity<AlbumMeta>>, Error> {
|
) -> Result<Vec<Entity<AlbumMeta>>, Error> {
|
||||||
// Some release groups may have a promotional early release messing up the search. Searching
|
// Some release groups may have a promotional early release messing up the search. Searching
|
||||||
// with just the year should be enough anyway.
|
// with just the year should be enough anyway.
|
||||||
let date = AlbumDate::new(album.date.year, None, None);
|
let date = AlbumDate::new(meta.info.date.year, None, None);
|
||||||
|
|
||||||
let query = SearchReleaseGroupRequest::new()
|
let query = SearchReleaseGroupRequest::new()
|
||||||
.arid(arid)
|
.arid(artist_mbid)
|
||||||
.and()
|
.and()
|
||||||
.first_release_date(&date)
|
.first_release_date(&date)
|
||||||
.and()
|
.and()
|
||||||
.release_group(&album.id.title);
|
.release_group(&meta.id.title);
|
||||||
|
|
||||||
let paging = PageSettings::default();
|
let paging = PageSettings::default();
|
||||||
let mb_response = self.client.search_release_group(&query, &paging)?;
|
let mb_response = self.client.search_release_group(&query, &paging)?;
|
||||||
@ -96,10 +96,11 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
|||||||
|
|
||||||
fn browse_release_group(
|
fn browse_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist: &Mbid,
|
artist_mbid: &Mbid,
|
||||||
paging: &mut Option<PageSettings>,
|
paging: &mut Option<PageSettings>,
|
||||||
) -> Result<Vec<Entity<AlbumMeta>>, Error> {
|
) -> Result<Vec<Entity<AlbumMeta>>, Error> {
|
||||||
let request = BrowseReleaseGroupRequest::artist(artist).filter_status_website_default();
|
let request =
|
||||||
|
BrowseReleaseGroupRequest::artist(artist_mbid).filter_status_website_default();
|
||||||
|
|
||||||
let page = paging.take().unwrap_or_default();
|
let page = paging.take().unwrap_or_default();
|
||||||
let mb_response = self.client.browse_release_group(&request, &page)?;
|
let mb_response = self.client.browse_release_group(&request, &page)?;
|
||||||
@ -115,12 +116,10 @@ fn from_mb_artist_meta(meta: MbArtistMeta) -> (ArtistMeta, Option<String>) {
|
|||||||
let sort = Some(meta.sort_name).filter(|s| s != &meta.name);
|
let sort = Some(meta.sort_name).filter(|s| s != &meta.name);
|
||||||
(
|
(
|
||||||
ArtistMeta {
|
ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
mb_ref: ArtistMbRef::Some(meta.id.into()),
|
|
||||||
},
|
|
||||||
sort,
|
sort,
|
||||||
info: ArtistInfo {
|
info: ArtistInfo {
|
||||||
|
mb_ref: ArtistMbRef::Some(meta.id.into()),
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -135,9 +134,9 @@ fn from_mb_release_group_meta(meta: MbReleaseGroupMeta) -> AlbumMeta {
|
|||||||
lib_id: AlbumLibId::None,
|
lib_id: AlbumLibId::None,
|
||||||
mb_ref: AlbumMbRef::Some(meta.id.into()),
|
mb_ref: AlbumMbRef::Some(meta.id.into()),
|
||||||
},
|
},
|
||||||
|
info: AlbumInfo {
|
||||||
date: meta.first_release_date,
|
date: meta.first_release_date,
|
||||||
seq: AlbumSeq::default(),
|
seq: AlbumSeq::default(),
|
||||||
info: AlbumInfo {
|
|
||||||
primary_type: meta.primary_type,
|
primary_type: meta.primary_type,
|
||||||
secondary_types: meta.secondary_types.unwrap_or_default(),
|
secondary_types: meta.secondary_types.unwrap_or_default(),
|
||||||
},
|
},
|
||||||
|
@ -256,30 +256,26 @@ impl JobInstance {
|
|||||||
MbParams::Lookup(lookup) => match lookup {
|
MbParams::Lookup(lookup) => match lookup {
|
||||||
LookupParams::Artist(p) => musicbrainz
|
LookupParams::Artist(p) => musicbrainz
|
||||||
.lookup_artist(&p.mbid)
|
.lookup_artist(&p.mbid)
|
||||||
.map(|rv| EntityMatches::artist_lookup(p.artist.clone(), rv)),
|
.map(|rv| EntityMatches::artist_lookup(p.matching.clone(), rv)),
|
||||||
LookupParams::ReleaseGroup(p) => {
|
LookupParams::ReleaseGroup(p) => musicbrainz
|
||||||
musicbrainz.lookup_release_group(&p.mbid).map(|rv| {
|
.lookup_release_group(&p.mbid)
|
||||||
EntityMatches::album_lookup(p.artist_id.clone(), p.album_id.clone(), rv)
|
.map(|rv| EntityMatches::album_lookup(p.artist_id, p.id.clone(), rv)),
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.map(MbReturn::Match),
|
.map(MbReturn::Match),
|
||||||
MbParams::Search(search) => match search {
|
MbParams::Search(search) => match search {
|
||||||
SearchParams::Artist(p) => musicbrainz
|
SearchParams::Artist(p) => musicbrainz
|
||||||
.search_artist(&p.artist)
|
.search_artist(&p.matching.name)
|
||||||
.map(|rv| EntityMatches::artist_search(p.artist.clone(), rv)),
|
.map(|rv| EntityMatches::artist_search(p.matching.clone(), rv)),
|
||||||
SearchParams::ReleaseGroup(p) => musicbrainz
|
SearchParams::ReleaseGroup(p) => musicbrainz
|
||||||
.search_release_group(&p.artist_mbid, &p.album)
|
.search_release_group(&p.artist_mbid, &p.meta)
|
||||||
.map(|rv| {
|
.map(|rv| EntityMatches::album_search(p.artist_id, p.meta.id.clone(), rv)),
|
||||||
EntityMatches::album_search(p.artist_id.clone(), p.album.id.clone(), rv)
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
.map(MbReturn::Match),
|
.map(MbReturn::Match),
|
||||||
MbParams::Browse(browse) => match browse {
|
MbParams::Browse(browse) => match browse {
|
||||||
BrowseParams::ReleaseGroup(params) => {
|
BrowseParams::ReleaseGroup(params) => {
|
||||||
Self::init_paging_if_none(paging);
|
Self::init_paging_if_none(paging);
|
||||||
musicbrainz
|
musicbrainz
|
||||||
.browse_release_group(¶ms.artist, paging)
|
.browse_release_group(¶ms.artist_mbid, paging)
|
||||||
.map(|rv| EntityList::Album(rv.into_iter().map(|rg| rg.entity).collect()))
|
.map(|rv| EntityList::Album(rv.into_iter().map(|rg| rg.entity).collect()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,11 +346,12 @@ mod tests {
|
|||||||
use mockall::{predicate, Sequence};
|
use mockall::{predicate, Sequence};
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::AlbumMeta,
|
album::AlbumMeta,
|
||||||
artist::{ArtistId, ArtistMeta},
|
artist::{ArtistId, ArtistMeta, ArtistName},
|
||||||
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
|
musicbrainz::{IMusicBrainzRef, MbRefOption, Mbid},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
|
app::ArtistMatching,
|
||||||
event::{Event, EventError, MockIFetchCompleteEventSender},
|
event::{Event, EventError, MockIFetchCompleteEventSender},
|
||||||
lib::interface::musicbrainz::api::{Entity, MockIMusicBrainz},
|
lib::interface::musicbrainz::api::{Entity, MockIMusicBrainz},
|
||||||
testmod::COLLECTION,
|
testmod::COLLECTION,
|
||||||
@ -426,38 +423,43 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_artist_requests() -> VecDeque<MbParams> {
|
fn lookup_artist_requests() -> VecDeque<MbParams> {
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
let mbid = mbid();
|
let mbid = mbid();
|
||||||
VecDeque::from([MbParams::lookup_artist(artist, mbid)])
|
VecDeque::from([MbParams::lookup_artist(matching, mbid)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_release_group_requests() -> VecDeque<MbParams> {
|
fn lookup_release_group_requests() -> VecDeque<MbParams> {
|
||||||
let artist_id = COLLECTION[1].meta.id.clone();
|
let artist_id = COLLECTION[1].id;
|
||||||
let album_id = COLLECTION[1].albums[0].meta.id.clone();
|
let album_id = COLLECTION[1].albums[0].meta.id.clone();
|
||||||
let mbid = mbid();
|
let mbid = mbid();
|
||||||
VecDeque::from([MbParams::lookup_release_group(artist_id, album_id, mbid)])
|
VecDeque::from([MbParams::lookup_release_group(artist_id, album_id, mbid)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist_requests() -> VecDeque<MbParams> {
|
fn search_artist_requests() -> VecDeque<MbParams> {
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
VecDeque::from([MbParams::search_artist(artist)])
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
VecDeque::from([MbParams::search_artist(matching)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_artist_expectations() -> (ArtistMeta, Vec<Entity<ArtistMeta>>) {
|
fn search_artist_expectations() -> (ArtistName, Vec<Entity<ArtistMeta>>) {
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let meta = COLLECTION[3].meta.clone();
|
||||||
|
|
||||||
let artist_match_1 = Entity::with_score(artist.clone(), 100);
|
let artist_match_1 = Entity::with_score(meta.clone(), 100);
|
||||||
let artist_match_2 = Entity::with_score(artist.clone(), 50);
|
let artist_match_2 = Entity::with_score(meta.clone(), 50);
|
||||||
let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
|
let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
|
||||||
|
|
||||||
(artist, matches)
|
(name, matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_albums_requests() -> VecDeque<MbParams> {
|
fn search_albums_requests() -> VecDeque<MbParams> {
|
||||||
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.id.mb_ref);
|
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.info.mb_ref);
|
||||||
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 artist_id = COLLECTION[1].id;
|
||||||
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();
|
||||||
|
|
||||||
@ -468,17 +470,21 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn browse_albums_requests() -> VecDeque<MbParams> {
|
fn browse_albums_requests() -> VecDeque<MbParams> {
|
||||||
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.id.mb_ref);
|
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.info.mb_ref);
|
||||||
let arid = mb_ref_opt_unwrap(mbref).mbid().clone();
|
let arid = mb_ref_opt_unwrap(mbref).mbid().clone();
|
||||||
VecDeque::from([MbParams::browse_release_group(arid)])
|
VecDeque::from([MbParams::browse_release_group(album_artist_id(), arid)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_id() -> ArtistId {
|
||||||
|
ArtistId(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_artist_id() -> ArtistId {
|
fn album_artist_id() -> ArtistId {
|
||||||
COLLECTION[1].meta.id.clone()
|
COLLECTION[1].id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_arid_expectation() -> Mbid {
|
fn album_arid_expectation() -> Mbid {
|
||||||
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.id.mb_ref);
|
let mbref = mb_ref_opt_as_ref(&COLLECTION[1].meta.info.mb_ref);
|
||||||
mb_ref_opt_unwrap(mbref).mbid().clone()
|
mb_ref_opt_unwrap(mbref).mbid().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,8 +600,12 @@ mod tests {
|
|||||||
fn execute_lookup_artist() {
|
fn execute_lookup_artist() {
|
||||||
let mut musicbrainz = musicbrainz();
|
let mut musicbrainz = musicbrainz();
|
||||||
let mbid = mbid();
|
let mbid = mbid();
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let id = COLLECTION[3].id;
|
||||||
let lookup = Entity::new(artist.clone());
|
let name = COLLECTION[3].meta.name.clone();
|
||||||
|
let meta = COLLECTION[3].meta.clone();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
|
||||||
|
let lookup = Entity::new(meta.clone());
|
||||||
lookup_artist_expectation(&mut musicbrainz, &mbid, &lookup);
|
lookup_artist_expectation(&mut musicbrainz, &mbid, &lookup);
|
||||||
|
|
||||||
let mut event_sender = event_sender();
|
let mut event_sender = event_sender();
|
||||||
@ -622,7 +632,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
Ok(MbReturn::Match(EntityMatches::artist_lookup(
|
Ok(MbReturn::Match(EntityMatches::artist_lookup(
|
||||||
artist, lookup
|
matching, lookup
|
||||||
)))
|
)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -681,13 +691,13 @@ mod tests {
|
|||||||
|
|
||||||
fn search_artist_expectation(
|
fn search_artist_expectation(
|
||||||
musicbrainz: &mut MockIMusicBrainz,
|
musicbrainz: &mut MockIMusicBrainz,
|
||||||
artist: &ArtistMeta,
|
name: &ArtistName,
|
||||||
matches: &[Entity<ArtistMeta>],
|
matches: &[Entity<ArtistMeta>],
|
||||||
) {
|
) {
|
||||||
let result = Ok(matches.to_owned());
|
let result = Ok(matches.to_owned());
|
||||||
musicbrainz
|
musicbrainz
|
||||||
.expect_search_artist()
|
.expect_search_artist()
|
||||||
.with(predicate::eq(artist.clone()))
|
.with(predicate::eq(name.clone()))
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| result);
|
.return_once(|_| result);
|
||||||
}
|
}
|
||||||
@ -695,8 +705,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn execute_search_artist() {
|
fn execute_search_artist() {
|
||||||
let mut musicbrainz = musicbrainz();
|
let mut musicbrainz = musicbrainz();
|
||||||
let (artist, matches) = search_artist_expectations();
|
let id = artist_id();
|
||||||
search_artist_expectation(&mut musicbrainz, &artist, &matches);
|
let (name, matches) = search_artist_expectations();
|
||||||
|
search_artist_expectation(&mut musicbrainz, &name, &matches);
|
||||||
|
|
||||||
let mut event_sender = event_sender();
|
let mut event_sender = event_sender();
|
||||||
fetch_complete_expectation(&mut event_sender, 1);
|
fetch_complete_expectation(&mut event_sender, 1);
|
||||||
@ -719,10 +730,11 @@ mod tests {
|
|||||||
assert_eq!(result, Err(JobError::JobQueueEmpty));
|
assert_eq!(result, Err(JobError::JobQueueEmpty));
|
||||||
|
|
||||||
let result = result_receiver.try_recv().unwrap();
|
let result = result_receiver.try_recv().unwrap();
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
Ok(MbReturn::Match(EntityMatches::artist_search(
|
Ok(MbReturn::Match(EntityMatches::artist_search(
|
||||||
artist, matches
|
matching, matches
|
||||||
)))
|
)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,11 @@
|
|||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
|
|
||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid},
|
collection::{
|
||||||
|
album::AlbumMeta,
|
||||||
|
artist::{ArtistMeta, ArtistName},
|
||||||
|
musicbrainz::Mbid,
|
||||||
|
},
|
||||||
external::musicbrainz::api::PageSettings,
|
external::musicbrainz::api::PageSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -12,15 +16,16 @@ use musichoard::{
|
|||||||
pub trait IMusicBrainz {
|
pub trait IMusicBrainz {
|
||||||
fn lookup_artist(&mut self, mbid: &Mbid) -> Result<Entity<ArtistMeta>, Error>;
|
fn lookup_artist(&mut self, mbid: &Mbid) -> Result<Entity<ArtistMeta>, Error>;
|
||||||
fn lookup_release_group(&mut self, mbid: &Mbid) -> Result<Entity<AlbumMeta>, Error>;
|
fn lookup_release_group(&mut self, mbid: &Mbid) -> Result<Entity<AlbumMeta>, Error>;
|
||||||
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Entity<ArtistMeta>>, Error>;
|
fn search_artist(&mut self, name: &ArtistName) -> Result<Vec<Entity<ArtistMeta>>, Error>;
|
||||||
|
// TODO: AlbumMeta -> AlbumTitle
|
||||||
fn search_release_group(
|
fn search_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
arid: &Mbid,
|
artist_mbid: &Mbid,
|
||||||
album: &AlbumMeta,
|
meta: &AlbumMeta,
|
||||||
) -> Result<Vec<Entity<AlbumMeta>>, Error>;
|
) -> Result<Vec<Entity<AlbumMeta>>, Error>;
|
||||||
fn browse_release_group(
|
fn browse_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist: &Mbid,
|
artist_mbid: &Mbid,
|
||||||
paging: &mut Option<PageSettings>,
|
paging: &mut Option<PageSettings>,
|
||||||
) -> Result<Vec<Entity<AlbumMeta>>, Error>;
|
) -> Result<Vec<Entity<AlbumMeta>>, Error>;
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,14 @@ use std::{collections::VecDeque, fmt, sync::mpsc};
|
|||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{AlbumId, AlbumMeta},
|
album::{AlbumId, AlbumMeta},
|
||||||
artist::{ArtistId, ArtistMeta},
|
artist::ArtistId,
|
||||||
musicbrainz::Mbid,
|
musicbrainz::Mbid,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{app::EntityMatches, lib::interface::musicbrainz::api::Error as MbApiError};
|
use crate::tui::{
|
||||||
|
app::{ArtistMatching, EntityMatches},
|
||||||
|
lib::interface::musicbrainz::api::Error as MbApiError,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
@ -74,14 +77,14 @@ pub enum LookupParams {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct LookupArtistParams {
|
pub struct LookupArtistParams {
|
||||||
pub artist: ArtistMeta,
|
pub matching: ArtistMatching,
|
||||||
pub mbid: Mbid,
|
pub mbid: Mbid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct LookupReleaseGroupParams {
|
pub struct LookupReleaseGroupParams {
|
||||||
pub artist_id: ArtistId,
|
pub artist_id: ArtistId,
|
||||||
pub album_id: AlbumId,
|
pub id: AlbumId,
|
||||||
pub mbid: Mbid,
|
pub mbid: Mbid,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,14 +96,15 @@ pub enum SearchParams {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SearchArtistParams {
|
pub struct SearchArtistParams {
|
||||||
pub artist: ArtistMeta,
|
pub matching: ArtistMatching,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SearchReleaseGroupParams {
|
pub struct SearchReleaseGroupParams {
|
||||||
pub artist_id: ArtistId,
|
pub artist_id: ArtistId,
|
||||||
pub artist_mbid: Mbid,
|
pub artist_mbid: Mbid,
|
||||||
pub album: AlbumMeta,
|
// TODO: probably needs AlbumId when we get there
|
||||||
|
pub meta: AlbumMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -110,37 +114,39 @@ pub enum BrowseParams {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct BrowseReleaseGroupParams {
|
pub struct BrowseReleaseGroupParams {
|
||||||
pub artist: Mbid,
|
pub artist_id: ArtistId,
|
||||||
|
pub artist_mbid: Mbid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MbParams {
|
impl MbParams {
|
||||||
pub fn lookup_artist(artist: ArtistMeta, mbid: Mbid) -> Self {
|
pub fn lookup_artist(matching: ArtistMatching, mbid: Mbid) -> Self {
|
||||||
MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid }))
|
MbParams::Lookup(LookupParams::Artist(LookupArtistParams { matching, mbid }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_release_group(artist_id: ArtistId, album_id: AlbumId, mbid: Mbid) -> Self {
|
pub fn lookup_release_group(artist_id: ArtistId, id: AlbumId, mbid: Mbid) -> Self {
|
||||||
MbParams::Lookup(LookupParams::ReleaseGroup(LookupReleaseGroupParams {
|
MbParams::Lookup(LookupParams::ReleaseGroup(LookupReleaseGroupParams {
|
||||||
artist_id,
|
artist_id,
|
||||||
album_id,
|
id,
|
||||||
mbid,
|
mbid,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_artist(artist: ArtistMeta) -> Self {
|
pub fn search_artist(matching: ArtistMatching) -> Self {
|
||||||
MbParams::Search(SearchParams::Artist(SearchArtistParams { artist }))
|
MbParams::Search(SearchParams::Artist(SearchArtistParams { matching }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_release_group(artist_id: ArtistId, artist_mbid: Mbid, album: AlbumMeta) -> Self {
|
pub fn search_release_group(artist_id: ArtistId, artist_mbid: Mbid, meta: AlbumMeta) -> Self {
|
||||||
MbParams::Search(SearchParams::ReleaseGroup(SearchReleaseGroupParams {
|
MbParams::Search(SearchParams::ReleaseGroup(SearchReleaseGroupParams {
|
||||||
artist_id,
|
artist_id,
|
||||||
artist_mbid,
|
artist_mbid,
|
||||||
album,
|
meta,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn browse_release_group(artist: Mbid) -> Self {
|
pub fn browse_release_group(artist_id: ArtistId, artist_mbid: Mbid) -> Self {
|
||||||
MbParams::Browse(BrowseParams::ReleaseGroup(BrowseReleaseGroupParams {
|
MbParams::Browse(BrowseParams::ReleaseGroup(BrowseReleaseGroupParams {
|
||||||
artist,
|
artist_id,
|
||||||
|
artist_mbid,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ pub mod interface;
|
|||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{AlbumId, AlbumInfo, AlbumMbRef, AlbumMeta},
|
album::{AlbumId, AlbumInfo, AlbumMbRef, AlbumMeta},
|
||||||
artist::{ArtistId, ArtistInfo, ArtistMbRef},
|
artist::{ArtistId, ArtistInfo},
|
||||||
Collection,
|
Collection,
|
||||||
},
|
},
|
||||||
interface::{database::IDatabase, library::ILibrary},
|
interface::{database::IDatabase, library::ILibrary},
|
||||||
@ -33,11 +33,6 @@ pub trait IMusicHoard {
|
|||||||
album_id: &AlbumId,
|
album_id: &AlbumId,
|
||||||
) -> Result<(), musichoard::Error>;
|
) -> Result<(), musichoard::Error>;
|
||||||
|
|
||||||
fn set_artist_mb_ref(
|
|
||||||
&mut self,
|
|
||||||
artist_id: &ArtistId,
|
|
||||||
mb_ref: ArtistMbRef,
|
|
||||||
) -> Result<(), musichoard::Error>;
|
|
||||||
fn merge_artist_info(
|
fn merge_artist_info(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &ArtistId,
|
id: &ArtistId,
|
||||||
@ -91,14 +86,6 @@ impl<Database: IDatabase, Library: ILibrary> IMusicHoard for MusicHoard<Database
|
|||||||
<Self as IMusicHoardDatabase>::remove_album(self, artist_id, album_id)
|
<Self as IMusicHoardDatabase>::remove_album(self, artist_id, album_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_artist_mb_ref(
|
|
||||||
&mut self,
|
|
||||||
artist_id: &ArtistId,
|
|
||||||
mb_ref: ArtistMbRef,
|
|
||||||
) -> Result<(), musichoard::Error> {
|
|
||||||
<Self as IMusicHoardDatabase>::set_artist_mb_ref(self, artist_id, mb_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merge_artist_info(
|
fn merge_artist_info(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &ArtistId,
|
id: &ArtistId,
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{
|
album::{Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMbRef, AlbumMeta, AlbumPrimaryType},
|
||||||
Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMbRef, AlbumMeta, AlbumPrimaryType, AlbumSeq,
|
|
||||||
},
|
|
||||||
artist::{Artist, ArtistId, ArtistInfo, ArtistMbRef, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistInfo, ArtistMbRef, ArtistMeta},
|
||||||
musicbrainz::{MbAlbumRef, MbArtistRef},
|
musicbrainz::{MbAlbumRef, MbArtistRef},
|
||||||
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
|
||||||
|
@ -126,7 +126,7 @@ impl<'a, 'b> ArtistState<'a, 'b> {
|
|||||||
let list = List::new(
|
let list = List::new(
|
||||||
artists
|
artists
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| ListItem::new(a.meta.id.name.as_str()))
|
.map(|a| ListItem::new(a.meta.name.as_str()))
|
||||||
.collect::<Vec<ListItem>>(),
|
.collect::<Vec<ListItem>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ impl<'a, 'b> AlbumState<'a, 'b> {
|
|||||||
Ownership: {}",
|
Ownership: {}",
|
||||||
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_date(&a.meta.date, &a.meta.seq))
|
.map(|a| UiDisplay::display_date(&a.meta.info.date, &a.meta.info.seq))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
album
|
album
|
||||||
.map(|a| UiDisplay::display_album_type(
|
.map(|a| UiDisplay::display_album_type(
|
||||||
|
@ -3,7 +3,7 @@ use musichoard::collection::{
|
|||||||
AlbumDate, AlbumId, AlbumLibId, AlbumMeta, AlbumOwnership, AlbumPrimaryType,
|
AlbumDate, AlbumId, AlbumLibId, AlbumMeta, AlbumOwnership, AlbumPrimaryType,
|
||||||
AlbumSecondaryType, AlbumSeq,
|
AlbumSecondaryType, AlbumSeq,
|
||||||
},
|
},
|
||||||
artist::ArtistMeta,
|
artist::{ArtistMeta, ArtistName},
|
||||||
musicbrainz::{IMusicBrainzRef, MbRefOption},
|
musicbrainz::{IMusicBrainzRef, MbRefOption},
|
||||||
track::{TrackFormat, TrackQuality},
|
track::{TrackFormat, TrackQuality},
|
||||||
};
|
};
|
||||||
@ -118,8 +118,8 @@ impl UiDisplay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_artist_matching(artist: &ArtistMeta) -> String {
|
pub fn display_artist_matching(name: &ArtistName) -> String {
|
||||||
format!("Matching artist: {}", &artist.id.name)
|
format!("Matching artist: {}", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_album_matching(album: &AlbumId) -> String {
|
pub fn display_album_matching(album: &AlbumId) -> String {
|
||||||
@ -128,7 +128,7 @@ impl UiDisplay {
|
|||||||
|
|
||||||
pub fn display_matching_info(info: &EntityMatches) -> String {
|
pub fn display_matching_info(info: &EntityMatches) -> String {
|
||||||
match info {
|
match info {
|
||||||
EntityMatches::Artist(m) => UiDisplay::display_artist_matching(&m.matching),
|
EntityMatches::Artist(m) => UiDisplay::display_artist_matching(&m.matching.name),
|
||||||
EntityMatches::Album(m) => UiDisplay::display_album_matching(&m.matching),
|
EntityMatches::Album(m) => UiDisplay::display_album_matching(&m.matching),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ impl UiDisplay {
|
|||||||
fn display_option_artist(artist: &ArtistMeta, disambiguation: &Option<String>) -> String {
|
fn display_option_artist(artist: &ArtistMeta, disambiguation: &Option<String>) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
artist.id.name,
|
artist.name,
|
||||||
disambiguation
|
disambiguation
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
@ -171,7 +171,7 @@ impl UiDisplay {
|
|||||||
fn display_option_album(album: &AlbumMeta, _disambiguation: &Option<String>) -> String {
|
fn display_option_album(album: &AlbumMeta, _disambiguation: &Option<String>) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{:010} | {} [{}]",
|
"{:010} | {} [{}]",
|
||||||
UiDisplay::display_album_date(&album.date),
|
UiDisplay::display_album_date(&album.info.date),
|
||||||
album.id.title,
|
album.id.title,
|
||||||
UiDisplay::display_album_type(&album.info.primary_type, &album.info.secondary_types),
|
UiDisplay::display_album_type(&album.info.primary_type, &album.info.secondary_types),
|
||||||
)
|
)
|
||||||
|
@ -74,9 +74,9 @@ impl<'a> ArtistOverlay<'a> {
|
|||||||
"Artist: {}\n\n{item_indent}\
|
"Artist: {}\n\n{item_indent}\
|
||||||
MusicBrainz: {}\n{item_indent}\
|
MusicBrainz: {}\n{item_indent}\
|
||||||
Properties: {}",
|
Properties: {}",
|
||||||
artist.map(|a| a.meta.id.name.as_str()).unwrap_or(""),
|
artist.map(|a| a.meta.name.as_str()).unwrap_or(""),
|
||||||
artist
|
artist
|
||||||
.map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.id.mb_ref))
|
.map(|a| UiDisplay::display_mb_ref_option_as_url(&a.meta.info.mb_ref))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
Self::opt_hashmap_to_string(
|
Self::opt_hashmap_to_string(
|
||||||
artist.map(|a| &a.meta.info.properties),
|
artist.map(|a| &a.meta.info.properties),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{AlbumId, AlbumMeta},
|
album::{AlbumId, AlbumMeta},
|
||||||
artist::ArtistMeta,
|
artist::{ArtistMeta, ArtistName},
|
||||||
};
|
};
|
||||||
use ratatui::widgets::{List, ListItem};
|
use ratatui::widgets::{List, ListItem};
|
||||||
|
|
||||||
@ -18,13 +18,13 @@ pub struct MatchOverlay<'a, 'b> {
|
|||||||
impl<'a, 'b> MatchOverlay<'a, 'b> {
|
impl<'a, 'b> MatchOverlay<'a, 'b> {
|
||||||
pub fn new(info: &'a EntityMatches, state: &'b mut WidgetState) -> Self {
|
pub fn new(info: &'a EntityMatches, state: &'b mut WidgetState) -> Self {
|
||||||
match info {
|
match info {
|
||||||
EntityMatches::Artist(m) => Self::artists(&m.matching, &m.list, state),
|
EntityMatches::Artist(m) => Self::artists(&m.matching.name, &m.list, state),
|
||||||
EntityMatches::Album(m) => Self::albums(&m.matching, &m.list, state),
|
EntityMatches::Album(m) => Self::albums(&m.matching, &m.list, state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artists(
|
fn artists(
|
||||||
matching: &ArtistMeta,
|
matching: &ArtistName,
|
||||||
matches: &'a [MatchOption<ArtistMeta>],
|
matches: &'a [MatchOption<ArtistMeta>],
|
||||||
state: &'b mut WidgetState,
|
state: &'b mut WidgetState,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -200,11 +200,11 @@ impl IUi for Ui {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{AlbumDate, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
|
album::{AlbumDate, AlbumId, AlbumInfo, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
|
||||||
artist::{Artist, ArtistId, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistMeta, ArtistName},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppPublic, AppPublicInner, Delta, MatchStatePublic},
|
app::{AppPublic, AppPublicInner, ArtistMatching, Delta, MatchStatePublic},
|
||||||
lib::interface::musicbrainz::api::Entity,
|
lib::interface::musicbrainz::api::Entity,
|
||||||
testmod::COLLECTION,
|
testmod::COLLECTION,
|
||||||
tests::terminal,
|
tests::terminal,
|
||||||
@ -287,7 +287,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_album() {
|
fn empty_album() {
|
||||||
let mut artists: Vec<Artist> = vec![Artist::new(ArtistId::new("An artist"))];
|
let mut artists: Vec<Artist> = vec![Artist::new(0, "An artist")];
|
||||||
artists[0].albums.push(Album::new("An album"));
|
artists[0].albums.push(Album::new("An album"));
|
||||||
let mut selection = Selection::new(&artists);
|
let mut selection = Selection::new(&artists);
|
||||||
|
|
||||||
@ -324,33 +324,49 @@ mod tests {
|
|||||||
draw_test_suite(artists, &mut selection);
|
draw_test_suite(artists, &mut selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_meta() -> ArtistMeta {
|
fn artist_id() -> ArtistId {
|
||||||
ArtistMeta::new(ArtistId::new("an artist"))
|
ArtistId(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_name() -> ArtistName {
|
||||||
|
"an artist".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_meta<Name: Into<ArtistName>>(name: Name) -> ArtistMeta {
|
||||||
|
ArtistMeta::new(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_matches() -> EntityMatches {
|
fn artist_matches() -> EntityMatches {
|
||||||
let artist = artist_meta();
|
let id = artist_id();
|
||||||
let artist_match = Entity::with_score(artist.clone(), 80);
|
let name = artist_name();
|
||||||
|
let meta = artist_meta(name.clone());
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
|
||||||
|
let artist_match = Entity::with_score(meta, 80);
|
||||||
let list = vec![artist_match.clone(), artist_match.clone()];
|
let list = vec![artist_match.clone(), artist_match.clone()];
|
||||||
|
|
||||||
let mut info = EntityMatches::artist_search(artist, list);
|
let mut info = EntityMatches::artist_search(matching, list);
|
||||||
info.push_cannot_have_mbid();
|
info.push_cannot_have_mbid();
|
||||||
info.push_manual_input_mbid();
|
info.push_manual_input_mbid();
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_lookup() -> EntityMatches {
|
fn artist_lookup() -> EntityMatches {
|
||||||
let artist = artist_meta();
|
let id = artist_id();
|
||||||
let artist_lookup = Entity::new(artist.clone());
|
let name = artist_name();
|
||||||
|
let meta = artist_meta(name.clone());
|
||||||
|
let matching = ArtistMatching::new(id, name);
|
||||||
|
|
||||||
let mut info = EntityMatches::artist_lookup(artist, artist_lookup);
|
let artist_lookup = Entity::new(meta.clone());
|
||||||
|
|
||||||
|
let mut info = EntityMatches::artist_lookup(matching, artist_lookup);
|
||||||
info.push_cannot_have_mbid();
|
info.push_cannot_have_mbid();
|
||||||
info.push_manual_input_mbid();
|
info.push_manual_input_mbid();
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_artist_id() -> ArtistId {
|
fn album_artist_id() -> ArtistId {
|
||||||
ArtistId::new("Artist")
|
ArtistId(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_id() -> AlbumId {
|
fn album_id() -> AlbumId {
|
||||||
@ -360,10 +376,14 @@ mod tests {
|
|||||||
fn album_meta(id: AlbumId) -> AlbumMeta {
|
fn album_meta(id: AlbumId) -> AlbumMeta {
|
||||||
AlbumMeta::new(id)
|
AlbumMeta::new(id)
|
||||||
.with_date(AlbumDate::new(Some(1990), Some(5), None))
|
.with_date(AlbumDate::new(Some(1990), Some(5), None))
|
||||||
.with_info(AlbumInfo::new(
|
.with_info(
|
||||||
Some(AlbumPrimaryType::Album),
|
AlbumInfo::default()
|
||||||
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
|
.with_primary_type(AlbumPrimaryType::Album)
|
||||||
))
|
.with_secondary_types(vec![
|
||||||
|
AlbumSecondaryType::Live,
|
||||||
|
AlbumSecondaryType::Compilation,
|
||||||
|
]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_matches() -> EntityMatches {
|
fn album_matches() -> EntityMatches {
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
use std::{fs, path::PathBuf};
|
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::{artist::Artist, Collection},
|
collection::{artist::Artist, Collection},
|
||||||
external::database::sql::{backend::SqlDatabaseSqliteBackend, SqlDatabase},
|
external::database::sql::{backend::SqlDatabaseSqliteBackend, SqlDatabase},
|
||||||
@ -9,10 +5,7 @@ use musichoard::{
|
|||||||
};
|
};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
use crate::testlib::COLLECTION;
|
use crate::{copy_file_into_temp, testlib::COLLECTION, COMPLETE_DB_TEST_FILE};
|
||||||
|
|
||||||
pub static DATABASE_TEST_FILE: Lazy<PathBuf> =
|
|
||||||
Lazy::new(|| fs::canonicalize("./tests/files/database/database.db").unwrap());
|
|
||||||
|
|
||||||
fn expected() -> Collection {
|
fn expected() -> Collection {
|
||||||
let mut expected = COLLECTION.to_owned();
|
let mut expected = COLLECTION.to_owned();
|
||||||
@ -24,6 +17,16 @@ fn expected() -> Collection {
|
|||||||
expected
|
expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset() {
|
||||||
|
let file = NamedTempFile::new().unwrap();
|
||||||
|
|
||||||
|
let backend = SqlDatabaseSqliteBackend::new(file.path()).unwrap();
|
||||||
|
let mut database = SqlDatabase::new(backend).unwrap();
|
||||||
|
|
||||||
|
database.reset().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn save() {
|
fn save() {
|
||||||
let file = NamedTempFile::new().unwrap();
|
let file = NamedTempFile::new().unwrap();
|
||||||
@ -37,7 +40,7 @@ fn save() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load() {
|
fn load() {
|
||||||
let backend = SqlDatabaseSqliteBackend::new(&*DATABASE_TEST_FILE).unwrap();
|
let backend = SqlDatabaseSqliteBackend::new(&*COMPLETE_DB_TEST_FILE).unwrap();
|
||||||
let mut database = SqlDatabase::new(backend).unwrap();
|
let mut database = SqlDatabase::new(backend).unwrap();
|
||||||
|
|
||||||
let read_data: Vec<Artist> = database.load().unwrap();
|
let read_data: Vec<Artist> = database.load().unwrap();
|
||||||
@ -61,3 +64,18 @@ fn reverse() {
|
|||||||
let expected = expected();
|
let expected = expected();
|
||||||
assert_eq!(read_data, expected);
|
assert_eq!(read_data, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reverse_with_reset() {
|
||||||
|
let file = copy_file_into_temp(&*COMPLETE_DB_TEST_FILE);
|
||||||
|
|
||||||
|
let backend = SqlDatabaseSqliteBackend::new(file.path()).unwrap();
|
||||||
|
let mut database = SqlDatabase::new(backend).unwrap();
|
||||||
|
|
||||||
|
let expected: Vec<Artist> = database.load().unwrap();
|
||||||
|
database.reset().unwrap();
|
||||||
|
database.save(&expected).unwrap();
|
||||||
|
let read_data: Vec<Artist> = database.load().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(read_data, expected);
|
||||||
|
}
|
||||||
|
Binary file not shown.
BIN
tests/files/database/partial.db
Normal file
BIN
tests/files/database/partial.db
Normal file
Binary file not shown.
29
tests/lib.rs
29
tests/lib.rs
@ -6,7 +6,7 @@ mod library;
|
|||||||
|
|
||||||
mod testlib;
|
mod testlib;
|
||||||
|
|
||||||
use std::{fs, path::PathBuf};
|
use std::{ffi::OsStr, fs, path::PathBuf, process::Command};
|
||||||
|
|
||||||
use musichoard::{
|
use musichoard::{
|
||||||
external::{
|
external::{
|
||||||
@ -15,16 +15,33 @@ use musichoard::{
|
|||||||
},
|
},
|
||||||
IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, MusicHoard,
|
IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, MusicHoard,
|
||||||
};
|
};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
use crate::testlib::COLLECTION;
|
use crate::testlib::COLLECTION;
|
||||||
|
|
||||||
fn copy_file_into_temp<P: Into<PathBuf>>(path: P) -> NamedTempFile {
|
pub static PARTIAL_DB_TEST_FILE: Lazy<PathBuf> =
|
||||||
|
Lazy::new(|| fs::canonicalize("./tests/files/database/partial.db").unwrap());
|
||||||
|
pub static COMPLETE_DB_TEST_FILE: Lazy<PathBuf> =
|
||||||
|
Lazy::new(|| fs::canonicalize("./tests/files/database/complete.db").unwrap());
|
||||||
|
|
||||||
|
pub fn copy_file_into_temp<P: Into<PathBuf>>(path: P) -> NamedTempFile {
|
||||||
let temp = NamedTempFile::new().unwrap();
|
let temp = NamedTempFile::new().unwrap();
|
||||||
fs::copy(path.into(), temp.path()).unwrap();
|
fs::copy(path.into(), temp.path()).unwrap();
|
||||||
temp
|
temp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sqldiff(left: &OsStr, right: &OsStr) {
|
||||||
|
let output = Command::new("sqldiff")
|
||||||
|
.arg(left)
|
||||||
|
.arg(right)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
assert_eq!(stdout, "");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_library_then_database() {
|
fn merge_library_then_database() {
|
||||||
// Acquired the lock on the beets config file. We need to own the underlying object so later we
|
// Acquired the lock on the beets config file. We need to own the underlying object so later we
|
||||||
@ -37,7 +54,7 @@ fn merge_library_then_database() {
|
|||||||
.config(Some(&*library::beets::BEETS_TEST_CONFIG_PATH));
|
.config(Some(&*library::beets::BEETS_TEST_CONFIG_PATH));
|
||||||
let library = BeetsLibrary::new(executor);
|
let library = BeetsLibrary::new(executor);
|
||||||
|
|
||||||
let file = copy_file_into_temp(&*database::sql::DATABASE_TEST_FILE);
|
let file = copy_file_into_temp(&*PARTIAL_DB_TEST_FILE);
|
||||||
let backend = SqlDatabaseSqliteBackend::new(file.path()).unwrap();
|
let backend = SqlDatabaseSqliteBackend::new(file.path()).unwrap();
|
||||||
let database = SqlDatabase::new(backend).unwrap();
|
let database = SqlDatabase::new(backend).unwrap();
|
||||||
|
|
||||||
@ -47,6 +64,8 @@ fn merge_library_then_database() {
|
|||||||
music_hoard.reload_database().unwrap();
|
music_hoard.reload_database().unwrap();
|
||||||
|
|
||||||
assert_eq!(music_hoard.get_collection(), &*COLLECTION);
|
assert_eq!(music_hoard.get_collection(), &*COLLECTION);
|
||||||
|
|
||||||
|
sqldiff(COMPLETE_DB_TEST_FILE.as_os_str(), file.path().as_os_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -61,7 +80,7 @@ fn merge_database_then_library() {
|
|||||||
.config(Some(&*library::beets::BEETS_TEST_CONFIG_PATH));
|
.config(Some(&*library::beets::BEETS_TEST_CONFIG_PATH));
|
||||||
let library = BeetsLibrary::new(executor);
|
let library = BeetsLibrary::new(executor);
|
||||||
|
|
||||||
let file = copy_file_into_temp(&*database::sql::DATABASE_TEST_FILE);
|
let file = copy_file_into_temp(&*PARTIAL_DB_TEST_FILE);
|
||||||
let backend = SqlDatabaseSqliteBackend::new(file.path()).unwrap();
|
let backend = SqlDatabaseSqliteBackend::new(file.path()).unwrap();
|
||||||
let database = SqlDatabase::new(backend).unwrap();
|
let database = SqlDatabase::new(backend).unwrap();
|
||||||
|
|
||||||
@ -71,4 +90,6 @@ fn merge_database_then_library() {
|
|||||||
music_hoard.rescan_library().unwrap();
|
music_hoard.rescan_library().unwrap();
|
||||||
|
|
||||||
assert_eq!(music_hoard.get_collection(), &*COLLECTION);
|
assert_eq!(music_hoard.get_collection(), &*COLLECTION);
|
||||||
|
|
||||||
|
sqldiff(COMPLETE_DB_TEST_FILE.as_os_str(), file.path().as_os_str());
|
||||||
}
|
}
|
||||||
|
105
tests/testlib.rs
105
tests/testlib.rs
@ -4,7 +4,7 @@ use std::collections::HashMap;
|
|||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{
|
album::{
|
||||||
Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMbRef, AlbumMeta, AlbumPrimaryType,
|
Album, AlbumId, AlbumInfo, AlbumLibId, AlbumMbRef, AlbumMeta, AlbumPrimaryType,
|
||||||
AlbumSecondaryType, AlbumSeq,
|
AlbumSecondaryType,
|
||||||
},
|
},
|
||||||
artist::{Artist, ArtistId, ArtistInfo, ArtistMbRef, ArtistMeta},
|
artist::{Artist, ArtistId, ArtistInfo, ArtistMbRef, ArtistMeta},
|
||||||
musicbrainz::MbArtistRef,
|
musicbrainz::MbArtistRef,
|
||||||
@ -15,15 +15,14 @@ use musichoard::collection::{
|
|||||||
pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
||||||
vec![
|
vec![
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(1),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: String::from("Аркона"),
|
name: String::from("Аркона"),
|
||||||
|
sort: Some(String::from("Arkona")),
|
||||||
|
info: ArtistInfo {
|
||||||
mb_ref: ArtistMbRef::Some(MbArtistRef::from_url_str(
|
mb_ref: ArtistMbRef::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()),
|
||||||
},
|
|
||||||
sort: Some(String::from("Arkona")),
|
|
||||||
info: ArtistInfo {
|
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
(String::from("MusicButler"), vec![
|
(String::from("MusicButler"), vec![
|
||||||
String::from("https://www.musicbutler.io/artist-page/283448581"),
|
String::from("https://www.musicbutler.io/artist-page/283448581"),
|
||||||
@ -44,12 +43,9 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
lib_id: AlbumLibId::Value(7),
|
lib_id: AlbumLibId::Value(7),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2011.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(2011)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -210,15 +206,14 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(2),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: String::from("Eluveitie"),
|
name: String::from("Eluveitie"),
|
||||||
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
mb_ref: ArtistMbRef::Some(MbArtistRef::from_url_str(
|
mb_ref: ArtistMbRef::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()),
|
||||||
},
|
|
||||||
sort: None,
|
|
||||||
info: ArtistInfo {
|
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
(String::from("MusicButler"), vec![
|
(String::from("MusicButler"), vec![
|
||||||
String::from("https://www.musicbutler.io/artist-page/269358403"),
|
String::from("https://www.musicbutler.io/artist-page/269358403"),
|
||||||
@ -237,12 +232,9 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
lib_id: AlbumLibId::Value(1),
|
lib_id: AlbumLibId::Value(1),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2004.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(2004)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Ep),
|
||||||
primary_type: Some(AlbumPrimaryType::Ep),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -320,12 +312,9 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
lib_id: AlbumLibId::Value(2),
|
lib_id: AlbumLibId::Value(2),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2008.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(2008)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -465,15 +454,14 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(3),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: String::from("Frontside"),
|
name: String::from("Frontside"),
|
||||||
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
mb_ref: ArtistMbRef::Some(MbArtistRef::from_url_str(
|
mb_ref: ArtistMbRef::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()),
|
||||||
},
|
|
||||||
sort: None,
|
|
||||||
info: ArtistInfo {
|
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
(String::from("MusicButler"), vec![
|
(String::from("MusicButler"), vec![
|
||||||
String::from("https://www.musicbutler.io/artist-page/826588800"),
|
String::from("https://www.musicbutler.io/artist-page/826588800"),
|
||||||
@ -491,12 +479,9 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
lib_id: AlbumLibId::Value(3),
|
lib_id: AlbumLibId::Value(3),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2001.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(2001)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -624,15 +609,14 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(4),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: String::from("Heaven’s Basement"),
|
name: String::from("Heaven’s Basement"),
|
||||||
|
sort: Some(String::from("Heaven’s Basement")),
|
||||||
|
info: ArtistInfo {
|
||||||
mb_ref: ArtistMbRef::Some(MbArtistRef::from_url_str(
|
mb_ref: ArtistMbRef::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()),
|
||||||
},
|
|
||||||
sort: Some(String::from("Heaven’s Basement")),
|
|
||||||
info: ArtistInfo {
|
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
(String::from("MusicButler"), vec![
|
(String::from("MusicButler"), vec![
|
||||||
String::from("https://www.musicbutler.io/artist-page/291158685"),
|
String::from("https://www.musicbutler.io/artist-page/291158685"),
|
||||||
@ -650,9 +634,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
lib_id: AlbumLibId::Singleton,
|
lib_id: AlbumLibId::Singleton,
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2011.into(),
|
info: AlbumInfo::default().with_date(2011),
|
||||||
seq: AlbumSeq(0),
|
|
||||||
info: AlbumInfo::default(),
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -674,12 +656,9 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
lib_id: AlbumLibId::Value(4),
|
lib_id: AlbumLibId::Value(4),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 2011.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(2011)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -763,15 +742,14 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
Artist {
|
Artist {
|
||||||
|
id: ArtistId(5),
|
||||||
meta: ArtistMeta {
|
meta: ArtistMeta {
|
||||||
id: ArtistId {
|
|
||||||
name: String::from("Metallica"),
|
name: String::from("Metallica"),
|
||||||
|
sort: None,
|
||||||
|
info: ArtistInfo {
|
||||||
mb_ref: ArtistMbRef::Some(MbArtistRef::from_url_str(
|
mb_ref: ArtistMbRef::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()),
|
||||||
},
|
|
||||||
sort: None,
|
|
||||||
info: ArtistInfo {
|
|
||||||
properties: HashMap::from([
|
properties: HashMap::from([
|
||||||
(String::from("MusicButler"), vec![
|
(String::from("MusicButler"), vec![
|
||||||
String::from("https://www.musicbutler.io/artist-page/3996865"),
|
String::from("https://www.musicbutler.io/artist-page/3996865"),
|
||||||
@ -790,12 +768,9 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
lib_id: AlbumLibId::Value(5),
|
lib_id: AlbumLibId::Value(5),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 1984.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(1984)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album),
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
|
||||||
secondary_types: vec![],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
@ -895,12 +870,10 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
lib_id: AlbumLibId::Value(6),
|
lib_id: AlbumLibId::Value(6),
|
||||||
mb_ref: AlbumMbRef::None,
|
mb_ref: AlbumMbRef::None,
|
||||||
},
|
},
|
||||||
date: 1999.into(),
|
info: AlbumInfo::default()
|
||||||
seq: AlbumSeq(0),
|
.with_date(1999)
|
||||||
info: AlbumInfo {
|
.with_primary_type(AlbumPrimaryType::Album)
|
||||||
primary_type: Some(AlbumPrimaryType::Album),
|
.with_secondary_types(vec![AlbumSecondaryType::Live]),
|
||||||
secondary_types: vec![AlbumSecondaryType::Live],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track {
|
Track {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user