Resolve "Local collection trait and beets implementation" #9
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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![
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user