Resolve "Local collection trait and beets implementation" #9
@ -59,21 +59,17 @@ impl DatabaseWrite for DatabaseJson {
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use tempfile::NamedTempFile;
|
||||
use uuid::uuid;
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::{Album, AlbumArtist, Track};
|
||||
use crate::{Album, Track};
|
||||
|
||||
const TEST_FILENAME: &str = "tests/files/database_json_test.json";
|
||||
|
||||
fn test_data() -> Vec<Album> {
|
||||
vec![
|
||||
Album {
|
||||
artist: AlbumArtist {
|
||||
name: String::from("Artist A"),
|
||||
mbid: Some(uuid!("f7769831-746b-4a12-8124-0123d7fe17c9")),
|
||||
},
|
||||
artist: String::from("Artist A"),
|
||||
year: 1998,
|
||||
title: String::from("Release group A"),
|
||||
tracks: vec![
|
||||
@ -95,10 +91,7 @@ mod tests {
|
||||
],
|
||||
},
|
||||
Album {
|
||||
artist: AlbumArtist {
|
||||
name: String::from("Artist B"),
|
||||
mbid: None,
|
||||
},
|
||||
artist: String::from("Artist B"),
|
||||
year: 2008,
|
||||
title: String::from("Release group B"),
|
||||
tracks: vec![Track {
|
||||
|
13
src/lib.rs
13
src/lib.rs
@ -9,15 +9,8 @@ pub mod library;
|
||||
/// [MusicBrainz Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID).
|
||||
pub type Mbid = Uuid;
|
||||
|
||||
/// An album artist. Carries a MBID to facilitate discography access.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct AlbumArtist {
|
||||
pub name: String,
|
||||
pub mbid: Option<Mbid>,
|
||||
}
|
||||
|
||||
/// A single track on an album.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Track {
|
||||
pub number: u32,
|
||||
pub title: String,
|
||||
@ -25,9 +18,9 @@ pub struct Track {
|
||||
}
|
||||
|
||||
/// An album is a collection of tracks that were released together.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Album {
|
||||
pub artist: AlbumArtist,
|
||||
pub artist: String,
|
||||
pub year: u32,
|
||||
pub title: String,
|
||||
pub tracks: Vec<Track>,
|
||||
|
@ -1,13 +1,32 @@
|
||||
use std::{collections::HashSet, fmt::Display, process::Command};
|
||||
|
||||
use crate::{Album, AlbumArtist, Track};
|
||||
use crate::{Album, Track};
|
||||
|
||||
use super::{Error, Library, Query, QueryValue};
|
||||
use super::{Error, Library, Query, QueryOption};
|
||||
|
||||
impl<T: Display> QueryValue<T> {
|
||||
fn to_string(&self, option_name: &str) -> String {
|
||||
let negate = if self.negate { "^" } else { "" };
|
||||
format!("{}{}:{}", negate, option_name, self.value)
|
||||
pub trait SimpleOption {}
|
||||
impl SimpleOption for String {}
|
||||
impl SimpleOption for u32 {}
|
||||
|
||||
impl<T: SimpleOption + Display> QueryOption<T> {
|
||||
fn to_arg(&self, option_name: &str) -> Option<String> {
|
||||
let (negate, value) = match self {
|
||||
Self::Include(value) => ("", value),
|
||||
Self::Exclude(value) => ("^", value),
|
||||
Self::None => return None,
|
||||
};
|
||||
Some(format!("{}{}:{}", negate, option_name, value))
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryOption<Vec<String>> {
|
||||
fn to_arg(&self, option_name: &str) -> Option<String> {
|
||||
let (negate, vec) = match self {
|
||||
Self::Include(value) => ("", value),
|
||||
Self::Exclude(value) => ("^", value),
|
||||
Self::None => return None,
|
||||
};
|
||||
Some(format!("{}{}:{}", negate, option_name, vec.join("; ")))
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,24 +34,28 @@ impl Query {
|
||||
fn to_args(&self) -> Vec<String> {
|
||||
let mut arguments: Vec<String> = vec![];
|
||||
|
||||
if let Some(ref albumartist) = self.albumartist {
|
||||
arguments.push(albumartist.to_string("albumartist"));
|
||||
if let Some(album_artist) = self.album_artist.to_arg("albumartist") {
|
||||
arguments.push(album_artist);
|
||||
};
|
||||
|
||||
if let Some(ref album) = self.album {
|
||||
arguments.push(album.to_string("album"));
|
||||
if let Some(album_year) = self.album_year.to_arg("year") {
|
||||
arguments.push(album_year);
|
||||
};
|
||||
|
||||
if let Some(ref track) = self.track {
|
||||
arguments.push(track.to_string("track"));
|
||||
if let Some(album_title) = self.album_title.to_arg("album") {
|
||||
arguments.push(album_title);
|
||||
};
|
||||
|
||||
if let Some(ref title) = self.title {
|
||||
arguments.push(title.to_string("title"));
|
||||
if let Some(track_number) = self.track_number.to_arg("track") {
|
||||
arguments.push(track_number);
|
||||
};
|
||||
|
||||
if let Some(ref artist) = self.artist {
|
||||
arguments.push(artist.to_string("artist"));
|
||||
if let Some(track_title) = self.track_title.to_arg("title") {
|
||||
arguments.push(track_title);
|
||||
};
|
||||
|
||||
if let Some(track_artist) = self.track_artist.to_arg("artist") {
|
||||
arguments.push(track_artist);
|
||||
};
|
||||
|
||||
arguments
|
||||
@ -82,9 +105,7 @@ struct AlbumId {
|
||||
|
||||
impl AlbumId {
|
||||
fn matches(&self, album: &Album) -> bool {
|
||||
(self.artist == album.artist.name)
|
||||
&& (self.year == album.year)
|
||||
&& (self.title == album.title)
|
||||
(self.artist == album.artist) && (self.year == album.year) && (self.title == album.title)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,10 +171,7 @@ impl Beets {
|
||||
let album = albums.iter_mut().rev().find(|a| aid.matches(a)).unwrap();
|
||||
album.tracks.push(track);
|
||||
} else {
|
||||
let album_artist = AlbumArtist {
|
||||
name: aid.artist.to_string(),
|
||||
mbid: None,
|
||||
};
|
||||
let album_artist = aid.artist.to_string();
|
||||
let album_title = aid.title.to_string();
|
||||
album_ids.insert(aid);
|
||||
albums.push(Album {
|
||||
@ -201,10 +219,7 @@ mod tests {
|
||||
fn test_data() -> Vec<Album> {
|
||||
vec![
|
||||
Album {
|
||||
artist: AlbumArtist {
|
||||
name: "album_artist.a".to_string(),
|
||||
mbid: None,
|
||||
},
|
||||
artist: "album_artist.a".to_string(),
|
||||
year: 1998,
|
||||
title: "album_title.a".to_string(),
|
||||
tracks: vec![
|
||||
@ -226,10 +241,7 @@ mod tests {
|
||||
],
|
||||
},
|
||||
Album {
|
||||
artist: AlbumArtist {
|
||||
name: "album_artist.b".to_string(),
|
||||
mbid: None,
|
||||
},
|
||||
artist: "album_artist.b".to_string(),
|
||||
year: 2003,
|
||||
title: "album_title.b".to_string(),
|
||||
tracks: vec![
|
||||
@ -249,7 +261,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn album_to_beets_string(album: &Album) -> Vec<String> {
|
||||
let album_artist = &album.artist.name;
|
||||
let album_artist = &album.artist;
|
||||
let album_year = &album.year;
|
||||
let album_title = &album.title;
|
||||
|
||||
@ -271,29 +283,20 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_query() {
|
||||
let query = Query {
|
||||
albumartist: None,
|
||||
album: Some(QueryValue {
|
||||
negate: true,
|
||||
value: String::from("some.album"),
|
||||
}),
|
||||
track: Some(QueryValue {
|
||||
negate: false,
|
||||
value: 5,
|
||||
}),
|
||||
title: None,
|
||||
artist: Some(QueryValue {
|
||||
negate: false,
|
||||
value: String::from("some.artist"),
|
||||
}),
|
||||
};
|
||||
let query = Query::new()
|
||||
.album_title(QueryOption::exclude(String::from("some.album")))
|
||||
.track_number(QueryOption::include(5))
|
||||
.track_artist(QueryOption::include(vec![
|
||||
String::from("some.artist.1"),
|
||||
String::from("some.artist.2"),
|
||||
]));
|
||||
|
||||
assert_eq!(
|
||||
query.to_args(),
|
||||
vec![
|
||||
String::from("^album:some.album"),
|
||||
String::from("track:5"),
|
||||
String::from("artist:some.artist")
|
||||
String::from("artist:some.artist.1; some.artist.2")
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -381,22 +384,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_list_query() {
|
||||
let query = Query {
|
||||
albumartist: None,
|
||||
album: Some(QueryValue {
|
||||
negate: true,
|
||||
value: String::from("some.album"),
|
||||
}),
|
||||
track: Some(QueryValue {
|
||||
negate: false,
|
||||
value: 5,
|
||||
}),
|
||||
title: None,
|
||||
artist: Some(QueryValue {
|
||||
negate: false,
|
||||
value: String::from("some.artist"),
|
||||
}),
|
||||
};
|
||||
let query = Query::new()
|
||||
.album_title(QueryOption::exclude(String::from("some.album")))
|
||||
.track_number(QueryOption::include(5))
|
||||
.track_artist(QueryOption::include(vec![String::from("some.artist")]));
|
||||
|
||||
let executor = TestExecutor {
|
||||
arguments: Some(vec![
|
||||
|
@ -4,19 +4,90 @@ use crate::Album;
|
||||
|
||||
pub mod beets;
|
||||
|
||||
pub struct QueryValue<T> {
|
||||
negate: bool,
|
||||
value: T,
|
||||
/// A single query option.
|
||||
pub enum QueryOption<T> {
|
||||
/// Inclusive query.
|
||||
Include(T),
|
||||
/// Exclusive query.
|
||||
Exclude(T),
|
||||
/// No query.
|
||||
None,
|
||||
}
|
||||
|
||||
impl<T> QueryOption<T> {
|
||||
/// Create an inclusive query option.
|
||||
pub fn include(value: T) -> Self {
|
||||
QueryOption::Include(value)
|
||||
}
|
||||
|
||||
pub fn exclude(value: T) -> Self {
|
||||
QueryOption::Exclude(value)
|
||||
}
|
||||
|
||||
pub fn none() -> Self {
|
||||
QueryOption::None
|
||||
}
|
||||
|
||||
pub fn is_some(&self) -> bool {
|
||||
!matches!(self, QueryOption::None)
|
||||
}
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, QueryOption::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for QueryOption<T> {
|
||||
fn default() -> Self {
|
||||
Self::none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for refining library queries.
|
||||
#[derive(Default)]
|
||||
pub struct Query {
|
||||
albumartist: Option<QueryValue<String>>,
|
||||
album: Option<QueryValue<String>>,
|
||||
track: Option<QueryValue<u32>>,
|
||||
title: Option<QueryValue<String>>,
|
||||
artist: Option<QueryValue<String>>,
|
||||
album_artist: QueryOption<String>,
|
||||
album_year: QueryOption<u32>,
|
||||
album_title: QueryOption<String>,
|
||||
track_number: QueryOption<u32>,
|
||||
track_title: QueryOption<String>,
|
||||
track_artist: QueryOption<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn new() -> Self {
|
||||
Query::default()
|
||||
}
|
||||
|
||||
pub fn album_artist(mut self, album_artist: QueryOption<String>) -> Self {
|
||||
self.album_artist = album_artist;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn album_year(mut self, album_year: QueryOption<u32>) -> Self {
|
||||
self.album_year = album_year;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn album_title(mut self, album_title: QueryOption<String>) -> Self {
|
||||
self.album_title = album_title;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn track_number(mut self, track_number: QueryOption<u32>) -> Self {
|
||||
self.track_number = track_number;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn track_title(mut self, track_title: QueryOption<String>) -> Self {
|
||||
self.track_title = track_title;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn track_artist(mut self, track_artist: QueryOption<Vec<String>>) -> Self {
|
||||
self.track_artist = track_artist;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for library calls.
|
||||
|
@ -1 +1 @@
|
||||
[{"artist":{"name":"Artist A","mbid":"f7769831-746b-4a12-8124-0123d7fe17c9"},"year":1998,"title":"Release group A","tracks":[{"number":1,"title":"Track A.1","artist":["Artist A.A"]},{"number":2,"title":"Track A.2","artist":["Artist A.A"]},{"number":3,"title":"Track A.3","artist":["Artist A.A","Artist A.B"]}]},{"artist":{"name":"Artist B","mbid":null},"year":2008,"title":"Release group B","tracks":[{"number":1,"title":"Track B.1","artist":["Artist B.A"]}]}]
|
||||
[{"artist":"Artist A","year":1998,"title":"Release group A","tracks":[{"number":1,"title":"Track A.1","artist":["Artist A.A"]},{"number":2,"title":"Track A.2","artist":["Artist A.A"]},{"number":3,"title":"Track A.3","artist":["Artist A.A","Artist A.B"]}]},{"artist":"Artist B","year":2008,"title":"Release group B","tracks":[{"number":1,"title":"Track B.1","artist":["Artist B.A"]}]}]
|
Loading…
Reference in New Issue
Block a user