Streamline adding new URL types #122
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -369,7 +369,6 @@ dependencies = [
|
|||||||
"mockall",
|
"mockall",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"openssh",
|
"openssh",
|
||||||
"paste",
|
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -457,12 +456,6 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "paste"
|
|
||||||
version = "1.0.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -8,7 +8,6 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = { version = "0.26.1", optional = true}
|
crossterm = { version = "0.26.1", optional = true}
|
||||||
openssh = { version = "0.9.9", features = ["native-mux"], default-features = false, optional = true}
|
openssh = { version = "0.9.9", features = ["native-mux"], default-features = false, optional = true}
|
||||||
paste = { version = "1.0.14" }
|
|
||||||
ratatui = { version = "0.20.1", optional = true}
|
ratatui = { version = "0.20.1", optional = true}
|
||||||
serde = { version = "1.0.159", features = ["derive"] }
|
serde = { version = "1.0.159", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.95", optional = true}
|
serde_json = { version = "1.0.95", optional = true}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use paste::paste;
|
|
||||||
use structopt::{clap::AppSettings, StructOpt};
|
use structopt::{clap::AppSettings, StructOpt};
|
||||||
|
|
||||||
use musichoard::{
|
use musichoard::{
|
||||||
@ -49,16 +48,9 @@ enum ArtistCommand {
|
|||||||
#[structopt(about = "Edit the artist's sort name")]
|
#[structopt(about = "Edit the artist's sort name")]
|
||||||
Sort(SortCommand),
|
Sort(SortCommand),
|
||||||
#[structopt(name = "musicbrainz", about = "Edit the MusicBrainz URL of an artist")]
|
#[structopt(name = "musicbrainz", about = "Edit the MusicBrainz URL of an artist")]
|
||||||
MusicBrainz(UrlCommand<SingleUrlValue>),
|
MusicBrainz(MusicBrainzCommand),
|
||||||
#[structopt(
|
#[structopt(name = "property", about = "Edit a property of an artist")]
|
||||||
name = "musicbutler",
|
Property(PropertyCommand),
|
||||||
about = "Edit the MusicButler URL(s) of an artist"
|
|
||||||
)]
|
|
||||||
MusicButler(UrlCommand<MultiUrlValue>),
|
|
||||||
#[structopt(about = "Edit the Bandcamp URL(s) of an artist")]
|
|
||||||
Bandcamp(UrlCommand<MultiUrlValue>),
|
|
||||||
#[structopt(about = "Edit the Qobuz URL of an artist")]
|
|
||||||
Qobuz(UrlCommand<SingleUrlValue>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
@ -84,68 +76,53 @@ struct ArtistSortValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
enum UrlCommand<T: StructOpt> {
|
enum MusicBrainzCommand {
|
||||||
#[structopt(about = "Add the provided URL(s) without overwriting existing values")]
|
#[structopt(about = "Add a MusicBrainz URL without overwriting the existing value")]
|
||||||
Add(T),
|
Add(MusicBrainzValue),
|
||||||
#[structopt(about = "Remove the provided URL(s)")]
|
#[structopt(about = "Remove the MusicBrainz URL")]
|
||||||
Remove(T),
|
Remove(MusicBrainzValue),
|
||||||
#[structopt(about = "Set the provided URL(s) overwriting any existing values")]
|
#[structopt(about = "Set the MusicBrainz URL overwriting any existing value")]
|
||||||
Set(T),
|
Set(MusicBrainzValue),
|
||||||
#[structopt(about = "Clear all URL(s)")]
|
#[structopt(about = "Clear the MusicBrainz URL)")]
|
||||||
Clear(ArtistValue),
|
Clear(ArtistValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
struct SingleUrlValue {
|
struct MusicBrainzValue {
|
||||||
#[structopt(help = "The name of the artist")]
|
#[structopt(help = "The name of the artist")]
|
||||||
artist: String,
|
artist: String,
|
||||||
#[structopt(help = "The URL")]
|
#[structopt(help = "The MusicBrainz URL")]
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
struct MultiUrlValue {
|
enum PropertyCommand {
|
||||||
|
#[structopt(about = "Add values to the property without overwriting existing values")]
|
||||||
|
Add(PropertyValue),
|
||||||
|
#[structopt(about = "Remove values from the property")]
|
||||||
|
Remove(PropertyValue),
|
||||||
|
#[structopt(about = "Set the property's values overwriting any existing values")]
|
||||||
|
Set(PropertyValue),
|
||||||
|
#[structopt(about = "Clear all values of a property")]
|
||||||
|
Clear(PropertyName),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
struct PropertyValue {
|
||||||
#[structopt(help = "The name of the artist")]
|
#[structopt(help = "The name of the artist")]
|
||||||
artist: String,
|
artist: String,
|
||||||
#[structopt(help = "The list of URLs")]
|
#[structopt(help = "The name of the property")]
|
||||||
urls: Vec<String>,
|
property: String,
|
||||||
|
#[structopt(help = "The list of values")]
|
||||||
|
values: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! url_command_dispatch {
|
#[derive(StructOpt, Debug)]
|
||||||
($cmd:ident, $mh:ident, $field:ident, $url:ident) => {
|
struct PropertyName {
|
||||||
paste! {
|
#[structopt(help = "The name of the artist")]
|
||||||
match $cmd {
|
artist: String,
|
||||||
UrlCommand::Add(url_value) => {
|
#[structopt(help = "The name of the property")]
|
||||||
$mh.[<add_ $field _ $url>](ArtistId::new(url_value.artist), url_value.$url)
|
property: String,
|
||||||
.expect("failed to add URL(s)");
|
|
||||||
}
|
|
||||||
UrlCommand::Remove(url_value) => {
|
|
||||||
$mh.[<remove_ $field _ $url>](ArtistId::new(url_value.artist), url_value.$url)
|
|
||||||
.expect("failed to remove URL(s)");
|
|
||||||
}
|
|
||||||
UrlCommand::Set(url_value) => {
|
|
||||||
$mh.[<set_ $field _ $url>](ArtistId::new(url_value.artist), url_value.$url)
|
|
||||||
.expect("failed to set URL(s)");
|
|
||||||
}
|
|
||||||
UrlCommand::Clear(artist_value) => {
|
|
||||||
$mh.[<clear_ $field _ $url>](ArtistId::new(artist_value.artist))
|
|
||||||
.expect("failed to clear URL(s)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! single_url_command_dispatch {
|
|
||||||
($cmd:ident, $mh:ident, $field:ident) => {
|
|
||||||
url_command_dispatch!($cmd, $mh, $field, url)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! multi_url_command_dispatch {
|
|
||||||
($cmd:ident, $mh:ident, $field:ident) => {
|
|
||||||
url_command_dispatch!($cmd, $mh, $field, urls)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArtistCommand {
|
impl ArtistCommand {
|
||||||
@ -160,17 +137,11 @@ impl ArtistCommand {
|
|||||||
ArtistCommand::Sort(sort_command) => {
|
ArtistCommand::Sort(sort_command) => {
|
||||||
sort_command.handle(music_hoard);
|
sort_command.handle(music_hoard);
|
||||||
}
|
}
|
||||||
ArtistCommand::MusicBrainz(url_command) => {
|
ArtistCommand::MusicBrainz(musicbrainz_command) => {
|
||||||
single_url_command_dispatch!(url_command, music_hoard, musicbrainz)
|
musicbrainz_command.handle(music_hoard)
|
||||||
}
|
}
|
||||||
ArtistCommand::MusicButler(url_command) => {
|
ArtistCommand::Property(property_command) => {
|
||||||
multi_url_command_dispatch!(url_command, music_hoard, musicbutler)
|
property_command.handle(music_hoard);
|
||||||
}
|
|
||||||
ArtistCommand::Bandcamp(url_command) => {
|
|
||||||
multi_url_command_dispatch!(url_command, music_hoard, bandcamp)
|
|
||||||
}
|
|
||||||
ArtistCommand::Qobuz(url_command) => {
|
|
||||||
single_url_command_dispatch!(url_command, music_hoard, qobuz)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,6 +163,65 @@ impl SortCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MusicBrainzCommand {
|
||||||
|
fn handle(self, music_hoard: &mut MH) {
|
||||||
|
match self {
|
||||||
|
MusicBrainzCommand::Add(musicbrainz_value) => music_hoard
|
||||||
|
.add_musicbrainz_url(
|
||||||
|
ArtistId::new(musicbrainz_value.artist),
|
||||||
|
musicbrainz_value.url,
|
||||||
|
)
|
||||||
|
.expect("failed to add MusicBrainz URL"),
|
||||||
|
MusicBrainzCommand::Remove(musicbrainz_value) => music_hoard
|
||||||
|
.remove_musicbrainz_url(
|
||||||
|
ArtistId::new(musicbrainz_value.artist),
|
||||||
|
musicbrainz_value.url,
|
||||||
|
)
|
||||||
|
.expect("failed to remove MusicBrainz URL"),
|
||||||
|
MusicBrainzCommand::Set(musicbrainz_value) => music_hoard
|
||||||
|
.set_musicbrainz_url(
|
||||||
|
ArtistId::new(musicbrainz_value.artist),
|
||||||
|
musicbrainz_value.url,
|
||||||
|
)
|
||||||
|
.expect("failed to set MusicBrainz URL"),
|
||||||
|
MusicBrainzCommand::Clear(artist_value) => music_hoard
|
||||||
|
.clear_musicbrainz_url(ArtistId::new(artist_value.artist))
|
||||||
|
.expect("failed to clear MusicBrainz URL"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PropertyCommand {
|
||||||
|
fn handle(self, music_hoard: &mut MH) {
|
||||||
|
match self {
|
||||||
|
PropertyCommand::Add(property_value) => music_hoard
|
||||||
|
.add_to_property(
|
||||||
|
ArtistId::new(property_value.artist),
|
||||||
|
property_value.property,
|
||||||
|
property_value.values,
|
||||||
|
)
|
||||||
|
.expect("failed to add values to property"),
|
||||||
|
PropertyCommand::Remove(property_value) => music_hoard
|
||||||
|
.remove_from_property(
|
||||||
|
ArtistId::new(property_value.artist),
|
||||||
|
property_value.property,
|
||||||
|
property_value.values,
|
||||||
|
)
|
||||||
|
.expect("failed to remove values from property"),
|
||||||
|
PropertyCommand::Set(property_value) => music_hoard
|
||||||
|
.set_property(
|
||||||
|
ArtistId::new(property_value.artist),
|
||||||
|
property_value.property,
|
||||||
|
property_value.values,
|
||||||
|
)
|
||||||
|
.expect("failed to set property"),
|
||||||
|
PropertyCommand::Clear(property_name) => music_hoard
|
||||||
|
.clear_property(ArtistId::new(property_name.artist), property_name.property)
|
||||||
|
.expect("failed to clear property"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::{HashMap, BTreeMap},
|
||||||
fmt::{self, Debug, Display},
|
fmt::{self, Debug, Display},
|
||||||
mem,
|
mem,
|
||||||
};
|
};
|
||||||
|
|
||||||
use paste::paste;
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -19,7 +19,9 @@ use crate::core::collection::{
|
|||||||
pub struct Artist {
|
pub struct Artist {
|
||||||
pub id: ArtistId,
|
pub id: ArtistId,
|
||||||
pub sort: Option<ArtistId>,
|
pub sort: Option<ArtistId>,
|
||||||
pub properties: ArtistProperties,
|
pub musicbrainz: Option<MusicBrainz>,
|
||||||
|
#[serde(serialize_with = "ordered_map")]
|
||||||
wojtek marked this conversation as resolved
|
|||||||
|
pub properties: HashMap<String, Vec<String>>,
|
||||||
pub albums: Vec<Album>,
|
pub albums: Vec<Album>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,75 +31,14 @@ pub struct ArtistId {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The artist properties.
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
|
||||||
pub struct ArtistProperties {
|
|
||||||
pub musicbrainz: Option<MusicBrainz>,
|
|
||||||
pub musicbutler: Vec<MusicButler>,
|
|
||||||
pub bandcamp: Vec<Bandcamp>,
|
|
||||||
pub qobuz: Option<Qobuz>,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! artist_unique_url_dispatch {
|
|
||||||
($field:ident) => {
|
|
||||||
paste! {
|
|
||||||
pub fn [<add_ $field _url>]<S: AsRef<str>>(&mut self, url: S) -> Result<(), Error> {
|
|
||||||
Self::add_unique_url(&mut self.properties.$field, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<remove_ $field _url>]<S: AsRef<str>>(&mut self, url: S) -> Result<(), Error> {
|
|
||||||
Self::remove_unique_url(&mut self.properties.$field, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<set_ $field _url>]<S: AsRef<str>>(&mut self, url: S) -> Result<(), Error> {
|
|
||||||
Self::set_unique_url(&mut self.properties.$field, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<clear_ $field _url>](&mut self) {
|
|
||||||
Self::clear_unique_url(&mut self.properties.$field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! artist_multi_url_dispatch {
|
|
||||||
($field:ident) => {
|
|
||||||
paste! {
|
|
||||||
pub fn [<add_ $field _urls>]<S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
urls: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Self::add_multi_urls(&mut self.properties.$field, urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<remove_ $field _urls>]<S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
urls: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Self::remove_multi_urls(&mut self.properties.$field, urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<set_ $field _urls>]<S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
urls: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Self::set_multi_urls(&mut self.properties.$field, urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<clear_ $field _urls>](&mut self) {
|
|
||||||
Self::clear_multi_urls(&mut self.properties.$field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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>>(id: ID) -> Self {
|
||||||
Artist {
|
Artist {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
sort: None,
|
sort: None,
|
||||||
properties: ArtistProperties::default(),
|
musicbrainz: None,
|
||||||
|
properties: HashMap::new(),
|
||||||
albums: vec![],
|
albums: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,13 +55,10 @@ impl Artist {
|
|||||||
_ = self.sort.take();
|
_ = self.sort.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_unique_url<S: AsRef<str>, T: for<'a> TryFrom<&'a str, Error = Error> + Eq + Display>(
|
pub fn add_musicbrainz_url<S: AsRef<str>>(&mut self, url: S) -> Result<(), Error> {
|
||||||
container: &mut Option<T>,
|
let url: MusicBrainz = url.as_ref().try_into()?;
|
||||||
url: S,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let url: T = url.as_ref().try_into()?;
|
|
||||||
|
|
||||||
match container {
|
match &self.musicbrainz {
|
||||||
Some(current) => {
|
Some(current) => {
|
||||||
if current != &url {
|
if current != &url {
|
||||||
return Err(Error::UrlError(format!(
|
return Err(Error::UrlError(format!(
|
||||||
@ -129,94 +67,85 @@ impl Artist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
_ = container.insert(url);
|
_ = self.musicbrainz.insert(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_unique_url<S: AsRef<str>, T: for<'a> TryFrom<&'a str, Error = Error> + Eq>(
|
pub fn remove_musicbrainz_url<S: AsRef<str>>(&mut self, url: S) -> Result<(), Error> {
|
||||||
container: &mut Option<T>,
|
let url = url.as_ref().try_into()?;
|
||||||
url: S,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let url: T = url.as_ref().try_into()?;
|
|
||||||
|
|
||||||
if container == &Some(url) {
|
if self.musicbrainz == Some(url) {
|
||||||
_ = container.take();
|
_ = self.musicbrainz.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_unique_url<S: AsRef<str>, T: for<'a> TryFrom<&'a str, Error = Error>>(
|
pub fn set_musicbrainz_url<S: AsRef<str>>(&mut self, url: S) -> Result<(), Error> {
|
||||||
container: &mut Option<T>,
|
_ = self.musicbrainz.insert(url.as_ref().try_into()?);
|
||||||
url: S,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
_ = container.insert(url.as_ref().try_into()?);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_unique_url<T>(container: &mut Option<T>) {
|
pub fn clear_musicbrainz_url(&mut self) {
|
||||||
_ = container.take();
|
_ = self.musicbrainz.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_multi_urls<S: AsRef<str>, T: for<'a> TryFrom<&'a str, Error = Error> + Eq>(
|
// In the functions below, it would be better to use `contains` instead of `iter().any`, but for
|
||||||
container: &mut Vec<T>,
|
// type reasons that does not work:
|
||||||
urls: Vec<S>,
|
// https://stackoverflow.com/questions/48985924/why-does-a-str-not-coerce-to-a-string-when-using-veccontains
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut new_urls = urls
|
|
||||||
.iter()
|
|
||||||
.map(|url| url.as_ref().try_into())
|
|
||||||
.filter(|res| {
|
|
||||||
res.as_ref()
|
|
||||||
.map(|url| !container.contains(url))
|
|
||||||
.unwrap_or(true) // Propagate errors.
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<T>, Error>>()?;
|
|
||||||
|
|
||||||
container.append(&mut new_urls);
|
pub fn add_to_property<S: AsRef<str> + Into<String>>(&mut self, property: S, values: Vec<S>) {
|
||||||
Ok(())
|
match self.properties.get_mut(property.as_ref()) {
|
||||||
|
Some(container) => {
|
||||||
|
container.append(
|
||||||
|
&mut values
|
||||||
|
.into_iter()
|
||||||
|
.filter(|val| !container.iter().any(|x| x == val.as_ref()))
|
||||||
|
.map(|val| val.into())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.properties.insert(
|
||||||
|
property.into(),
|
||||||
|
values.into_iter().map(|s| s.into()).collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_multi_urls<S: AsRef<str>, T: for<'a> TryFrom<&'a str, Error = Error> + Eq>(
|
pub fn remove_from_property<S: AsRef<str>>(&mut self, property: S, values: Vec<S>) {
|
||||||
container: &mut Vec<T>,
|
let container = self.properties.get_mut(property.as_ref()).map(|container| {
|
||||||
wojtek marked this conversation as resolved
wojtek
commented
Split map from the get_mut. Will be more legible and the container variable is used later anyway. Split map from the get_mut. Will be more legible and the container variable is used later anyway.
|
|||||||
urls: Vec<S>,
|
container.retain(|val| !values.iter().any(|x| x.as_ref() == val));
|
||||||
) -> Result<(), Error> {
|
container
|
||||||
let urls = urls
|
});
|
||||||
.iter()
|
if let Some(container) = container {
|
||||||
wojtek marked this conversation as resolved
wojtek
commented
When addressing the comment above, move the retain operation to be inside this if-let. When addressing the comment above, move the retain operation to be inside this if-let.
|
|||||||
.map(|url| url.as_ref().try_into())
|
if container.is_empty() {
|
||||||
.collect::<Result<Vec<T>, Error>>()?;
|
self.properties.remove(property.as_ref());
|
||||||
|
}
|
||||||
container.retain(|url| !urls.contains(url));
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_multi_urls<S: AsRef<str>, T: for<'a> TryFrom<&'a str, Error = Error>>(
|
pub fn set_property<S: AsRef<str> + Into<String>>(&mut self, property: S, values: Vec<S>) {
|
||||||
container: &mut Vec<T>,
|
let mut values = values.into_iter().map(|s| s.into()).collect();
|
||||||
urls: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut urls = urls
|
|
||||||
.iter()
|
|
||||||
.map(|url| url.as_ref().try_into())
|
|
||||||
.collect::<Result<Vec<T>, Error>>()?;
|
|
||||||
|
|
||||||
container.clear();
|
match self.properties.get_mut(property.as_ref()) {
|
||||||
container.append(&mut urls);
|
Some(container) => {
|
||||||
Ok(())
|
container.clear();
|
||||||
|
container.append(&mut values);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.properties.insert(property.into(), values);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_multi_urls<T>(container: &mut Vec<T>) {
|
pub fn clear_property<S: AsRef<str>>(&mut self, property: S) {
|
||||||
container.clear();
|
self.properties.remove(property.as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
artist_unique_url_dispatch!(musicbrainz);
|
|
||||||
|
|
||||||
artist_multi_url_dispatch!(musicbutler);
|
|
||||||
|
|
||||||
artist_multi_url_dispatch!(bandcamp);
|
|
||||||
|
|
||||||
artist_unique_url_dispatch!(qobuz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for Artist {
|
impl PartialOrd for Artist {
|
||||||
@ -235,12 +164,25 @@ impl Merge for Artist {
|
|||||||
fn merge_in_place(&mut self, other: Self) {
|
fn merge_in_place(&mut self, other: Self) {
|
||||||
assert_eq!(self.id, other.id);
|
assert_eq!(self.id, other.id);
|
||||||
self.sort = self.sort.take().or(other.sort);
|
self.sort = self.sort.take().or(other.sort);
|
||||||
|
self.musicbrainz = self.musicbrainz.take().or(other.musicbrainz);
|
||||||
self.properties.merge_in_place(other.properties);
|
self.properties.merge_in_place(other.properties);
|
||||||
let albums = mem::take(&mut self.albums);
|
let albums = mem::take(&mut self.albums);
|
||||||
self.albums = MergeSorted::new(albums.into_iter(), other.albums.into_iter()).collect();
|
self.albums = MergeSorted::new(albums.into_iter(), other.albums.into_iter()).collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For use with serde's [serialize_with] attribute
|
||||||
|
fn ordered_map<S, K: Ord + Serialize, V: Serialize>(
|
||||||
|
value: &HashMap<K, V>,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let ordered: BTreeMap<_, _> = value.iter().collect();
|
||||||
|
ordered.serialize(serializer)
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRef<ArtistId> for ArtistId {
|
impl AsRef<ArtistId> for ArtistId {
|
||||||
fn as_ref(&self) -> &ArtistId {
|
fn as_ref(&self) -> &ArtistId {
|
||||||
self
|
self
|
||||||
@ -259,15 +201,6 @@ impl Display for ArtistId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Merge for ArtistProperties {
|
|
||||||
fn merge_in_place(&mut self, other: Self) {
|
|
||||||
self.musicbrainz = self.musicbrainz.take().or(other.musicbrainz);
|
|
||||||
Self::merge_vecs(&mut self.musicbutler, other.musicbutler);
|
|
||||||
Self::merge_vecs(&mut self.bandcamp, other.bandcamp);
|
|
||||||
self.qobuz = self.qobuz.take().or(other.qobuz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An object with the [`IMbid`] trait contains a [MusicBrainz
|
/// An object with the [`IMbid`] trait contains a [MusicBrainz
|
||||||
/// Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID).
|
/// Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID).
|
||||||
pub trait IMbid {
|
pub trait IMbid {
|
||||||
@ -331,137 +264,6 @@ impl IMbid for MusicBrainz {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MusicButler reference.
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct MusicButler(Url);
|
|
||||||
|
|
||||||
impl MusicButler {
|
|
||||||
/// Validate and wrap a MusicButler URL.
|
|
||||||
pub fn new<S: AsRef<str>>(url: S) -> Result<Self, Error> {
|
|
||||||
let url = Url::parse(url.as_ref())?;
|
|
||||||
|
|
||||||
if !url
|
|
||||||
.domain()
|
|
||||||
.map(|u| u.ends_with("musicbutler.io"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Err(Self::invalid_url_error(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(MusicButler(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
self.0.as_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid_url_error<U: Display>(url: U) -> Error {
|
|
||||||
Error::UrlError(format!("invalid MusicButler URL: {url}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for MusicButler {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for MusicButler {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
||||||
MusicButler::new(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bandcamp reference.
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct Bandcamp(Url);
|
|
||||||
|
|
||||||
impl Bandcamp {
|
|
||||||
/// Validate and wrap a Bandcamp URL.
|
|
||||||
pub fn new<S: AsRef<str>>(url: S) -> Result<Self, Error> {
|
|
||||||
let url = Url::parse(url.as_ref())?;
|
|
||||||
|
|
||||||
if !url
|
|
||||||
.domain()
|
|
||||||
.map(|u| u.ends_with("bandcamp.com"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Err(Self::invalid_url_error(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Bandcamp(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
self.0.as_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid_url_error<U: Display>(url: U) -> Error {
|
|
||||||
Error::UrlError(format!("invalid Bandcamp URL: {url}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for Bandcamp {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for Bandcamp {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
||||||
Bandcamp::new(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Qobuz reference.
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct Qobuz(Url);
|
|
||||||
|
|
||||||
impl Qobuz {
|
|
||||||
/// Validate and wrap a Qobuz URL.
|
|
||||||
pub fn new<S: AsRef<str>>(url: S) -> Result<Self, Error> {
|
|
||||||
let url = Url::parse(url.as_ref())?;
|
|
||||||
|
|
||||||
if !url
|
|
||||||
.domain()
|
|
||||||
.map(|u| u.ends_with("qobuz.com"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Err(Self::invalid_url_error(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Qobuz(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid_url_error<U: Display>(url: U) -> Error {
|
|
||||||
Error::UrlError(format!("invalid Qobuz URL: {url}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for Qobuz {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for Qobuz {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
||||||
Qobuz::new(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Qobuz {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::core::testmod::FULL_COLLECTION;
|
use crate::core::testmod::FULL_COLLECTION;
|
||||||
@ -474,10 +276,6 @@ mod tests {
|
|||||||
"https://musicbrainz.org/artist/823869a5-5ded-4f6b-9fb7-2a9344d83c6b";
|
"https://musicbrainz.org/artist/823869a5-5ded-4f6b-9fb7-2a9344d83c6b";
|
||||||
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/";
|
||||||
static BANDCAMP: &str = "https://thelasthangmen.bandcamp.com/";
|
|
||||||
static BANDCAMP_2: &str = "https://viciouscrusade.bandcamp.com/";
|
|
||||||
static QOBUZ: &str = "https://www.qobuz.com/nl-nl/interpreter/the-last-hangmen/1244413";
|
|
||||||
static QOBUZ_2: &str = "https://www.qobuz.com/nl-nl/interpreter/vicious-crusade/7522386";
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn musicbrainz() {
|
fn musicbrainz() {
|
||||||
@ -510,23 +308,6 @@ mod tests {
|
|||||||
fn urls() {
|
fn urls() {
|
||||||
assert!(MusicBrainz::new(MUSICBRAINZ).is_ok());
|
assert!(MusicBrainz::new(MUSICBRAINZ).is_ok());
|
||||||
assert!(MusicBrainz::new(MUSICBUTLER).is_err());
|
assert!(MusicBrainz::new(MUSICBUTLER).is_err());
|
||||||
assert!(MusicBrainz::new(BANDCAMP).is_err());
|
|
||||||
assert!(MusicBrainz::new(QOBUZ).is_err());
|
|
||||||
|
|
||||||
assert!(MusicButler::new(MUSICBRAINZ).is_err());
|
|
||||||
assert!(MusicButler::new(MUSICBUTLER).is_ok());
|
|
||||||
assert!(MusicButler::new(BANDCAMP).is_err());
|
|
||||||
assert!(MusicButler::new(QOBUZ).is_err());
|
|
||||||
|
|
||||||
assert!(Bandcamp::new(MUSICBRAINZ).is_err());
|
|
||||||
assert!(Bandcamp::new(MUSICBUTLER).is_err());
|
|
||||||
assert!(Bandcamp::new(BANDCAMP).is_ok());
|
|
||||||
assert!(Bandcamp::new(QOBUZ).is_err());
|
|
||||||
|
|
||||||
assert!(Qobuz::new(MUSICBRAINZ).is_err());
|
|
||||||
assert!(Qobuz::new(MUSICBUTLER).is_err());
|
|
||||||
assert!(Qobuz::new(BANDCAMP).is_err());
|
|
||||||
assert!(Qobuz::new(QOBUZ).is_ok());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -573,38 +354,36 @@ mod tests {
|
|||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut artist = Artist::new(ArtistId::new("an artist"));
|
||||||
|
|
||||||
let mut expected: Option<MusicBrainz> = None;
|
let mut expected: Option<MusicBrainz> = None;
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Adding incorect URL is an error.
|
// Adding incorect URL is an error.
|
||||||
assert!(artist.add_musicbrainz_url(MUSICBUTLER).is_err());
|
assert!(artist.add_musicbrainz_url(MUSICBUTLER).is_err());
|
||||||
assert!(artist.add_musicbrainz_url(BANDCAMP).is_err());
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
assert!(artist.add_musicbrainz_url(QOBUZ).is_err());
|
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
|
||||||
|
|
||||||
// Adding URL to artist.
|
// Adding URL to artist.
|
||||||
assert!(artist.add_musicbrainz_url(MUSICBRAINZ).is_ok());
|
assert!(artist.add_musicbrainz_url(MUSICBRAINZ).is_ok());
|
||||||
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap());
|
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap());
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Adding the same URL again is ok, but does not do anything.
|
// Adding the same URL again is ok, but does not do anything.
|
||||||
assert!(artist.add_musicbrainz_url(MUSICBRAINZ).is_ok());
|
assert!(artist.add_musicbrainz_url(MUSICBRAINZ).is_ok());
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Adding further URLs is an error.
|
// Adding further URLs is an error.
|
||||||
assert!(artist.add_musicbrainz_url(MUSICBRAINZ_2).is_err());
|
assert!(artist.add_musicbrainz_url(MUSICBRAINZ_2).is_err());
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Removing a URL not in the collection is okay, but does not do anything.
|
// Removing a URL not in the collection is okay, but does not do anything.
|
||||||
assert!(artist.remove_musicbrainz_url(MUSICBRAINZ_2).is_ok());
|
assert!(artist.remove_musicbrainz_url(MUSICBRAINZ_2).is_ok());
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Removing a URL in the collection removes it.
|
// Removing a URL in the collection removes it.
|
||||||
assert!(artist.remove_musicbrainz_url(MUSICBRAINZ).is_ok());
|
assert!(artist.remove_musicbrainz_url(MUSICBRAINZ).is_ok());
|
||||||
_ = expected.take();
|
_ = expected.take();
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
assert!(artist.remove_musicbrainz_url(MUSICBRAINZ).is_ok());
|
assert!(artist.remove_musicbrainz_url(MUSICBRAINZ).is_ok());
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -612,366 +391,128 @@ mod tests {
|
|||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut artist = Artist::new(ArtistId::new("an artist"));
|
||||||
|
|
||||||
let mut expected: Option<MusicBrainz> = None;
|
let mut expected: Option<MusicBrainz> = None;
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Setting an incorrect URL is an error.
|
// Setting an incorrect URL is an error.
|
||||||
assert!(artist.set_musicbrainz_url(MUSICBUTLER).is_err());
|
assert!(artist.set_musicbrainz_url(MUSICBUTLER).is_err());
|
||||||
assert!(artist.set_musicbrainz_url(BANDCAMP).is_err());
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
assert!(artist.set_musicbrainz_url(QOBUZ).is_err());
|
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
|
||||||
|
|
||||||
// Setting a URL on an artist.
|
// Setting a URL on an artist.
|
||||||
assert!(artist.set_musicbrainz_url(MUSICBRAINZ).is_ok());
|
assert!(artist.set_musicbrainz_url(MUSICBRAINZ).is_ok());
|
||||||
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap());
|
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap());
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
assert!(artist.set_musicbrainz_url(MUSICBRAINZ).is_ok());
|
assert!(artist.set_musicbrainz_url(MUSICBRAINZ).is_ok());
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
assert!(artist.set_musicbrainz_url(MUSICBRAINZ_2).is_ok());
|
assert!(artist.set_musicbrainz_url(MUSICBRAINZ_2).is_ok());
|
||||||
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ_2).unwrap());
|
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ_2).unwrap());
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
|
|
||||||
// Clearing URLs.
|
// Clearing URLs.
|
||||||
artist.clear_musicbrainz_url();
|
artist.clear_musicbrainz_url();
|
||||||
_ = expected.take();
|
_ = expected.take();
|
||||||
assert_eq!(artist.properties.musicbrainz, expected);
|
assert_eq!(artist.musicbrainz, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_remove_musicbutler_urls() {
|
fn add_to_remove_from_property() {
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut artist = Artist::new(ArtistId::new("an artist"));
|
||||||
|
|
||||||
let mut expected: Vec<MusicButler> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert!(artist.properties.is_empty());
|
||||||
|
|
||||||
// If any URL is incorrect adding URLs is an error.
|
|
||||||
assert!(artist
|
|
||||||
.add_musicbutler_urls(vec![MUSICBRAINZ, MUSICBRAINZ_2])
|
|
||||||
.is_err());
|
|
||||||
assert!(artist
|
|
||||||
.add_musicbutler_urls(vec![BANDCAMP, BANDCAMP_2])
|
|
||||||
.is_err());
|
|
||||||
assert!(artist.add_musicbutler_urls(vec![QOBUZ, QOBUZ_2]).is_err());
|
|
||||||
assert!(artist
|
|
||||||
.add_musicbutler_urls(vec![MUSICBRAINZ, MUSICBUTLER, BANDCAMP, QOBUZ])
|
|
||||||
.is_err());
|
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
|
||||||
|
|
||||||
// Adding a single URL.
|
// Adding a single URL.
|
||||||
assert!(artist.add_musicbutler_urls(vec![MUSICBUTLER]).is_ok());
|
artist.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
expected.push(MusicButler::new(MUSICBUTLER).unwrap());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Adding a URL that already exists is ok, but does not do anything.
|
// Adding a URL that already exists is ok, but does not do anything.
|
||||||
assert!(artist.add_musicbutler_urls(vec![MUSICBUTLER]).is_ok());
|
artist.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Adding another single URL.
|
// Adding another single URL.
|
||||||
assert!(artist.add_musicbutler_urls(vec![MUSICBUTLER_2]).is_ok());
|
artist.add_to_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
expected.push(MusicButler::new(MUSICBUTLER_2).unwrap());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
assert!(artist.add_musicbutler_urls(vec![MUSICBUTLER_2]).is_ok());
|
artist.add_to_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Removing a URL.
|
// Removing a URL.
|
||||||
assert!(artist.remove_musicbutler_urls(vec![MUSICBUTLER]).is_ok());
|
artist.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
expected.retain(|url| url.as_str() != MUSICBUTLER);
|
expected.retain(|url| url != MUSICBUTLER);
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Removing URls that do not exist is okay, they will be ignored.
|
// Removing URls that do not exist is okay, they will be ignored.
|
||||||
assert!(artist.remove_musicbutler_urls(vec![MUSICBUTLER]).is_ok());
|
artist.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Removing a URL.
|
// Removing a URL.
|
||||||
assert!(artist.remove_musicbutler_urls(vec![MUSICBUTLER_2]).is_ok());
|
artist.remove_from_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
expected.retain(|url| url.as_str() != MUSICBUTLER_2);
|
expected.retain(|url| url.as_str() != MUSICBUTLER_2);
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert!(artist.properties.is_empty());
|
||||||
|
|
||||||
assert!(artist.remove_musicbutler_urls(vec![MUSICBUTLER_2]).is_ok());
|
artist.remove_from_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert!(artist.properties.is_empty());
|
||||||
|
|
||||||
// Adding URLs if some exist is okay, they will be ignored.
|
// Adding URLs if some exist is okay, they will be ignored.
|
||||||
assert!(artist.add_musicbutler_urls(vec![MUSICBUTLER]).is_ok());
|
artist.add_to_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
expected.push(MusicButler::new(MUSICBUTLER).unwrap());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
assert!(artist
|
artist.add_to_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.add_musicbutler_urls(vec![MUSICBUTLER, MUSICBUTLER_2])
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
.is_ok());
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
expected.push(MusicButler::new(MUSICBUTLER_2).unwrap());
|
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
|
||||||
|
|
||||||
// Removing URLs if some do not exist is okay, they will be ignored.
|
// Removing URLs if some do not exist is okay, they will be ignored.
|
||||||
assert!(artist.remove_musicbutler_urls(vec![MUSICBUTLER]).is_ok());
|
artist.remove_from_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
expected.retain(|url| url.as_str() != MUSICBUTLER);
|
expected.retain(|url| url.as_str() != MUSICBUTLER);
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
assert!(artist
|
artist.remove_from_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.remove_musicbutler_urls(vec![MUSICBUTLER, MUSICBUTLER_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.retain(|url| url.as_str() != MUSICBUTLER_2);
|
expected.retain(|url| url.as_str() != MUSICBUTLER_2);
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert!(artist.properties.is_empty());
|
||||||
|
|
||||||
// Adding mutliple URLs without clashes.
|
// Adding mutliple URLs without clashes.
|
||||||
assert!(artist
|
artist.add_to_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.add_musicbutler_urls(vec![MUSICBUTLER, MUSICBUTLER_2])
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
.is_ok());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
expected.push(MusicButler::new(MUSICBUTLER).unwrap());
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
expected.push(MusicButler::new(MUSICBUTLER_2).unwrap());
|
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
|
||||||
|
|
||||||
// Removing multiple URLs without clashes.
|
// Removing multiple URLs without clashes.
|
||||||
assert!(artist
|
artist.remove_from_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.remove_musicbutler_urls(vec![MUSICBUTLER, MUSICBUTLER_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.clear();
|
expected.clear();
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert!(artist.properties.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_musicbutler_urls() {
|
fn set_clear_musicbutler_urls() {
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
let mut artist = Artist::new(ArtistId::new("an artist"));
|
||||||
|
|
||||||
let mut expected: Vec<MusicButler> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert!(artist.properties.is_empty());
|
||||||
|
|
||||||
// If any URL is incorrect setting URLs is an error.
|
|
||||||
assert!(artist
|
|
||||||
.set_musicbutler_urls(vec![MUSICBRAINZ, MUSICBRAINZ_2])
|
|
||||||
.is_err());
|
|
||||||
assert!(artist
|
|
||||||
.set_musicbutler_urls(vec![BANDCAMP, BANDCAMP_2])
|
|
||||||
.is_err());
|
|
||||||
assert!(artist.set_musicbutler_urls(vec![QOBUZ, QOBUZ_2]).is_err());
|
|
||||||
assert!(artist
|
|
||||||
.set_musicbutler_urls(vec![MUSICBRAINZ, MUSICBUTLER, BANDCAMP, QOBUZ])
|
|
||||||
.is_err());
|
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
|
||||||
|
|
||||||
// Set URLs.
|
// Set URLs.
|
||||||
assert!(artist.set_musicbutler_urls(vec![MUSICBUTLER]).is_ok());
|
artist.set_property("MusicButler", vec![MUSICBUTLER]);
|
||||||
expected.push(MusicButler::new(MUSICBUTLER).unwrap());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
assert!(artist.set_musicbutler_urls(vec![MUSICBUTLER_2]).is_ok());
|
artist.set_property("MusicButler", vec![MUSICBUTLER_2]);
|
||||||
expected.clear();
|
expected.clear();
|
||||||
expected.push(MusicButler::new(MUSICBUTLER_2).unwrap());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
assert!(artist
|
artist.set_property("MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]);
|
||||||
.set_musicbutler_urls(vec![MUSICBUTLER, MUSICBUTLER_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.clear();
|
expected.clear();
|
||||||
expected.push(MusicButler::new(MUSICBUTLER).unwrap());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
expected.push(MusicButler::new(MUSICBUTLER_2).unwrap());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert_eq!(artist.properties.get("MusicButler"), Some(&expected));
|
||||||
|
|
||||||
// Clear URLs.
|
// Clear URLs.
|
||||||
artist.clear_musicbutler_urls();
|
artist.clear_property("MusicButler");
|
||||||
expected.clear();
|
expected.clear();
|
||||||
assert_eq!(artist.properties.musicbutler, expected);
|
assert!(artist.properties.is_empty());
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_remove_bandcamp_urls() {
|
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
|
||||||
|
|
||||||
let mut expected: Vec<Bandcamp> = vec![];
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// If any URL is incorrect adding URLs is an error.
|
|
||||||
assert!(artist
|
|
||||||
.add_bandcamp_urls(vec![MUSICBRAINZ, MUSICBRAINZ_2])
|
|
||||||
.is_err());
|
|
||||||
assert!(artist
|
|
||||||
.add_bandcamp_urls(vec![MUSICBUTLER, MUSICBUTLER_2])
|
|
||||||
.is_err());
|
|
||||||
assert!(artist.add_bandcamp_urls(vec![QOBUZ, QOBUZ_2]).is_err());
|
|
||||||
assert!(artist
|
|
||||||
.add_bandcamp_urls(vec![MUSICBRAINZ, MUSICBUTLER, BANDCAMP, QOBUZ])
|
|
||||||
.is_err());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Adding a single URL.
|
|
||||||
assert!(artist.add_bandcamp_urls(vec![BANDCAMP]).is_ok());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP).unwrap());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Adding a URL that already exists is ok, but does not do anything.
|
|
||||||
assert!(artist.add_bandcamp_urls(vec![BANDCAMP]).is_ok());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Adding another single URL.
|
|
||||||
assert!(artist.add_bandcamp_urls(vec![BANDCAMP_2]).is_ok());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP_2).unwrap());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
assert!(artist.add_bandcamp_urls(vec![BANDCAMP_2]).is_ok());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Removing a URL.
|
|
||||||
assert!(artist.remove_bandcamp_urls(vec![BANDCAMP]).is_ok());
|
|
||||||
expected.retain(|url| url.as_str() != BANDCAMP);
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Removing URls that do not exist is okay, they will be ignored.
|
|
||||||
assert!(artist.remove_bandcamp_urls(vec![BANDCAMP]).is_ok());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Removing a URL.
|
|
||||||
assert!(artist.remove_bandcamp_urls(vec![BANDCAMP_2]).is_ok());
|
|
||||||
expected.retain(|url| url.as_str() != BANDCAMP_2);
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
assert!(artist.remove_bandcamp_urls(vec![BANDCAMP_2]).is_ok());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Adding URLs if some exist is okay, they will be ignored.
|
|
||||||
assert!(artist.add_bandcamp_urls(vec![BANDCAMP]).is_ok());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP).unwrap());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
assert!(artist.add_bandcamp_urls(vec![BANDCAMP, BANDCAMP_2]).is_ok());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP_2).unwrap());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Removing URLs if some do not exist is okay, they will be ignored.
|
|
||||||
assert!(artist.remove_bandcamp_urls(vec![BANDCAMP]).is_ok());
|
|
||||||
expected.retain(|url| url.as_str() != BANDCAMP);
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
assert!(artist
|
|
||||||
.remove_bandcamp_urls(vec![BANDCAMP, BANDCAMP_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.retain(|url| url.as_str() != BANDCAMP_2);
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Adding mutliple URLs without clashes.
|
|
||||||
assert!(artist.add_bandcamp_urls(vec![BANDCAMP, BANDCAMP_2]).is_ok());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP).unwrap());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP_2).unwrap());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Removing multiple URLs without clashes.
|
|
||||||
assert!(artist
|
|
||||||
.remove_bandcamp_urls(vec![BANDCAMP, BANDCAMP_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.clear();
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_clear_bandcamp_urls() {
|
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
|
||||||
|
|
||||||
let mut expected: Vec<Bandcamp> = vec![];
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// If any URL is incorrect setting URLs is an error.
|
|
||||||
assert!(artist
|
|
||||||
.set_bandcamp_urls(vec![MUSICBRAINZ, MUSICBRAINZ_2])
|
|
||||||
.is_err());
|
|
||||||
assert!(artist
|
|
||||||
.set_bandcamp_urls(vec![MUSICBUTLER, MUSICBUTLER_2])
|
|
||||||
.is_err());
|
|
||||||
assert!(artist.set_bandcamp_urls(vec![QOBUZ, QOBUZ_2]).is_err());
|
|
||||||
assert!(artist
|
|
||||||
.set_bandcamp_urls(vec![MUSICBRAINZ, MUSICBUTLER, BANDCAMP, QOBUZ])
|
|
||||||
.is_err());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Set URLs.
|
|
||||||
assert!(artist.set_bandcamp_urls(vec![BANDCAMP]).is_ok());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP).unwrap());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
assert!(artist.set_bandcamp_urls(vec![BANDCAMP_2]).is_ok());
|
|
||||||
expected.clear();
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP_2).unwrap());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
assert!(artist.set_bandcamp_urls(vec![BANDCAMP, BANDCAMP_2]).is_ok());
|
|
||||||
expected.clear();
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP).unwrap());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP_2).unwrap());
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Clear URLs.
|
|
||||||
artist.clear_bandcamp_urls();
|
|
||||||
expected.clear();
|
|
||||||
assert_eq!(artist.properties.bandcamp, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_remove_qobuz_url() {
|
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
|
||||||
|
|
||||||
let mut expected: Option<Qobuz> = None;
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Adding incorect URL is an error.
|
|
||||||
assert!(artist.add_qobuz_url(MUSICBRAINZ).is_err());
|
|
||||||
assert!(artist.add_qobuz_url(MUSICBUTLER).is_err());
|
|
||||||
assert!(artist.add_qobuz_url(BANDCAMP).is_err());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Adding URL to artist.
|
|
||||||
assert!(artist.add_qobuz_url(QOBUZ).is_ok());
|
|
||||||
_ = expected.insert(Qobuz::new(QOBUZ).unwrap());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Adding the same URL again is ok, but does not do anything.
|
|
||||||
assert!(artist.add_qobuz_url(QOBUZ).is_ok());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Adding further URLs is an error.
|
|
||||||
assert!(artist.add_qobuz_url(QOBUZ_2).is_err());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Removing a URL not in the collection is okay, but does not do anything.
|
|
||||||
assert!(artist.remove_qobuz_url(QOBUZ_2).is_ok());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Removing a URL in the collection removes it.
|
|
||||||
assert!(artist.remove_qobuz_url(QOBUZ).is_ok());
|
|
||||||
_ = expected.take();
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
assert!(artist.remove_qobuz_url(QOBUZ).is_ok());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_clear_qobuz_url() {
|
|
||||||
let mut artist = Artist::new(ArtistId::new("an artist"));
|
|
||||||
|
|
||||||
let mut expected: Option<Qobuz> = None;
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Setting an incorrect URL is an error.
|
|
||||||
assert!(artist.set_qobuz_url(MUSICBUTLER).is_err());
|
|
||||||
assert!(artist.set_qobuz_url(BANDCAMP).is_err());
|
|
||||||
assert!(artist.set_qobuz_url(MUSICBRAINZ).is_err());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Setting a URL on an artist.
|
|
||||||
assert!(artist.set_qobuz_url(QOBUZ).is_ok());
|
|
||||||
_ = expected.insert(Qobuz::new(QOBUZ).unwrap());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
assert!(artist.set_qobuz_url(QOBUZ).is_ok());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
assert!(artist.set_qobuz_url(QOBUZ_2).is_ok());
|
|
||||||
_ = expected.insert(Qobuz::new(QOBUZ_2).unwrap());
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Clearing URLs.
|
|
||||||
artist.clear_qobuz_url();
|
|
||||||
_ = expected.take();
|
|
||||||
assert_eq!(artist.properties.qobuz, expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -979,7 +520,8 @@ mod tests {
|
|||||||
let left = FULL_COLLECTION[0].to_owned();
|
let left = FULL_COLLECTION[0].to_owned();
|
||||||
let mut right = FULL_COLLECTION[1].to_owned();
|
let mut right = FULL_COLLECTION[1].to_owned();
|
||||||
right.id = left.id.clone();
|
right.id = left.id.clone();
|
||||||
right.properties = ArtistProperties::default();
|
right.musicbrainz = None;
|
||||||
|
right.properties = HashMap::new();
|
||||||
|
|
||||||
let mut expected = left.clone();
|
let mut expected = left.clone();
|
||||||
expected.properties = expected.properties.merge(right.clone().properties);
|
expected.properties = expected.properties.merge(right.clone().properties);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{cmp::Ordering, iter::Peekable};
|
use std::{cmp::Ordering, iter::Peekable, collections::HashMap, hash::Hash};
|
||||||
|
|
||||||
/// A trait for merging two objects. The merge is asymmetric with the left argument considered to be
|
/// A trait for merging two objects. The merge is asymmetric with the left argument considered to be
|
||||||
/// the primary whose properties are to be kept in case of collisions.
|
/// the primary whose properties are to be kept in case of collisions.
|
||||||
@ -12,11 +12,25 @@ pub trait Merge {
|
|||||||
self.merge_in_place(other);
|
self.merge_in_place(other);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn merge_vecs<T: Ord + Eq>(this: &mut Vec<T>, mut other: Vec<T>) {
|
impl<T: Ord> Merge for Vec<T> {
|
||||||
this.append(&mut other);
|
fn merge_in_place(&mut self, mut other: Self) {
|
||||||
this.sort_unstable();
|
self.append(&mut other);
|
||||||
this.dedup();
|
self.sort_unstable();
|
||||||
|
self.dedup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Hash + PartialEq + Eq, V: Merge> Merge for HashMap<K, V> {
|
||||||
wojtek marked this conversation as resolved
Outdated
wojtek
commented
Is it possible to have a blanket merge with specialisations for ones that implement merge? Is it possible to have a blanket merge with specialisations for ones that implement merge?
|
|||||||
|
fn merge_in_place(&mut self, mut other: Self) {
|
||||||
|
for (other_key, other_value) in other.drain() {
|
||||||
|
if let Some(ref mut value) = self.get_mut(&other_key) {
|
||||||
|
value.merge_in_place(other_value)
|
||||||
|
} else {
|
||||||
|
self.insert(other_key, other_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,10 @@ pub static DATABASE_JSON: &str = "[\
|
|||||||
{\
|
{\
|
||||||
\"id\":{\"name\":\"album_artist a\"},\
|
\"id\":{\"name\":\"album_artist a\"},\
|
||||||
\"sort\":null,\
|
\"sort\":null,\
|
||||||
\"properties\":{\
|
|
||||||
\"musicbrainz\":\"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000\",\
|
\"musicbrainz\":\"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000\",\
|
||||||
\"musicbutler\":[\"https://www.musicbutler.io/artist-page/000000000\"],\
|
\"properties\":{\
|
||||||
\"bandcamp\":[],\
|
\"MusicButler\":[\"https://www.musicbutler.io/artist-page/000000000\"],\
|
||||||
\"qobuz\":\"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums\"\
|
\"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums\"]\
|
||||||
},\
|
},\
|
||||||
\"albums\":[\
|
\"albums\":[\
|
||||||
{\
|
{\
|
||||||
@ -54,14 +53,14 @@ pub static DATABASE_JSON: &str = "[\
|
|||||||
{\
|
{\
|
||||||
\"id\":{\"name\":\"album_artist b\"},\
|
\"id\":{\"name\":\"album_artist b\"},\
|
||||||
\"sort\":null,\
|
\"sort\":null,\
|
||||||
\"properties\":{\
|
|
||||||
\"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\
|
\"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\
|
||||||
\"musicbutler\":[\
|
\"properties\":{\
|
||||||
|
\"Bandcamp\":[\"https://artist-b.bandcamp.com/\"],\
|
||||||
|
\"MusicButler\":[\
|
||||||
\"https://www.musicbutler.io/artist-page/111111111\",\
|
\"https://www.musicbutler.io/artist-page/111111111\",\
|
||||||
\"https://www.musicbutler.io/artist-page/111111112\"\
|
\"https://www.musicbutler.io/artist-page/111111112\"\
|
||||||
],\
|
],\
|
||||||
\"bandcamp\":[\"https://artist-b.bandcamp.com/\"],\
|
\"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums\"]\
|
||||||
\"qobuz\":\"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums\"\
|
|
||||||
},\
|
},\
|
||||||
\"albums\":[\
|
\"albums\":[\
|
||||||
{\
|
{\
|
||||||
@ -129,12 +128,8 @@ pub static DATABASE_JSON: &str = "[\
|
|||||||
{\
|
{\
|
||||||
\"id\":{\"name\":\"album_artist c\"},\
|
\"id\":{\"name\":\"album_artist c\"},\
|
||||||
\"sort\":null,\
|
\"sort\":null,\
|
||||||
\"properties\":{\
|
|
||||||
\"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\
|
\"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\
|
||||||
\"musicbutler\":[],\
|
\"properties\":{},\
|
||||||
\"bandcamp\":[],\
|
|
||||||
\"qobuz\":null\
|
|
||||||
},\
|
|
||||||
\"albums\":[\
|
\"albums\":[\
|
||||||
{\
|
{\
|
||||||
\"id\":{\"year\":1985,\"title\":\"album_title c.a\"},\
|
\"id\":{\"year\":1985,\"title\":\"album_title c.a\"},\
|
||||||
@ -171,12 +166,8 @@ pub static DATABASE_JSON: &str = "[\
|
|||||||
{\
|
{\
|
||||||
\"id\":{\"name\":\"album_artist d\"},\
|
\"id\":{\"name\":\"album_artist d\"},\
|
||||||
\"sort\":null,\
|
\"sort\":null,\
|
||||||
\"properties\":{\
|
|
||||||
\"musicbrainz\":null,\
|
\"musicbrainz\":null,\
|
||||||
\"musicbutler\":[],\
|
\"properties\":{},\
|
||||||
\"bandcamp\":[],\
|
|
||||||
\"qobuz\":null\
|
|
||||||
},\
|
|
||||||
\"albums\":[\
|
\"albums\":[\
|
||||||
{\
|
{\
|
||||||
\"id\":{\"year\":1995,\"title\":\"album_title d.a\"},\
|
\"id\":{\"year\":1995,\"title\":\"album_title d.a\"},\
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use paste::paste;
|
|
||||||
|
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{Album, AlbumId},
|
album::{Album, AlbumId},
|
||||||
@ -31,81 +29,6 @@ pub struct NoLibrary;
|
|||||||
/// Phantom type for when a database implementation is not needed.
|
/// Phantom type for when a database implementation is not needed.
|
||||||
pub struct NoDatabase;
|
pub struct NoDatabase;
|
||||||
|
|
||||||
macro_rules! music_hoard_unique_url_dispatch {
|
|
||||||
($field:ident) => {
|
|
||||||
paste! {
|
|
||||||
pub fn [<add_ $field _url>]<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ID,
|
|
||||||
url: S,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Ok(self.get_artist_mut_or_err(artist_id.as_ref())?.[<add_ $field _url>](url)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<remove_ $field _url>]<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ID,
|
|
||||||
url: S,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Ok(self.get_artist_mut_or_err(artist_id.as_ref())?.[<remove_ $field _url>](url)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<set_ $field _url>]<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ID,
|
|
||||||
url: S,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Ok(self.get_artist_mut_or_err(artist_id.as_ref())?.[<set_ $field _url>](url)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<clear_ $field _url>]<ID: AsRef<ArtistId>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ID,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.get_artist_mut_or_err(artist_id.as_ref())?.[<clear_ $field _url>]();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! music_hoard_multi_url_dispatch {
|
|
||||||
($field:ident) => {
|
|
||||||
paste! {
|
|
||||||
pub fn [<add_ $field _urls>]<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ID,
|
|
||||||
urls: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Ok(self.get_artist_mut_or_err(artist_id.as_ref())?.[<add_ $field _urls>](urls)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<remove_ $field _urls>]<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ID,
|
|
||||||
urls: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Ok(self.get_artist_mut_or_err(artist_id.as_ref())?.[<remove_ $field _urls>](urls)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<set_ $field _urls>]<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
artist_id: ID,
|
|
||||||
urls: Vec<S>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Ok(self.get_artist_mut_or_err(artist_id.as_ref())?.[<set_ $field _urls>](urls)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<clear_ $field _urls>]<ID: AsRef<ArtistId>>(
|
|
||||||
&mut self, artist_id: ID,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.get_artist_mut_or_err(artist_id.as_ref())?.[<clear_ $field _urls>]();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MusicHoard<NoLibrary, NoDatabase> {
|
impl Default for MusicHoard<NoLibrary, NoDatabase> {
|
||||||
/// Create a new [`MusicHoard`] without any library or database.
|
/// Create a new [`MusicHoard`] without any library or database.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -167,13 +90,87 @@ impl<LIB, DB> MusicHoard<LIB, DB> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
music_hoard_unique_url_dispatch!(musicbrainz);
|
pub fn add_musicbrainz_url<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: ID,
|
||||||
|
url: S,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.get_artist_mut_or_err(artist_id.as_ref())?
|
||||||
|
.add_musicbrainz_url(url)?)
|
||||||
|
}
|
||||||
|
|
||||||
music_hoard_multi_url_dispatch!(musicbutler);
|
pub fn remove_musicbrainz_url<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: ID,
|
||||||
|
url: S,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.get_artist_mut_or_err(artist_id.as_ref())?
|
||||||
|
.remove_musicbrainz_url(url)?)
|
||||||
|
}
|
||||||
|
|
||||||
music_hoard_multi_url_dispatch!(bandcamp);
|
pub fn set_musicbrainz_url<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: ID,
|
||||||
|
url: S,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.get_artist_mut_or_err(artist_id.as_ref())?
|
||||||
|
.set_musicbrainz_url(url)?)
|
||||||
|
}
|
||||||
|
|
||||||
music_hoard_unique_url_dispatch!(qobuz);
|
pub fn clear_musicbrainz_url<ID: AsRef<ArtistId>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: ID,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.get_artist_mut_or_err(artist_id.as_ref())?
|
||||||
|
.clear_musicbrainz_url())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_property<ID: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: ID,
|
||||||
|
property: S,
|
||||||
|
values: Vec<S>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.get_artist_mut_or_err(artist_id.as_ref())?
|
||||||
|
.add_to_property(property, values))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_from_property<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: ID,
|
||||||
|
property: S,
|
||||||
|
values: Vec<S>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.get_artist_mut_or_err(artist_id.as_ref())?
|
||||||
|
.remove_from_property(property, values))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_property<ID: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: ID,
|
||||||
|
property: S,
|
||||||
|
values: Vec<S>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.get_artist_mut_or_err(artist_id.as_ref())?
|
||||||
|
.set_property(property, values))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_property<ID: AsRef<ArtistId>, S: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
artist_id: ID,
|
||||||
|
property: S,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(self
|
||||||
|
.get_artist_mut_or_err(artist_id.as_ref())?
|
||||||
|
.clear_property(property))
|
||||||
|
}
|
||||||
|
|
||||||
fn sort(collection: &mut [Artist]) {
|
fn sort(collection: &mut [Artist]) {
|
||||||
Self::sort_artists(collection);
|
Self::sort_artists(collection);
|
||||||
@ -324,7 +321,7 @@ mod tests {
|
|||||||
use mockall::predicate;
|
use mockall::predicate;
|
||||||
|
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
collection::artist::{ArtistId, Bandcamp, MusicBrainz, MusicButler, Qobuz},
|
collection::artist::{ArtistId, MusicBrainz},
|
||||||
database::{self, MockIDatabase},
|
database::{self, MockIDatabase},
|
||||||
library::{self, testmod::LIBRARY_ITEMS, MockILibrary},
|
library::{self, testmod::LIBRARY_ITEMS, MockILibrary},
|
||||||
testmod::{FULL_COLLECTION, LIBRARY_COLLECTION},
|
testmod::{FULL_COLLECTION, LIBRARY_COLLECTION},
|
||||||
@ -336,9 +333,6 @@ mod tests {
|
|||||||
"https://musicbrainz.org/artist/d368baa8-21ca-4759-9731-0b2753071ad8";
|
"https://musicbrainz.org/artist/d368baa8-21ca-4759-9731-0b2753071ad8";
|
||||||
static MUSICBUTLER: &str = "https://www.musicbutler.io/artist-page/483340948";
|
static MUSICBUTLER: &str = "https://www.musicbutler.io/artist-page/483340948";
|
||||||
static MUSICBUTLER_2: &str = "https://www.musicbutler.io/artist-page/658903042/";
|
static MUSICBUTLER_2: &str = "https://www.musicbutler.io/artist-page/658903042/";
|
||||||
static BANDCAMP: &str = "https://thelasthangmen.bandcamp.com/";
|
|
||||||
static BANDCAMP_2: &str = "https://viciouscrusade.bandcamp.com/";
|
|
||||||
static QOBUZ: &str = "https://www.qobuz.com/nl-nl/interpreter/the-last-hangmen/1244413";
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn artist_new_delete() {
|
fn artist_new_delete() {
|
||||||
@ -417,10 +411,10 @@ mod tests {
|
|||||||
music_hoard.add_artist(artist_id.clone());
|
music_hoard.add_artist(artist_id.clone());
|
||||||
|
|
||||||
let actual_err = music_hoard
|
let actual_err = music_hoard
|
||||||
.add_musicbrainz_url(&artist_id, QOBUZ)
|
.add_musicbrainz_url(&artist_id, MUSICBUTLER)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
let expected_err = Error::CollectionError(format!(
|
let expected_err = Error::CollectionError(format!(
|
||||||
"an error occurred when processing a URL: invalid MusicBrainz URL: {QOBUZ}"
|
"an error occurred when processing a URL: invalid MusicBrainz URL: {MUSICBUTLER}"
|
||||||
));
|
));
|
||||||
assert_eq!(actual_err, expected_err);
|
assert_eq!(actual_err, expected_err);
|
||||||
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
||||||
@ -435,33 +429,33 @@ mod tests {
|
|||||||
music_hoard.add_artist(artist_id.clone());
|
music_hoard.add_artist(artist_id.clone());
|
||||||
|
|
||||||
let mut expected: Option<MusicBrainz> = None;
|
let mut expected: Option<MusicBrainz> = None;
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Adding URL to an artist not in the collection is an error.
|
// Adding URL to an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.add_musicbrainz_url(&artist_id_2, MUSICBRAINZ)
|
.add_musicbrainz_url(&artist_id_2, MUSICBRAINZ)
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Adding URL to artist.
|
// Adding URL to artist.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.add_musicbrainz_url(&artist_id, MUSICBRAINZ)
|
.add_musicbrainz_url(&artist_id, MUSICBRAINZ)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap());
|
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Removing a URL from an artist not in the collection is an error.
|
// Removing a URL from an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.remove_musicbrainz_url(&artist_id_2, MUSICBRAINZ)
|
.remove_musicbrainz_url(&artist_id_2, MUSICBRAINZ)
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Removing a URL in the collection removes it.
|
// Removing a URL in the collection removes it.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.remove_musicbrainz_url(&artist_id, MUSICBRAINZ)
|
.remove_musicbrainz_url(&artist_id, MUSICBRAINZ)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
_ = expected.take();
|
_ = expected.take();
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -473,237 +467,116 @@ mod tests {
|
|||||||
music_hoard.add_artist(artist_id.clone());
|
music_hoard.add_artist(artist_id.clone());
|
||||||
|
|
||||||
let mut expected: Option<MusicBrainz> = None;
|
let mut expected: Option<MusicBrainz> = None;
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Setting a URL on an artist not in the collection is an error.
|
// Setting a URL on an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_musicbrainz_url(&artist_id_2, MUSICBRAINZ)
|
.set_musicbrainz_url(&artist_id_2, MUSICBRAINZ)
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Setting a URL on an artist.
|
// Setting a URL on an artist.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_musicbrainz_url(&artist_id, MUSICBRAINZ)
|
.set_musicbrainz_url(&artist_id, MUSICBRAINZ)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap());
|
_ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Clearing URLs on an artist that does not exist is an error.
|
// Clearing URLs on an artist that does not exist is an error.
|
||||||
assert!(music_hoard.clear_musicbrainz_url(&artist_id_2).is_err());
|
assert!(music_hoard.clear_musicbrainz_url(&artist_id_2).is_err());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
|
|
||||||
// Clearing URLs.
|
// Clearing URLs.
|
||||||
assert!(music_hoard.clear_musicbrainz_url(&artist_id).is_ok());
|
assert!(music_hoard.clear_musicbrainz_url(&artist_id).is_ok());
|
||||||
_ = expected.take();
|
_ = expected.take();
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbrainz, expected);
|
assert_eq!(music_hoard.collection[0].musicbrainz, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_remove_musicbutler_urls() {
|
fn add_to_remove_from_property() {
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist_id = ArtistId::new("an artist");
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
let artist_id_2 = ArtistId::new("another artist");
|
||||||
let mut music_hoard = MusicHoard::default();
|
let mut music_hoard = MusicHoard::default();
|
||||||
|
|
||||||
music_hoard.add_artist(artist_id.clone());
|
music_hoard.add_artist(artist_id.clone());
|
||||||
|
|
||||||
let mut expected: Vec<MusicButler> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
assert!(music_hoard.collection[0].properties.is_empty());
|
||||||
|
|
||||||
// Adding URLs to an artist not in the collection is an error.
|
// Adding URLs to an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.add_musicbutler_urls(&artist_id_2, vec![MUSICBUTLER])
|
.add_to_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
assert!(music_hoard.collection[0].properties.is_empty());
|
||||||
|
|
||||||
// Adding mutliple URLs without clashes.
|
// Adding mutliple URLs without clashes.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.add_musicbutler_urls(&artist_id, vec![MUSICBUTLER, MUSICBUTLER_2])
|
.add_to_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2])
|
||||||
.is_ok());
|
.is_ok());
|
||||||
expected.push(MusicButler::new(MUSICBUTLER).unwrap());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
expected.push(MusicButler::new(MUSICBUTLER_2).unwrap());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
assert_eq!(
|
||||||
|
music_hoard.collection[0].properties.get("MusicButler"),
|
||||||
|
Some(&expected)
|
||||||
|
);
|
||||||
|
|
||||||
// Removing URLs from an artist not in the collection is an error.
|
// Removing URLs from an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.remove_musicbutler_urls(&artist_id_2, vec![MUSICBUTLER])
|
.remove_from_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
assert_eq!(
|
||||||
|
music_hoard.collection[0].properties.get("MusicButler"),
|
||||||
|
Some(&expected)
|
||||||
|
);
|
||||||
|
|
||||||
// Removing multiple URLs without clashes.
|
// Removing multiple URLs without clashes.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.remove_musicbutler_urls(&artist_id, vec![MUSICBUTLER, MUSICBUTLER_2])
|
.remove_from_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2])
|
||||||
.is_ok());
|
.is_ok());
|
||||||
expected.clear();
|
expected.clear();
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
assert!(music_hoard.collection[0].properties.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_clear_musicbutler_urls() {
|
fn set_clear_property() {
|
||||||
let artist_id = ArtistId::new("an artist");
|
let artist_id = ArtistId::new("an artist");
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
let artist_id_2 = ArtistId::new("another artist");
|
||||||
let mut music_hoard = MusicHoard::default();
|
let mut music_hoard = MusicHoard::default();
|
||||||
|
|
||||||
music_hoard.add_artist(artist_id.clone());
|
music_hoard.add_artist(artist_id.clone());
|
||||||
|
|
||||||
let mut expected: Vec<MusicButler> = vec![];
|
let mut expected: Vec<String> = vec![];
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
assert!(music_hoard.collection[0].properties.is_empty());
|
||||||
|
|
||||||
// Seting URL on an artist not in the collection is an error.
|
// Seting URL on an artist not in the collection is an error.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_musicbutler_urls(&artist_id_2, vec![MUSICBUTLER])
|
.set_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER])
|
||||||
.is_err());
|
.is_err());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
assert!(music_hoard.collection[0].properties.is_empty());
|
||||||
|
|
||||||
// Set URLs.
|
// Set URLs.
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.set_musicbutler_urls(&artist_id, vec![MUSICBUTLER, MUSICBUTLER_2])
|
.set_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2])
|
||||||
.is_ok());
|
.is_ok());
|
||||||
expected.clear();
|
expected.clear();
|
||||||
expected.push(MusicButler::new(MUSICBUTLER).unwrap());
|
expected.push(MUSICBUTLER.to_owned());
|
||||||
expected.push(MusicButler::new(MUSICBUTLER_2).unwrap());
|
expected.push(MUSICBUTLER_2.to_owned());
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
assert_eq!(
|
||||||
|
music_hoard.collection[0].properties.get("MusicButler"),
|
||||||
|
Some(&expected)
|
||||||
|
);
|
||||||
|
|
||||||
// Clearing URLs on an artist that does not exist is an error.
|
// Clearing URLs on an artist that does not exist is an error.
|
||||||
assert!(music_hoard.clear_musicbutler_urls(&artist_id_2).is_err());
|
assert!(music_hoard
|
||||||
|
.clear_property(&artist_id_2, "MusicButler")
|
||||||
|
.is_err());
|
||||||
|
|
||||||
// Clear URLs.
|
// Clear URLs.
|
||||||
assert!(music_hoard.clear_musicbutler_urls(&artist_id).is_ok());
|
|
||||||
expected.clear();
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.musicbutler, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_remove_bandcamp_urls() {
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
|
||||||
let mut music_hoard = MusicHoard::default();
|
|
||||||
|
|
||||||
music_hoard.add_artist(artist_id.clone());
|
|
||||||
|
|
||||||
let mut expected: Vec<Bandcamp> = vec![];
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Adding URLs to an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard
|
assert!(music_hoard
|
||||||
.add_bandcamp_urls(&artist_id_2, vec![BANDCAMP])
|
.clear_property(&artist_id, "MusicButler")
|
||||||
.is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Adding mutliple URLs without clashes.
|
|
||||||
assert!(music_hoard
|
|
||||||
.add_bandcamp_urls(&artist_id, vec![BANDCAMP, BANDCAMP_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP).unwrap());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP_2).unwrap());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Removing URLs from an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.remove_bandcamp_urls(&artist_id_2, vec![BANDCAMP])
|
|
||||||
.is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Removing multiple URLs without clashes.
|
|
||||||
assert!(music_hoard
|
|
||||||
.remove_bandcamp_urls(&artist_id, vec![BANDCAMP, BANDCAMP_2])
|
|
||||||
.is_ok());
|
.is_ok());
|
||||||
expected.clear();
|
expected.clear();
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
assert!(music_hoard.collection[0].properties.is_empty());
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_clear_bandcamp_urls() {
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
|
||||||
let mut music_hoard = MusicHoard::default();
|
|
||||||
|
|
||||||
music_hoard.add_artist(artist_id.clone());
|
|
||||||
|
|
||||||
let mut expected: Vec<Bandcamp> = vec![];
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Seting URL on an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard
|
|
||||||
.set_bandcamp_urls(&artist_id_2, vec![BANDCAMP])
|
|
||||||
.is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Set URLs.
|
|
||||||
assert!(music_hoard
|
|
||||||
.set_bandcamp_urls(&artist_id, vec![BANDCAMP, BANDCAMP_2])
|
|
||||||
.is_ok());
|
|
||||||
expected.clear();
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP).unwrap());
|
|
||||||
expected.push(Bandcamp::new(BANDCAMP_2).unwrap());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
|
||||||
|
|
||||||
// Clearing URLs on an artist that does not exist is an error.
|
|
||||||
assert!(music_hoard.clear_bandcamp_urls(&artist_id_2).is_err());
|
|
||||||
|
|
||||||
// Clear URLs.
|
|
||||||
assert!(music_hoard.clear_bandcamp_urls(&artist_id).is_ok());
|
|
||||||
expected.clear();
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.bandcamp, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_remove_qobuz_url() {
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
|
||||||
let mut music_hoard = MusicHoard::default();
|
|
||||||
|
|
||||||
music_hoard.add_artist(artist_id.clone());
|
|
||||||
|
|
||||||
let mut expected: Option<Qobuz> = None;
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Adding URL to an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard.add_qobuz_url(&artist_id_2, QOBUZ).is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Adding URL to artist.
|
|
||||||
assert!(music_hoard.add_qobuz_url(&artist_id, QOBUZ).is_ok());
|
|
||||||
_ = expected.insert(Qobuz::new(QOBUZ).unwrap());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Removing a URL from an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard.remove_qobuz_url(&artist_id_2, QOBUZ).is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Removing a URL in the collection removes it.
|
|
||||||
assert!(music_hoard.remove_qobuz_url(&artist_id, QOBUZ).is_ok());
|
|
||||||
_ = expected.take();
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_clear_qobuz_url() {
|
|
||||||
let artist_id = ArtistId::new("an artist");
|
|
||||||
let artist_id_2 = ArtistId::new("another artist");
|
|
||||||
let mut music_hoard = MusicHoard::default();
|
|
||||||
|
|
||||||
music_hoard.add_artist(artist_id.clone());
|
|
||||||
|
|
||||||
let mut expected: Option<Qobuz> = None;
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Setting a URL on an artist not in the collection is an error.
|
|
||||||
assert!(music_hoard.set_qobuz_url(&artist_id_2, QOBUZ).is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Setting a URL on an artist.
|
|
||||||
assert!(music_hoard.set_qobuz_url(&artist_id, QOBUZ).is_ok());
|
|
||||||
_ = expected.insert(Qobuz::new(QOBUZ).unwrap());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Clearing URLs on an artist that does not exist is an error.
|
|
||||||
assert!(music_hoard.clear_qobuz_url(&artist_id_2).is_err());
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
|
|
||||||
// Clearing URLs.
|
|
||||||
assert!(music_hoard.clear_qobuz_url(&artist_id).is_ok());
|
|
||||||
_ = expected.take();
|
|
||||||
assert_eq!(music_hoard.collection[0].properties.qobuz, expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::core::collection::{
|
use crate::core::collection::{
|
||||||
album::{Album, AlbumId},
|
album::{Album, AlbumId},
|
||||||
artist::{Artist, ArtistId, ArtistProperties, Bandcamp, MusicBrainz, MusicButler, Qobuz},
|
artist::{Artist, ArtistId, MusicBrainz},
|
||||||
track::{Format, Quality, Track, TrackId},
|
track::{Format, Quality, Track, TrackId},
|
||||||
};
|
};
|
||||||
use crate::tests::*;
|
use crate::tests::*;
|
||||||
|
110
src/tests.rs
110
src/tests.rs
@ -6,12 +6,8 @@ macro_rules! library_collection {
|
|||||||
name: "album_artist a".to_string(),
|
name: "album_artist a".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
properties: ArtistProperties {
|
musicbrainz: None,
|
||||||
musicbrainz: None,
|
properties: HashMap::new(),
|
||||||
musicbutler: vec![],
|
|
||||||
bandcamp: vec![],
|
|
||||||
qobuz: None,
|
|
||||||
},
|
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
@ -105,12 +101,8 @@ macro_rules! library_collection {
|
|||||||
name: "album_artist b".to_string(),
|
name: "album_artist b".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
properties: ArtistProperties {
|
musicbrainz: None,
|
||||||
musicbrainz: None,
|
properties: HashMap::new(),
|
||||||
musicbutler: vec![],
|
|
||||||
bandcamp: vec![],
|
|
||||||
qobuz: None,
|
|
||||||
},
|
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
@ -251,12 +243,8 @@ macro_rules! library_collection {
|
|||||||
name: "album_artist c".to_string(),
|
name: "album_artist c".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
properties: ArtistProperties {
|
musicbrainz: None,
|
||||||
musicbrainz: None,
|
properties: HashMap::new(),
|
||||||
musicbutler: vec![],
|
|
||||||
bandcamp: vec![],
|
|
||||||
qobuz: None,
|
|
||||||
},
|
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
@ -331,12 +319,8 @@ macro_rules! library_collection {
|
|||||||
name: "album_artist d".to_string(),
|
name: "album_artist d".to_string(),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
properties: ArtistProperties {
|
musicbrainz: None,
|
||||||
musicbrainz: None,
|
properties: HashMap::new(),
|
||||||
musicbutler: vec![],
|
|
||||||
bandcamp: vec![],
|
|
||||||
qobuz: None,
|
|
||||||
},
|
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
@ -418,62 +402,54 @@ macro_rules! full_collection {
|
|||||||
let artist_a = iter.next().unwrap();
|
let artist_a = iter.next().unwrap();
|
||||||
assert_eq!(artist_a.id.name, "album_artist a");
|
assert_eq!(artist_a.id.name, "album_artist a");
|
||||||
|
|
||||||
artist_a.properties = ArtistProperties {
|
artist_a.musicbrainz = Some(
|
||||||
musicbrainz: Some(
|
MusicBrainz::new(
|
||||||
MusicBrainz::new(
|
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000",
|
||||||
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000",
|
)
|
||||||
)
|
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
),
|
);
|
||||||
musicbutler: vec![
|
|
||||||
MusicButler::new("https://www.musicbutler.io/artist-page/000000000").unwrap(),
|
artist_a.properties = HashMap::from([
|
||||||
],
|
(String::from("MusicButler"), vec![
|
||||||
bandcamp: vec![],
|
String::from("https://www.musicbutler.io/artist-page/000000000"),
|
||||||
qobuz: Some(
|
]),
|
||||||
Qobuz::new(
|
(String::from("Qobuz"), vec![
|
||||||
|
String::from(
|
||||||
"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums",
|
"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums",
|
||||||
)
|
)
|
||||||
.unwrap(),
|
]),
|
||||||
),
|
]);
|
||||||
};
|
|
||||||
|
|
||||||
let artist_b = iter.next().unwrap();
|
let artist_b = iter.next().unwrap();
|
||||||
assert_eq!(artist_b.id.name, "album_artist b");
|
assert_eq!(artist_b.id.name, "album_artist b");
|
||||||
|
|
||||||
artist_b.properties = ArtistProperties {
|
artist_b.musicbrainz = Some(
|
||||||
musicbrainz: Some(
|
MusicBrainz::new(
|
||||||
MusicBrainz::new(
|
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
|
||||||
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
|
).unwrap(),
|
||||||
)
|
);
|
||||||
.unwrap(),
|
|
||||||
),
|
artist_b.properties = HashMap::from([
|
||||||
musicbutler: vec![
|
(String::from("MusicButler"), vec![
|
||||||
MusicButler::new("https://www.musicbutler.io/artist-page/111111111").unwrap(),
|
String::from("https://www.musicbutler.io/artist-page/111111111"),
|
||||||
MusicButler::new("https://www.musicbutler.io/artist-page/111111112").unwrap(),
|
String::from("https://www.musicbutler.io/artist-page/111111112"),
|
||||||
],
|
]),
|
||||||
bandcamp: vec![Bandcamp::new("https://artist-b.bandcamp.com/").unwrap()],
|
(String::from("Bandcamp"), vec![String::from("https://artist-b.bandcamp.com/")]),
|
||||||
qobuz: Some(
|
(String::from("Qobuz"), vec![
|
||||||
Qobuz::new(
|
String::from(
|
||||||
"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums",
|
"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums",
|
||||||
)
|
)
|
||||||
.unwrap(),
|
]),
|
||||||
),
|
]);
|
||||||
};
|
|
||||||
|
|
||||||
let artist_c = iter.next().unwrap();
|
let artist_c = iter.next().unwrap();
|
||||||
assert_eq!(artist_c.id.name, "album_artist c");
|
assert_eq!(artist_c.id.name, "album_artist c");
|
||||||
|
|
||||||
artist_c.properties = ArtistProperties {
|
artist_c.musicbrainz = Some(
|
||||||
musicbrainz: Some(
|
MusicBrainz::new(
|
||||||
MusicBrainz::new(
|
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
|
||||||
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
|
).unwrap(),
|
||||||
)
|
);
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
musicbutler: vec![],
|
|
||||||
bandcamp: vec![],
|
|
||||||
qobuz: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Nothing for artist_d
|
// Nothing for artist_d
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{Album, AlbumId},
|
album::{Album, AlbumId},
|
||||||
artist::{Artist, ArtistId, ArtistProperties, Bandcamp, MusicBrainz, MusicButler, Qobuz},
|
artist::{Artist, ArtistId, MusicBrainz},
|
||||||
track::{Format, Quality, Track, TrackId},
|
track::{Format, Quality, Track, TrackId},
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::Album,
|
album::Album,
|
||||||
artist::Artist,
|
artist::Artist,
|
||||||
@ -188,27 +190,50 @@ impl<'a> ArtistOverlay<'a> {
|
|||||||
opt.flatten().map(|item| item.as_ref()).unwrap_or("")
|
opt.flatten().map(|item| item.as_ref()).unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn opt_vec_to_string<S: AsRef<str>>(opt_vec: Option<&Vec<S>>, indent: &str) -> String {
|
fn opt_hashmap_to_string<K: Ord + AsRef<str>, T: AsRef<str>>(
|
||||||
opt_vec
|
opt_map: Option<&HashMap<K, Vec<T>>>,
|
||||||
.map(|vec| {
|
item_indent: &str,
|
||||||
if vec.len() < 2 {
|
list_indent: &str,
|
||||||
vec.first()
|
) -> String {
|
||||||
.map(|item| item.as_ref())
|
opt_map
|
||||||
.unwrap_or("")
|
.map(|map| Self::hashmap_to_string(map, item_indent, list_indent))
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
let indent = format!("\n{indent}");
|
|
||||||
let list = vec
|
|
||||||
.iter()
|
|
||||||
.map(|item| item.as_ref())
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join(&indent);
|
|
||||||
format!("{indent}{list}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| String::from(""))
|
.unwrap_or_else(|| String::from(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hashmap_to_string<K: AsRef<str>, T: AsRef<str>>(
|
||||||
|
map: &HashMap<K, Vec<T>>,
|
||||||
|
item_indent: &str,
|
||||||
|
list_indent: &str,
|
||||||
|
) -> String {
|
||||||
|
let mut vec: Vec<(&str, &Vec<T>)> = map.iter().map(|(k, v)| (k.as_ref(), v)).collect();
|
||||||
|
vec.sort_by(|x, y| x.0.cmp(&y.0));
|
||||||
|
|
||||||
|
let indent = format!("\n{item_indent}");
|
||||||
|
let list = vec
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| format!("{k}: {}", Self::vec_to_string(v, list_indent)))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(&indent);
|
||||||
|
format!("{indent}{list}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vec_to_string<S: AsRef<str>>(vec: &Vec<S>, indent: &str) -> String {
|
||||||
|
if vec.len() < 2 {
|
||||||
|
vec.first()
|
||||||
|
.map(|item| item.as_ref())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
let indent = format!("\n{indent}");
|
||||||
|
let list = vec
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.as_ref())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(&indent);
|
||||||
|
format!("{indent}{list}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new(artists: &'a [Artist], state: &ListState) -> ArtistOverlay<'a> {
|
fn new(artists: &'a [Artist], state: &ListState) -> ArtistOverlay<'a> {
|
||||||
let artist = state.selected().map(|i| &artists[i]);
|
let artist = state.selected().map(|i| &artists[i]);
|
||||||
|
|
||||||
@ -216,15 +241,10 @@ impl<'a> ArtistOverlay<'a> {
|
|||||||
let list_indent = " - ";
|
let list_indent = " - ";
|
||||||
let properties = Paragraph::new(format!(
|
let properties = Paragraph::new(format!(
|
||||||
"Artist: {}\n\n{item_indent}\
|
"Artist: {}\n\n{item_indent}\
|
||||||
MusicBrainz: {}\n{item_indent}\
|
MusicBrainz: {}{}",
|
||||||
MusicButler: {}\n{item_indent}\
|
|
||||||
Bandcamp: {}\n{item_indent}\
|
|
||||||
Qobuz: {}",
|
|
||||||
artist.map(|a| a.id.name.as_str()).unwrap_or(""),
|
artist.map(|a| a.id.name.as_str()).unwrap_or(""),
|
||||||
Self::opt_opt_to_str(artist.map(|a| a.properties.musicbrainz.as_ref())),
|
Self::opt_opt_to_str(artist.map(|a| a.musicbrainz.as_ref())),
|
||||||
Self::opt_vec_to_string(artist.map(|a| &a.properties.musicbutler), list_indent),
|
Self::opt_hashmap_to_string(artist.map(|a| &a.properties), item_indent, list_indent),
|
||||||
Self::opt_vec_to_string(artist.map(|a| &a.properties.bandcamp), list_indent),
|
|
||||||
Self::opt_opt_to_str(artist.map(|a| a.properties.qobuz.as_ref())),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
ArtistOverlay { properties }
|
ArtistOverlay { properties }
|
||||||
|
File diff suppressed because one or more lines are too long
1
tests/files/database/tmp.json
Normal file
1
tests/files/database/tmp.json
Normal file
File diff suppressed because one or more lines are too long
113
tests/testlib.rs
113
tests/testlib.rs
@ -1,8 +1,9 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{Album, AlbumId},
|
album::{Album, AlbumId},
|
||||||
artist::{Artist, ArtistId, ArtistProperties, Bandcamp, MusicBrainz, MusicButler, Qobuz},
|
artist::{Artist, ArtistId, MusicBrainz},
|
||||||
track::{Format, Quality, Track, TrackId},
|
track::{Format, Quality, Track, TrackId},
|
||||||
Collection,
|
Collection,
|
||||||
};
|
};
|
||||||
@ -16,20 +17,20 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
sort: Some(ArtistId{
|
sort: Some(ArtistId{
|
||||||
name: String::from("Arkona")
|
name: String::from("Arkona")
|
||||||
}),
|
}),
|
||||||
properties: ArtistProperties {
|
musicbrainz: Some(MusicBrainz::new(
|
||||||
musicbrainz: Some(MusicBrainz::new(
|
"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212",
|
||||||
"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212",
|
).unwrap()),
|
||||||
).unwrap()),
|
properties: HashMap::from([
|
||||||
musicbutler: vec![
|
(String::from("MusicButler"), vec![
|
||||||
MusicButler::new("https://www.musicbutler.io/artist-page/283448581").unwrap(),
|
String::from("https://www.musicbutler.io/artist-page/283448581"),
|
||||||
],
|
]),
|
||||||
bandcamp: vec![
|
(String::from("Bandcamp"), vec![
|
||||||
Bandcamp::new("https://arkonamoscow.bandcamp.com/").unwrap(),
|
String::from("https://arkonamoscow.bandcamp.com/"),
|
||||||
],
|
]),
|
||||||
qobuz: Some(Qobuz::new(
|
(String::from("Qobuz"), vec![String::from(
|
||||||
"https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums",
|
"https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums",
|
||||||
).unwrap()),
|
)]),
|
||||||
},
|
]),
|
||||||
albums: vec![Album {
|
albums: vec![Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
year: 2011,
|
year: 2011,
|
||||||
@ -198,18 +199,17 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Eluveitie"),
|
name: String::from("Eluveitie"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
properties: ArtistProperties {
|
musicbrainz: Some(MusicBrainz::new(
|
||||||
musicbrainz: Some(MusicBrainz::new(
|
"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38",
|
||||||
"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38",
|
).unwrap()),
|
||||||
).unwrap()),
|
properties: HashMap::from([
|
||||||
musicbutler: vec![
|
(String::from("MusicButler"), vec![
|
||||||
MusicButler::new("https://www.musicbutler.io/artist-page/269358403").unwrap(),
|
String::from("https://www.musicbutler.io/artist-page/269358403"),
|
||||||
],
|
]),
|
||||||
bandcamp: vec![],
|
(String::from("Qobuz"), vec![String::from(
|
||||||
qobuz: Some(Qobuz::new(
|
|
||||||
"https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums",
|
"https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums",
|
||||||
).unwrap()),
|
)]),
|
||||||
},
|
]),
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
@ -432,18 +432,17 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Frontside"),
|
name: String::from("Frontside"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
properties: ArtistProperties {
|
musicbrainz: Some(MusicBrainz::new(
|
||||||
musicbrainz: Some(MusicBrainz::new(
|
"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490",
|
||||||
"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490",
|
).unwrap()),
|
||||||
).unwrap()),
|
properties: HashMap::from([
|
||||||
musicbutler: vec![
|
(String::from("MusicButler"), vec![
|
||||||
MusicButler::new("https://www.musicbutler.io/artist-page/826588800").unwrap(),
|
String::from("https://www.musicbutler.io/artist-page/826588800"),
|
||||||
],
|
]),
|
||||||
bandcamp: vec![],
|
(String::from("Qobuz"), vec![String::from(
|
||||||
qobuz: Some(Qobuz::new(
|
|
||||||
"https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums",
|
"https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums",
|
||||||
).unwrap()),
|
)]),
|
||||||
},
|
]),
|
||||||
albums: vec![Album {
|
albums: vec![Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
year: 2001,
|
year: 2001,
|
||||||
@ -581,18 +580,17 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
sort: Some(ArtistId {
|
sort: Some(ArtistId {
|
||||||
name: String::from("Heaven’s Basement"),
|
name: String::from("Heaven’s Basement"),
|
||||||
}),
|
}),
|
||||||
properties: ArtistProperties {
|
musicbrainz: Some(MusicBrainz::new(
|
||||||
musicbrainz: Some(MusicBrainz::new(
|
"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc",
|
||||||
"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc",
|
).unwrap()),
|
||||||
).unwrap()),
|
properties: HashMap::from([
|
||||||
musicbutler: vec![
|
(String::from("MusicButler"), vec![
|
||||||
MusicButler::new("https://www.musicbutler.io/artist-page/291158685").unwrap(),
|
String::from("https://www.musicbutler.io/artist-page/291158685"),
|
||||||
],
|
]),
|
||||||
bandcamp: vec![],
|
(String::from("Qobuz"), vec![String::from(
|
||||||
qobuz: Some(Qobuz::new(
|
|
||||||
"https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums",
|
"https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums",
|
||||||
).unwrap()),
|
)]),
|
||||||
},
|
]),
|
||||||
albums: vec![Album {
|
albums: vec![Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
year: 2011,
|
year: 2011,
|
||||||
@ -702,18 +700,17 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
|||||||
name: String::from("Metallica"),
|
name: String::from("Metallica"),
|
||||||
},
|
},
|
||||||
sort: None,
|
sort: None,
|
||||||
properties: ArtistProperties {
|
musicbrainz: Some(MusicBrainz::new(
|
||||||
musicbrainz: Some(MusicBrainz::new(
|
"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab",
|
||||||
"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab",
|
).unwrap()),
|
||||||
).unwrap()),
|
properties: HashMap::from([
|
||||||
musicbutler: vec![
|
(String::from("MusicButler"), vec![
|
||||||
MusicButler::new("https://www.musicbutler.io/artist-page/3996865").unwrap(),
|
String::from("https://www.musicbutler.io/artist-page/3996865"),
|
||||||
],
|
]),
|
||||||
bandcamp: vec![],
|
(String::from("Qobuz"), vec![String::from(
|
||||||
qobuz: Some(Qobuz::new(
|
|
||||||
"https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums",
|
"https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums",
|
||||||
).unwrap()),
|
)]),
|
||||||
},
|
]),
|
||||||
albums: vec![
|
albums: vec![
|
||||||
Album {
|
Album {
|
||||||
id: AlbumId {
|
id: AlbumId {
|
||||||
|
Loading…
Reference in New Issue
Block a user
This couples the on-disk database serialisation with the in-memory abstract representation. To be fair, it was always there under the subtle guise of
Serialize
/Deserialize
. Think whether to create the split here, or in a separate issue following this one.