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