Artist merge for non-null properties always erases database properties (#72)
Closes #71 Reviewed-on: https://git.wojciechkozlowski.eu/wojtek/musichoard/pulls/72
This commit is contained in:
parent
fd775372cd
commit
3cd0cfde18
38
src/lib.rs
38
src/lib.rs
@ -48,7 +48,7 @@ impl fmt::Display for InvalidUrlError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// MusicBrainz reference.
|
/// MusicBrainz reference.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct MusicBrainz(Url);
|
pub struct MusicBrainz(Url);
|
||||||
|
|
||||||
impl MusicBrainz {
|
impl MusicBrainz {
|
||||||
@ -93,7 +93,7 @@ impl IMbid for MusicBrainz {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// MusicButler reference.
|
/// MusicButler reference.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct MusicButler(Url);
|
pub struct MusicButler(Url);
|
||||||
|
|
||||||
impl MusicButler {
|
impl MusicButler {
|
||||||
@ -126,7 +126,7 @@ impl IUrl for MusicButler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Bandcamp reference.
|
/// Bandcamp reference.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Bandcamp(Url);
|
pub struct Bandcamp(Url);
|
||||||
|
|
||||||
impl Bandcamp {
|
impl Bandcamp {
|
||||||
@ -159,7 +159,7 @@ impl IUrl for Bandcamp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Qobuz reference.
|
/// Qobuz reference.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Qobuz(Url);
|
pub struct Qobuz(Url);
|
||||||
|
|
||||||
impl Qobuz {
|
impl Qobuz {
|
||||||
@ -192,7 +192,7 @@ impl IUrl for Qobuz {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The track file format.
|
/// The track file format.
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||||
pub enum Format {
|
pub enum Format {
|
||||||
Flac,
|
Flac,
|
||||||
Mp3,
|
Mp3,
|
||||||
@ -288,6 +288,17 @@ pub struct ArtistProperties {
|
|||||||
pub qobuz: Option<Qobuz>,
|
pub qobuz: Option<Qobuz>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Merge for ArtistProperties {
|
||||||
|
fn merge(mut self, other: Self) -> Self {
|
||||||
|
self.musicbrainz = Self::merge_opts(self.musicbrainz, other.musicbrainz);
|
||||||
|
self.musicbutler = Self::merge_vecs(self.musicbutler, other.musicbutler);
|
||||||
|
self.bandcamp = Self::merge_vecs(self.bandcamp, other.bandcamp);
|
||||||
|
self.qobuz = Self::merge_opts(self.qobuz, other.qobuz);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An artist.
|
/// An artist.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct Artist {
|
pub struct Artist {
|
||||||
@ -311,6 +322,7 @@ impl Ord for Artist {
|
|||||||
impl Merge for Artist {
|
impl Merge for Artist {
|
||||||
fn merge(mut self, other: Self) -> Self {
|
fn merge(mut self, other: Self) -> Self {
|
||||||
assert_eq!(self.id, other.id);
|
assert_eq!(self.id, other.id);
|
||||||
|
self.properties = self.properties.merge(other.properties);
|
||||||
self.albums = MergeSorted::new(self.albums.into_iter(), other.albums.into_iter()).collect();
|
self.albums = MergeSorted::new(self.albums.into_iter(), other.albums.into_iter()).collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -321,6 +333,20 @@ pub type Collection = Vec<Artist>;
|
|||||||
|
|
||||||
trait Merge {
|
trait Merge {
|
||||||
fn merge(self, other: Self) -> Self;
|
fn merge(self, other: Self) -> Self;
|
||||||
|
|
||||||
|
fn merge_opts<T>(this: Option<T>, other: Option<T>) -> Option<T> {
|
||||||
|
match &this {
|
||||||
|
Some(_) => this,
|
||||||
|
None => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_vecs<T: Ord + Eq>(mut this: Vec<T>, mut other: Vec<T>) -> Vec<T> {
|
||||||
|
this.append(&mut other);
|
||||||
|
this.sort_unstable();
|
||||||
|
this.dedup();
|
||||||
|
this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MergeSorted<L, R>
|
struct MergeSorted<L, R>
|
||||||
@ -740,6 +766,7 @@ mod tests {
|
|||||||
right.id = left.id.clone();
|
right.id = left.id.clone();
|
||||||
|
|
||||||
let mut expected = left.clone();
|
let mut expected = left.clone();
|
||||||
|
expected.properties = expected.properties.merge(right.clone().properties);
|
||||||
expected.albums.append(&mut right.albums.clone());
|
expected.albums.append(&mut right.albums.clone());
|
||||||
expected.albums.sort_unstable();
|
expected.albums.sort_unstable();
|
||||||
|
|
||||||
@ -756,6 +783,7 @@ mod tests {
|
|||||||
left.albums.sort_unstable();
|
left.albums.sort_unstable();
|
||||||
|
|
||||||
let mut expected = left.clone();
|
let mut expected = left.clone();
|
||||||
|
expected.properties = expected.properties.merge(right.clone().properties);
|
||||||
expected.albums.append(&mut right.albums.clone());
|
expected.albums.append(&mut right.albums.clone());
|
||||||
expected.albums.sort_unstable();
|
expected.albums.sort_unstable();
|
||||||
expected.albums.dedup();
|
expected.albums.dedup();
|
||||||
|
@ -18,7 +18,7 @@ pub trait ILibrary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An item from the library. An item corresponds to an individual file (usually a single track).
|
/// An item from the library. An item corresponds to an individual file (usually a single track).
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub album_artist: String,
|
pub album_artist: String,
|
||||||
pub album_year: u32,
|
pub album_year: u32,
|
||||||
|
@ -10,9 +10,9 @@ use musichoard::{
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
use crate::COLLECTION;
|
use crate::testlib::COLLECTION;
|
||||||
|
|
||||||
static DATABASE_TEST_FILE: Lazy<PathBuf> =
|
pub static DATABASE_TEST_FILE: Lazy<PathBuf> =
|
||||||
Lazy::new(|| fs::canonicalize("./tests/files/database/database.json").unwrap());
|
Lazy::new(|| fs::canonicalize("./tests/files/database/database.json").unwrap());
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#[cfg(feature = "database-json")]
|
#[cfg(feature = "database-json")]
|
||||||
mod json;
|
pub mod json;
|
||||||
|
File diff suppressed because one or more lines are too long
1085
tests/lib.rs
1085
tests/lib.rs
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
fs,
|
fs,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
@ -14,21 +15,22 @@ use musichoard::{
|
|||||||
Artist,
|
Artist,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::COLLECTION;
|
use crate::testlib::COLLECTION;
|
||||||
|
|
||||||
static BEETS_EMPTY_CONFIG: Lazy<Arc<Mutex<BeetsLibrary<BeetsLibraryProcessExecutor>>>> =
|
pub static BEETS_TEST_CONFIG_PATH: Lazy<PathBuf> =
|
||||||
|
Lazy::new(|| fs::canonicalize("./tests/files/library/config.yml").unwrap());
|
||||||
|
|
||||||
|
pub static BEETS_EMPTY_CONFIG: Lazy<Arc<Mutex<BeetsLibrary<BeetsLibraryProcessExecutor>>>> =
|
||||||
Lazy::new(|| {
|
Lazy::new(|| {
|
||||||
Arc::new(Mutex::new(BeetsLibrary::new(
|
Arc::new(Mutex::new(BeetsLibrary::new(
|
||||||
BeetsLibraryProcessExecutor::default(),
|
BeetsLibraryProcessExecutor::default(),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
|
|
||||||
static BEETS_TEST_CONFIG: Lazy<Arc<Mutex<BeetsLibrary<BeetsLibraryProcessExecutor>>>> =
|
pub static BEETS_TEST_CONFIG: Lazy<Arc<Mutex<BeetsLibrary<BeetsLibraryProcessExecutor>>>> =
|
||||||
Lazy::new(|| {
|
Lazy::new(|| {
|
||||||
Arc::new(Mutex::new(BeetsLibrary::new(
|
Arc::new(Mutex::new(BeetsLibrary::new(
|
||||||
BeetsLibraryProcessExecutor::default().config(Some(
|
BeetsLibraryProcessExecutor::default().config(Some(&*BEETS_TEST_CONFIG_PATH)),
|
||||||
&fs::canonicalize("./tests/files/library/config.yml").unwrap(),
|
|
||||||
)),
|
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,8 +91,10 @@ fn test_full_list() {
|
|||||||
let beets = &mut beets_arc.lock().unwrap();
|
let beets = &mut beets_arc.lock().unwrap();
|
||||||
|
|
||||||
let output = beets.list(&Query::new()).unwrap();
|
let output = beets.list(&Query::new()).unwrap();
|
||||||
|
|
||||||
let expected: Vec<Item> = artists_to_items(&COLLECTION);
|
let expected: Vec<Item> = artists_to_items(&COLLECTION);
|
||||||
|
|
||||||
|
let output: HashSet<_> = output.iter().collect();
|
||||||
|
let expected: HashSet<_> = expected.iter().collect();
|
||||||
assert_eq!(output, expected);
|
assert_eq!(output, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +107,7 @@ fn test_album_artist_query() {
|
|||||||
.list(Query::new().include(Field::AlbumArtist(String::from("Аркона"))))
|
.list(Query::new().include(Field::AlbumArtist(String::from("Аркона"))))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let expected: Vec<Item> = artists_to_items(&COLLECTION[0..1]);
|
let expected: Vec<Item> = artists_to_items(&COLLECTION[4..5]);
|
||||||
assert_eq!(output, expected);
|
assert_eq!(output, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +120,7 @@ fn test_album_title_query() {
|
|||||||
.list(&Query::new().include(Field::AlbumTitle(String::from("Slovo"))))
|
.list(&Query::new().include(Field::AlbumTitle(String::from("Slovo"))))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let expected: Vec<Item> = artists_to_items(&COLLECTION[0..1]);
|
let expected: Vec<Item> = artists_to_items(&COLLECTION[4..5]);
|
||||||
assert_eq!(output, expected);
|
assert_eq!(output, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +132,9 @@ fn test_exclude_query() {
|
|||||||
let output = beets
|
let output = beets
|
||||||
.list(&Query::new().exclude(Field::AlbumArtist(String::from("Аркона"))))
|
.list(&Query::new().exclude(Field::AlbumArtist(String::from("Аркона"))))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let expected: Vec<Item> = artists_to_items(&COLLECTION[..4]);
|
||||||
|
|
||||||
let expected: Vec<Item> = artists_to_items(&COLLECTION[1..]);
|
let output: HashSet<_> = output.iter().collect();
|
||||||
|
let expected: HashSet<_> = expected.iter().collect();
|
||||||
assert_eq!(output, expected);
|
assert_eq!(output, expected);
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#[cfg(feature = "library-beets")]
|
#[cfg(feature = "library-beets")]
|
||||||
mod beets;
|
pub mod beets;
|
||||||
|
1026
tests/testlib.rs
Normal file
1026
tests/testlib.rs
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user