Make Library and Database type naming consistent #21

Merged
wojtek merged 2 commits from 20---make-library-and-database-type-naming-consistent into main 2023-04-10 21:36:43 +02:00
6 changed files with 95 additions and 76 deletions
Showing only changes of commit 68d5fa69d4 - Show all commits

View File

@ -6,10 +6,10 @@ use std::path::{Path, PathBuf};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use super::{DatabaseRead, DatabaseWrite}; use super::{Database, DatabaseRead, DatabaseWrite};
/// Trait for the JSON database backend. /// Trait for the JSON database backend.
pub trait DatabaseJsonBackend { pub trait JsonDatabaseBackend {
/// Read the JSON string from the backend. /// Read the JSON string from the backend.
fn read(&self) -> Result<String, std::io::Error>; fn read(&self) -> Result<String, std::io::Error>;
@ -18,18 +18,18 @@ pub trait DatabaseJsonBackend {
} }
/// JSON database. /// JSON database.
pub struct DatabaseJson { pub struct JsonDatabase {
backend: Box<dyn DatabaseJsonBackend + Send>, backend: Box<dyn JsonDatabaseBackend + Send>,
} }
impl DatabaseJson { impl JsonDatabase {
/// Create a new JSON database with the provided executor, e.g. [DatabaseJsonFile]. /// Create a new JSON database with the provided backend, e.g. [JsonDatabaseFileBackend].
pub fn new(backend: Box<dyn DatabaseJsonBackend + Send>) -> Self { pub fn new(backend: Box<dyn JsonDatabaseBackend + Send>) -> Self {
DatabaseJson { backend } JsonDatabase { backend }
} }
} }
impl DatabaseRead for DatabaseJson { impl DatabaseRead for JsonDatabase {
fn read<D>(&self, collection: &mut D) -> Result<(), std::io::Error> fn read<D>(&self, collection: &mut D) -> Result<(), std::io::Error>
where where
D: DeserializeOwned, D: DeserializeOwned,
@ -40,7 +40,7 @@ impl DatabaseRead for DatabaseJson {
} }
} }
impl DatabaseWrite for DatabaseJson { impl DatabaseWrite for JsonDatabase {
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,
@ -51,21 +51,23 @@ impl DatabaseWrite for DatabaseJson {
} }
} }
impl Database for JsonDatabase {}
/// JSON database that uses a local file for persistent storage. /// JSON database that uses a local file for persistent storage.
pub struct DatabaseJsonFile { pub struct JsonDatabaseFileBackend {
path: PathBuf, path: PathBuf,
} }
impl DatabaseJsonFile { impl JsonDatabaseFileBackend {
/// Create a database instance that will read/write to the provided path. /// Create a database instance that will read/write to the provided path.
pub fn new(path: &Path) -> Self { pub fn new(path: &Path) -> Self {
DatabaseJsonFile { JsonDatabaseFileBackend {
path: path.to_path_buf(), path: path.to_path_buf(),
} }
} }
} }
impl DatabaseJsonBackend for DatabaseJsonFile { impl JsonDatabaseBackend for JsonDatabaseFileBackend {
fn read(&self) -> Result<String, std::io::Error> { fn read(&self) -> Result<String, std::io::Error> {
// Read entire file to memory as for now this is faster than a buffered read from disk: // Read entire file to memory as for now this is faster than a buffered read from disk:
// https://github.com/serde-rs/json/issues/160 // https://github.com/serde-rs/json/issues/160
@ -83,11 +85,11 @@ mod tests {
use crate::{tests::COLLECTION, Artist, TrackFormat}; use crate::{tests::COLLECTION, Artist, TrackFormat};
struct DatabaseJsonTest { struct JsonDatabaseTestBackend {
json: String, json: String,
} }
impl DatabaseJsonBackend for DatabaseJsonTest { impl JsonDatabaseBackend for JsonDatabaseTestBackend {
fn read(&self) -> Result<String, std::io::Error> { fn read(&self) -> Result<String, std::io::Error> {
Ok(self.json.clone()) Ok(self.json.clone())
} }
@ -165,11 +167,11 @@ mod tests {
#[test] #[test]
fn write() { fn write() {
let write_data = COLLECTION.to_owned(); let write_data = COLLECTION.to_owned();
let backend = DatabaseJsonTest { let backend = JsonDatabaseTestBackend {
json: artists_to_json(&write_data), json: artists_to_json(&write_data),
}; };
DatabaseJson::new(Box::new(backend)) JsonDatabase::new(Box::new(backend))
.write(&write_data) .write(&write_data)
.unwrap(); .unwrap();
} }
@ -177,12 +179,12 @@ mod tests {
#[test] #[test]
fn read() { fn read() {
let expected = COLLECTION.to_owned(); let expected = COLLECTION.to_owned();
let backend = DatabaseJsonTest { let backend = JsonDatabaseTestBackend {
json: artists_to_json(&expected), json: artists_to_json(&expected),
}; };
let mut read_data: Vec<Artist> = vec![]; let mut read_data: Vec<Artist> = vec![];
DatabaseJson::new(Box::new(backend)) JsonDatabase::new(Box::new(backend))
.read(&mut read_data) .read(&mut read_data)
.unwrap(); .unwrap();
@ -192,10 +194,10 @@ mod tests {
#[test] #[test]
fn reverse() { fn reverse() {
let expected = COLLECTION.to_owned(); let expected = COLLECTION.to_owned();
let backend = DatabaseJsonTest { let backend = JsonDatabaseTestBackend {
json: artists_to_json(&expected), json: artists_to_json(&expected),
}; };
let mut database = DatabaseJson::new(Box::new(backend)); let mut database = JsonDatabase::new(Box::new(backend));
let write_data = COLLECTION.to_owned(); let write_data = COLLECTION.to_owned();
let mut read_data: Vec<Artist> = vec![]; let mut read_data: Vec<Artist> = vec![];

View File

@ -20,3 +20,6 @@ pub trait DatabaseWrite {
where where
S: Serialize; S: Serialize;
} }
/// Trait for database reads and writes.
pub trait Database: DatabaseRead + DatabaseWrite {}

View File

@ -84,14 +84,14 @@ impl QueryArgsBeets for Query {
} }
/// Trait for invoking beets commands. /// Trait for invoking beets commands.
pub trait BeetsExecutor { pub trait BeetsLibraryExecutor {
/// Invoke beets with the provided arguments. /// Invoke beets with the provided arguments.
fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error>; fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error>;
} }
/// Struct for interacting with the music library via beets. /// Struct for interacting with the music library via beets.
pub struct Beets { pub struct BeetsLibrary {
executor: Box<dyn BeetsExecutor + Send>, executor: Box<dyn BeetsLibraryExecutor + Send>,
} }
trait LibraryPrivate { trait LibraryPrivate {
@ -105,14 +105,14 @@ trait LibraryPrivate {
fn list_to_artists(list_output: Vec<String>) -> Result<Vec<Artist>, Error>; fn list_to_artists(list_output: Vec<String>) -> Result<Vec<Artist>, Error>;
} }
impl Beets { impl BeetsLibrary {
/// Create a new beets library instance with the provided executor, e.g. [SystemExecutor]. /// Create a new beets library with the provided executor, e.g. [BeetsLibrarySystemExecutor].
pub fn new(executor: Box<dyn BeetsExecutor + Send>) -> Beets { pub fn new(executor: Box<dyn BeetsLibraryExecutor + Send>) -> BeetsLibrary {
Beets { executor } BeetsLibrary { executor }
} }
} }
impl Library for Beets { impl Library for BeetsLibrary {
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error> { fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error> {
let cmd = Self::list_cmd_and_args(query); let cmd = Self::list_cmd_and_args(query);
let output = self.executor.exec(&cmd)?; let output = self.executor.exec(&cmd)?;
@ -126,7 +126,7 @@ macro_rules! list_format_separator {
}; };
} }
impl LibraryPrivate for Beets { impl LibraryPrivate for BeetsLibrary {
const CMD_LIST: &'static str = "ls"; const CMD_LIST: &'static str = "ls";
const LIST_FORMAT_SEPARATOR: &'static str = list_format_separator!(); const LIST_FORMAT_SEPARATOR: &'static str = list_format_separator!();
const LIST_FORMAT_ARG: &'static str = concat!( const LIST_FORMAT_ARG: &'static str = concat!(
@ -244,19 +244,19 @@ impl LibraryPrivate for Beets {
/// database/library. Therefore, all functions that create a [SystemExecutor] or modify which /// database/library. Therefore, all functions that create a [SystemExecutor] or modify which
/// library it works with are marked unsafe. It is the caller's responsibility to make sure the /// library it works with are marked unsafe. It is the caller's responsibility to make sure the
/// library is not being concurrently accessed from anywhere else. /// library is not being concurrently accessed from anywhere else.
pub struct SystemExecutor { pub struct BeetsLibrarySystemExecutor {
bin: String, bin: String,
config: Option<PathBuf>, config: Option<PathBuf>,
} }
impl SystemExecutor { impl BeetsLibrarySystemExecutor {
/// Create a new [SystemExecutor] that uses the provided beets executable. /// Create a new [SystemExecutor] that uses the provided beets executable.
/// ///
/// # Safety /// # Safety
/// ///
/// The caller must ensure the library is not being concurrently accessed from anywhere else. /// The caller must ensure the library is not being concurrently accessed from anywhere else.
pub unsafe fn new(bin: &str) -> Self { pub unsafe fn new(bin: &str) -> Self {
SystemExecutor { BeetsLibrarySystemExecutor {
bin: bin.to_string(), bin: bin.to_string(),
config: None, config: None,
} }
@ -268,7 +268,7 @@ impl SystemExecutor {
/// ///
/// The caller must ensure the library is not being concurrently accessed from anywhere else. /// The caller must ensure the library is not being concurrently accessed from anywhere else.
pub unsafe fn default() -> Self { pub unsafe fn default() -> Self {
SystemExecutor::new("beet") BeetsLibrarySystemExecutor::new("beet")
} }
/// Update the configuration file for the beets executable. /// Update the configuration file for the beets executable.
@ -282,7 +282,7 @@ impl SystemExecutor {
} }
} }
impl BeetsExecutor for SystemExecutor { impl BeetsLibraryExecutor for BeetsLibrarySystemExecutor {
fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error> { fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error> {
let mut cmd = Command::new(&self.bin); let mut cmd = Command::new(&self.bin);
if let Some(ref path) = self.config { if let Some(ref path) = self.config {
@ -306,12 +306,12 @@ mod tests {
use crate::tests::COLLECTION; use crate::tests::COLLECTION;
struct TestExecutor { struct BeetsLibraryTestExecutor {
arguments: Option<Vec<String>>, arguments: Option<Vec<String>>,
output: Option<Result<Vec<String>, Error>>, output: Option<Result<Vec<String>, Error>>,
} }
impl BeetsExecutor for TestExecutor { impl BeetsLibraryExecutor for BeetsLibraryTestExecutor {
fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error> { fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error> {
assert_eq!(&self.arguments.take().unwrap(), arguments); assert_eq!(&self.arguments.take().unwrap(), arguments);
self.output.take().unwrap() self.output.take().unwrap()
@ -332,14 +332,14 @@ mod tests {
let track_title = &track.title; let track_title = &track.title;
let track_artist = &track.artist.join("; "); let track_artist = &track.artist.join("; ");
let track_format = match track.format { let track_format = match track.format {
TrackFormat::Flac => Beets::TRACK_FORMAT_FLAC, TrackFormat::Flac => BeetsLibrary::TRACK_FORMAT_FLAC,
TrackFormat::Mp3 => Beets::TRACK_FORMAT_MP3, TrackFormat::Mp3 => BeetsLibrary::TRACK_FORMAT_MP3,
}; };
strings.push(format!( strings.push(format!(
"{album_artist}{0}{album_year}{0}{album_title}{0}\ "{album_artist}{0}{album_year}{0}{album_title}{0}\
{track_number}{0}{track_title}{0}{track_artist}{0}{track_format}", {track_number}{0}{track_title}{0}{track_artist}{0}{track_format}",
Beets::LIST_FORMAT_SEPARATOR, BeetsLibrary::LIST_FORMAT_SEPARATOR,
)); ));
} }
} }
@ -379,11 +379,14 @@ mod tests {
#[test] #[test]
fn test_list_empty() { fn test_list_empty() {
let executor = TestExecutor { let executor = BeetsLibraryTestExecutor {
arguments: Some(vec!["ls".to_string(), Beets::LIST_FORMAT_ARG.to_string()]), arguments: Some(vec![
"ls".to_string(),
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
]),
output: Some(Ok(vec![])), output: Some(Ok(vec![])),
}; };
let mut beets = Beets::new(Box::new(executor)); let mut beets = BeetsLibrary::new(Box::new(executor));
let output = beets.list(&Query::default()).unwrap(); let output = beets.list(&Query::default()).unwrap();
let expected: Vec<Artist> = vec![]; let expected: Vec<Artist> = vec![];
@ -395,11 +398,14 @@ mod tests {
let expected = COLLECTION.to_owned(); let expected = COLLECTION.to_owned();
let output = artists_to_beets_string(&expected); let output = artists_to_beets_string(&expected);
let executor = TestExecutor { let executor = BeetsLibraryTestExecutor {
arguments: Some(vec!["ls".to_string(), Beets::LIST_FORMAT_ARG.to_string()]), arguments: Some(vec![
"ls".to_string(),
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
]),
output: Some(Ok(output)), output: Some(Ok(output)),
}; };
let mut beets = Beets::new(Box::new(executor)); let mut beets = BeetsLibrary::new(Box::new(executor));
let output = beets.list(&Query::default()).unwrap(); let output = beets.list(&Query::default()).unwrap();
assert_eq!(output, expected); assert_eq!(output, expected);
@ -424,11 +430,14 @@ mod tests {
// And the (now) second album's tracks first track comes last. // And the (now) second album's tracks first track comes last.
expected[1].albums[0].tracks.rotate_left(1); expected[1].albums[0].tracks.rotate_left(1);
let executor = TestExecutor { let executor = BeetsLibraryTestExecutor {
arguments: Some(vec!["ls".to_string(), Beets::LIST_FORMAT_ARG.to_string()]), arguments: Some(vec![
"ls".to_string(),
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
]),
output: Some(Ok(output)), output: Some(Ok(output)),
}; };
let mut beets = Beets::new(Box::new(executor)); let mut beets = BeetsLibrary::new(Box::new(executor));
let output = beets.list(&Query::default()).unwrap(); let output = beets.list(&Query::default()).unwrap();
assert_eq!(output, expected); assert_eq!(output, expected);
@ -442,11 +451,14 @@ mod tests {
let output = artists_to_beets_string(&expected); let output = artists_to_beets_string(&expected);
let executor = TestExecutor { let executor = BeetsLibraryTestExecutor {
arguments: Some(vec!["ls".to_string(), Beets::LIST_FORMAT_ARG.to_string()]), arguments: Some(vec![
"ls".to_string(),
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
]),
output: Some(Ok(output)), output: Some(Ok(output)),
}; };
let mut beets = Beets::new(Box::new(executor)); let mut beets = BeetsLibrary::new(Box::new(executor));
let output = beets.list(&Query::default()).unwrap(); let output = beets.list(&Query::default()).unwrap();
assert_eq!(output, expected); assert_eq!(output, expected);
@ -459,17 +471,17 @@ mod tests {
.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 = BeetsLibraryTestExecutor {
arguments: Some(vec![ arguments: Some(vec![
"ls".to_string(), "ls".to_string(),
Beets::LIST_FORMAT_ARG.to_string(), BeetsLibrary::LIST_FORMAT_ARG.to_string(),
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"),
]), ]),
output: Some(Ok(vec![])), output: Some(Ok(vec![])),
}; };
let mut beets = Beets::new(Box::new(executor)); let mut beets = BeetsLibrary::new(Box::new(executor));
let output = beets.list(&query).unwrap(); let output = beets.list(&query).unwrap();
let expected: Vec<Artist> = vec![]; let expected: Vec<Artist> = vec![];

View File

@ -4,11 +4,11 @@ use structopt::StructOpt;
use musichoard::{ use musichoard::{
database::{ database::{
json::{DatabaseJson, DatabaseJsonFile}, json::{JsonDatabase, JsonDatabaseFileBackend},
DatabaseWrite, DatabaseWrite,
}, },
library::{ library::{
beets::{Beets, SystemExecutor}, beets::{BeetsLibrary, BeetsLibrarySystemExecutor},
Library, Query, Library, Query,
}, },
}; };
@ -36,15 +36,17 @@ struct Opt {
fn main() { fn main() {
let opt = Opt::from_args(); let opt = Opt::from_args();
let mut beets = Beets::new(Box::new( let mut beets = BeetsLibrary::new(Box::new(unsafe {
unsafe { SystemExecutor::default().config(opt.beets_config_file_path.as_deref()) }, BeetsLibrarySystemExecutor::default().config(opt.beets_config_file_path.as_deref())
)); }));
let collection = beets let collection = beets
.list(&Query::new()) .list(&Query::new())
.expect("failed to query the library"); .expect("failed to query the library");
let mut database = DatabaseJson::new(Box::new(DatabaseJsonFile::new(&opt.database_file_path))); let mut database = JsonDatabase::new(Box::new(JsonDatabaseFileBackend::new(
&opt.database_file_path,
)));
database database
.write(&collection) .write(&collection)

View File

@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
use musichoard::{ use musichoard::{
database::{ database::{
json::{DatabaseJson, DatabaseJsonFile}, json::{JsonDatabase, JsonDatabaseFileBackend},
DatabaseRead, DatabaseWrite, DatabaseRead, DatabaseWrite,
}, },
Artist, Artist,
@ -19,8 +19,8 @@ static DATABASE_TEST_FILE: Lazy<PathBuf> =
fn write() { fn write() {
let file = NamedTempFile::new().unwrap(); let file = NamedTempFile::new().unwrap();
let backend = DatabaseJsonFile::new(file.path()); let backend = JsonDatabaseFileBackend::new(file.path());
let mut database = DatabaseJson::new(Box::new(backend)); let mut database = JsonDatabase::new(Box::new(backend));
let write_data = COLLECTION.to_owned(); let write_data = COLLECTION.to_owned();
database.write(&write_data).unwrap(); database.write(&write_data).unwrap();
@ -33,8 +33,8 @@ fn write() {
#[test] #[test]
fn read() { fn read() {
let backend = DatabaseJsonFile::new(&*DATABASE_TEST_FILE); let backend = JsonDatabaseFileBackend::new(&*DATABASE_TEST_FILE);
let database = DatabaseJson::new(Box::new(backend)); let database = JsonDatabase::new(Box::new(backend));
let mut read_data: Vec<Artist> = vec![]; let mut read_data: Vec<Artist> = vec![];
database.read(&mut read_data).unwrap(); database.read(&mut read_data).unwrap();
@ -47,8 +47,8 @@ fn read() {
fn reverse() { fn reverse() {
let file = NamedTempFile::new().unwrap(); let file = NamedTempFile::new().unwrap();
let backend = DatabaseJsonFile::new(file.path()); let backend = JsonDatabaseFileBackend::new(file.path());
let mut database = DatabaseJson::new(Box::new(backend)); let mut database = JsonDatabase::new(Box::new(backend));
let write_data = COLLECTION.to_owned(); let write_data = COLLECTION.to_owned();
database.write(&write_data).unwrap(); database.write(&write_data).unwrap();

View File

@ -7,7 +7,7 @@ use once_cell::sync::Lazy;
use musichoard::{ use musichoard::{
library::{ library::{
beets::{Beets, SystemExecutor}, beets::{BeetsLibrary, BeetsLibrarySystemExecutor},
Library, Query, QueryOption, Library, Query, QueryOption,
}, },
Artist, Artist,
@ -15,15 +15,15 @@ use musichoard::{
use crate::COLLECTION; use crate::COLLECTION;
static BEETS_EMPTY_CONFIG: Lazy<Arc<Mutex<Beets>>> = Lazy::new(|| { static BEETS_EMPTY_CONFIG: Lazy<Arc<Mutex<BeetsLibrary>>> = Lazy::new(|| {
Arc::new(Mutex::new(Beets::new(Box::new(unsafe { Arc::new(Mutex::new(BeetsLibrary::new(Box::new(unsafe {
SystemExecutor::default() BeetsLibrarySystemExecutor::default()
})))) }))))
}); });
static BEETS_TEST_CONFIG: Lazy<Arc<Mutex<Beets>>> = Lazy::new(|| { static BEETS_TEST_CONFIG: Lazy<Arc<Mutex<BeetsLibrary>>> = Lazy::new(|| {
Arc::new(Mutex::new(Beets::new(Box::new(unsafe { Arc::new(Mutex::new(BeetsLibrary::new(Box::new(unsafe {
SystemExecutor::default().config(Some( BeetsLibrarySystemExecutor::default().config(Some(
&fs::canonicalize("./tests/files/library/config.yml").unwrap(), &fs::canonicalize("./tests/files/library/config.yml").unwrap(),
)) ))
})))) }))))