Resolve "Local collection trait and beets implementation" #9

Merged
wojtek merged 12 commits from 4---local-collection-trait-and-beets-implementation into main 2023-03-31 14:24:54 +02:00
4 changed files with 55 additions and 30 deletions
Showing only changes of commit 9f0cffdd4d - Show all commits

View File

@ -1,3 +1,5 @@
//! Module for storing MusicHoard data in a JSON file database.
use std::fs::File; use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::Path; use std::path::Path;

View File

@ -1,3 +1,5 @@
//! Module for storing MusicHoard data in a database.
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
@ -5,6 +7,7 @@ pub mod json;
/// Trait for database reads. /// Trait for database reads.
pub trait DatabaseRead { pub trait DatabaseRead {
/// Read collection from the database.
fn read<D>(&mut self, collection: &mut D) -> Result<(), std::io::Error> fn read<D>(&mut self, collection: &mut D) -> Result<(), std::io::Error>
where where
D: DeserializeOwned; D: DeserializeOwned;
@ -12,6 +15,7 @@ pub trait DatabaseRead {
/// Trait for database writes. /// Trait for database writes.
pub trait DatabaseWrite { pub trait DatabaseWrite {
/// Write collection to the database.
fn write<S>(&mut self, collection: &S) -> Result<(), std::io::Error> fn write<S>(&mut self, collection: &S) -> Result<(), std::io::Error>
where where
S: Serialize; S: Serialize;

View File

@ -1,3 +1,6 @@
//! Module for interacting with the music library via
//! [beets](https://beets.readthedocs.io/en/stable/).
use std::{collections::HashSet, fmt::Display, process::Command}; use std::{collections::HashSet, fmt::Display, process::Command};
use crate::{Album, AlbumId, Track}; use crate::{Album, AlbumId, Track};
@ -23,7 +26,7 @@ impl<T: SimpleOption + Display> QueryOptionArgBeets for QueryOption<T> {
Self::Exclude(value) => ("^", value), Self::Exclude(value) => ("^", value),
Self::None => return None, Self::None => return None,
}; };
Some(format!("{}{}:{}", negate, option_name, value)) Some(format!("{}{}{}", negate, option_name, value))
} }
} }
@ -34,7 +37,7 @@ impl QueryOptionArgBeets for QueryOption<Vec<String>> {
Self::Exclude(value) => ("^", value), Self::Exclude(value) => ("^", value),
Self::None => return None, Self::None => return None,
}; };
Some(format!("{}{}:{}", negate, option_name, vec.join("; "))) Some(format!("{}{}{}", negate, option_name, vec.join("; ")))
} }
} }
@ -42,38 +45,45 @@ impl QueryArgsBeets for 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(album_artist) = self.album_artist.to_arg("albumartist") { if let Some(album_artist) = self.album_artist.to_arg("albumartist:") {
arguments.push(album_artist); arguments.push(album_artist);
}; };
if let Some(album_year) = self.album_year.to_arg("year") { if let Some(album_year) = self.album_year.to_arg("year:") {
arguments.push(album_year); arguments.push(album_year);
}; };
if let Some(album_title) = self.album_title.to_arg("album") { if let Some(album_title) = self.album_title.to_arg("album:") {
arguments.push(album_title); arguments.push(album_title);
}; };
if let Some(track_number) = self.track_number.to_arg("track") { if let Some(track_number) = self.track_number.to_arg("track:") {
arguments.push(track_number); arguments.push(track_number);
}; };
if let Some(track_title) = self.track_title.to_arg("title") { if let Some(track_title) = self.track_title.to_arg("title:") {
arguments.push(track_title); arguments.push(track_title);
}; };
if let Some(track_artist) = self.track_artist.to_arg("artist") { if let Some(track_artist) = self.track_artist.to_arg("artist:") {
arguments.push(track_artist); arguments.push(track_artist);
}; };
if let Some(all) = self.all.to_arg("") {
arguments.push(all);
}
arguments arguments
} }
} }
/// Trait for invoking beets commands.
pub trait BeetsExecutor { pub trait BeetsExecutor {
/// Invoke beets with the provided arguments.
fn exec(&mut self, arguments: Vec<String>) -> Result<Vec<String>, Error>; fn exec(&mut self, arguments: Vec<String>) -> Result<Vec<String>, Error>;
} }
/// Struct for interacting with the music library via beets.
pub struct Beets { pub struct Beets {
executor: Box<dyn BeetsExecutor>, executor: Box<dyn BeetsExecutor>,
} }
@ -179,6 +189,7 @@ impl LibraryPrivate for Beets {
} }
} }
/// Executor for executing beets commands on the local system.
pub struct SystemExecutor { pub struct SystemExecutor {
bin: String, bin: String,
} }
@ -295,19 +306,21 @@ mod tests {
#[test] #[test]
fn test_query() { fn test_query() {
let query = Query::new() let query = Query::new()
.album_title(QueryOption::exclude(String::from("some.album"))) .album_title(QueryOption::Exclude(String::from("some.album")))
.track_number(QueryOption::include(5)) .track_number(QueryOption::Include(5))
.track_artist(QueryOption::include(vec![ .track_artist(QueryOption::Include(vec![
String::from("some.artist.1"), String::from("some.artist.1"),
String::from("some.artist.2"), String::from("some.artist.2"),
])); ]))
.all(QueryOption::Exclude(String::from("some.all")));
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.1; some.artist.2") String::from("artist:some.artist.1; some.artist.2"),
String::from("^some.all"),
] ]
); );
} }
@ -396,9 +409,9 @@ mod tests {
#[test] #[test]
fn test_list_query() { fn test_list_query() {
let query = Query::new() let query = Query::new()
.album_title(QueryOption::exclude(String::from("some.album"))) .album_title(QueryOption::Exclude(String::from("some.album")))
.track_number(QueryOption::include(5)) .track_number(QueryOption::Include(5))
.track_artist(QueryOption::include(vec![String::from("some.artist")])); .track_artist(QueryOption::Include(vec![String::from("some.artist")]));
let executor = TestExecutor { let executor = TestExecutor {
arguments: Some(vec![ arguments: Some(vec![

View File

@ -1,3 +1,5 @@
//! Module for interacting with the music library.
use std::{num::ParseIntError, str::Utf8Error}; use std::{num::ParseIntError, str::Utf8Error};
use crate::Album; use crate::Album;
@ -15,23 +17,12 @@ pub enum QueryOption<T> {
} }
impl<T> QueryOption<T> { impl<T> QueryOption<T> {
/// Create an inclusive query option. /// Return `true` if [QueryOption] is not [QueryOption::None].
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 { pub fn is_some(&self) -> bool {
!matches!(self, QueryOption::None) !matches!(self, QueryOption::None)
} }
/// Return `true` if [QueryOption] is [QueryOption::None].
pub fn is_none(&self) -> bool { pub fn is_none(&self) -> bool {
matches!(self, QueryOption::None) matches!(self, QueryOption::None)
} }
@ -39,7 +30,7 @@ impl<T> QueryOption<T> {
impl<T> Default for QueryOption<T> { impl<T> Default for QueryOption<T> {
fn default() -> Self { fn default() -> Self {
Self::none() Self::None
} }
} }
@ -52,42 +43,56 @@ pub struct Query {
track_number: QueryOption<u32>, track_number: QueryOption<u32>,
track_title: QueryOption<String>, track_title: QueryOption<String>,
track_artist: QueryOption<Vec<String>>, track_artist: QueryOption<Vec<String>>,
all: QueryOption<String>,
} }
impl Query { impl Query {
/// Create an empty query.
pub fn new() -> Self { pub fn new() -> Self {
Query::default() Query::default()
} }
/// Refine the query to a specific album artist.
pub fn album_artist(mut self, album_artist: QueryOption<String>) -> Self { pub fn album_artist(mut self, album_artist: QueryOption<String>) -> Self {
self.album_artist = album_artist; self.album_artist = album_artist;
self self
} }
/// Refine the query to a specific album year.
pub fn album_year(mut self, album_year: QueryOption<u32>) -> Self { pub fn album_year(mut self, album_year: QueryOption<u32>) -> Self {
self.album_year = album_year; self.album_year = album_year;
self self
} }
/// Refine the query to a specific album title.
pub fn album_title(mut self, album_title: QueryOption<String>) -> Self { pub fn album_title(mut self, album_title: QueryOption<String>) -> Self {
self.album_title = album_title; self.album_title = album_title;
self self
} }
/// Refine the query to a specific track number.
pub fn track_number(mut self, track_number: QueryOption<u32>) -> Self { pub fn track_number(mut self, track_number: QueryOption<u32>) -> Self {
self.track_number = track_number; self.track_number = track_number;
self self
} }
/// Refine the query to a specific track title.
pub fn track_title(mut self, track_title: QueryOption<String>) -> Self { pub fn track_title(mut self, track_title: QueryOption<String>) -> Self {
self.track_title = track_title; self.track_title = track_title;
self self
} }
/// Refine the query to a specific set of track artists.
pub fn track_artist(mut self, track_artist: QueryOption<Vec<String>>) -> Self { pub fn track_artist(mut self, track_artist: QueryOption<Vec<String>>) -> Self {
self.track_artist = track_artist; self.track_artist = track_artist;
self self
} }
/// Refine the query for all fields.
pub fn all(mut self, all: QueryOption<String>) -> Self {
self.all = all;
self
}
} }
/// Error type for library calls. /// Error type for library calls.
@ -123,5 +128,6 @@ impl From<Utf8Error> for Error {
/// Trait for interacting with the music library. /// Trait for interacting with the music library.
pub trait Library { pub trait Library {
/// List lirbary items that match the a specific query.
fn list(&mut self, query: &Query) -> Result<Vec<Album>, Error>; fn list(&mut self, query: &Query) -> Result<Vec<Album>, Error>;
} }