Make Library and Database type naming consistent (#21)
Closes #20 Reviewed-on: https://git.wojciechkozlowski.eu/wojtek/musichoard/pulls/21
This commit is contained in:
parent
8b89a13b90
commit
67ba032247
@ -6,10 +6,10 @@ use std::path::{Path, PathBuf};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{DatabaseRead, DatabaseWrite};
|
||||
use super::{Database, DatabaseRead, DatabaseWrite};
|
||||
|
||||
/// Trait for the JSON database backend.
|
||||
pub trait DatabaseJsonBackend {
|
||||
pub trait JsonDatabaseBackend {
|
||||
/// Read the JSON string from the backend.
|
||||
fn read(&self) -> Result<String, std::io::Error>;
|
||||
|
||||
@ -18,18 +18,18 @@ pub trait DatabaseJsonBackend {
|
||||
}
|
||||
|
||||
/// JSON database.
|
||||
pub struct DatabaseJson {
|
||||
backend: Box<dyn DatabaseJsonBackend + Send>,
|
||||
pub struct JsonDatabase {
|
||||
backend: Box<dyn JsonDatabaseBackend + Send>,
|
||||
}
|
||||
|
||||
impl DatabaseJson {
|
||||
/// Create a new JSON database with the provided executor, e.g. [DatabaseJsonFile].
|
||||
pub fn new(backend: Box<dyn DatabaseJsonBackend + Send>) -> Self {
|
||||
DatabaseJson { backend }
|
||||
impl JsonDatabase {
|
||||
/// Create a new JSON database with the provided backend, e.g. [JsonDatabaseFileBackend].
|
||||
pub fn new(backend: Box<dyn JsonDatabaseBackend + Send>) -> Self {
|
||||
JsonDatabase { backend }
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseRead for DatabaseJson {
|
||||
impl DatabaseRead for JsonDatabase {
|
||||
fn read<D>(&self, collection: &mut D) -> Result<(), std::io::Error>
|
||||
where
|
||||
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>
|
||||
where
|
||||
S: Serialize,
|
||||
@ -51,21 +51,23 @@ impl DatabaseWrite for DatabaseJson {
|
||||
}
|
||||
}
|
||||
|
||||
/// JSON database that uses a local file for persistent storage.
|
||||
pub struct DatabaseJsonFile {
|
||||
impl Database for JsonDatabase {}
|
||||
|
||||
/// JSON database backend that uses a local file for persistent storage.
|
||||
pub struct JsonDatabaseFileBackend {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DatabaseJsonFile {
|
||||
/// Create a database instance that will read/write to the provided path.
|
||||
impl JsonDatabaseFileBackend {
|
||||
/// Create a [JsonDatabaseFileBackend] that will read/write to the provided path.
|
||||
pub fn new(path: &Path) -> Self {
|
||||
DatabaseJsonFile {
|
||||
JsonDatabaseFileBackend {
|
||||
path: path.to_path_buf(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseJsonBackend for DatabaseJsonFile {
|
||||
impl JsonDatabaseBackend for JsonDatabaseFileBackend {
|
||||
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:
|
||||
// https://github.com/serde-rs/json/issues/160
|
||||
@ -83,11 +85,11 @@ mod tests {
|
||||
|
||||
use crate::{tests::COLLECTION, Artist, TrackFormat};
|
||||
|
||||
struct DatabaseJsonTest {
|
||||
struct JsonDatabaseTestBackend {
|
||||
json: String,
|
||||
}
|
||||
|
||||
impl DatabaseJsonBackend for DatabaseJsonTest {
|
||||
impl JsonDatabaseBackend for JsonDatabaseTestBackend {
|
||||
fn read(&self) -> Result<String, std::io::Error> {
|
||||
Ok(self.json.clone())
|
||||
}
|
||||
@ -165,11 +167,11 @@ mod tests {
|
||||
#[test]
|
||||
fn write() {
|
||||
let write_data = COLLECTION.to_owned();
|
||||
let backend = DatabaseJsonTest {
|
||||
let backend = JsonDatabaseTestBackend {
|
||||
json: artists_to_json(&write_data),
|
||||
};
|
||||
|
||||
DatabaseJson::new(Box::new(backend))
|
||||
JsonDatabase::new(Box::new(backend))
|
||||
.write(&write_data)
|
||||
.unwrap();
|
||||
}
|
||||
@ -177,12 +179,12 @@ mod tests {
|
||||
#[test]
|
||||
fn read() {
|
||||
let expected = COLLECTION.to_owned();
|
||||
let backend = DatabaseJsonTest {
|
||||
let backend = JsonDatabaseTestBackend {
|
||||
json: artists_to_json(&expected),
|
||||
};
|
||||
|
||||
let mut read_data: Vec<Artist> = vec![];
|
||||
DatabaseJson::new(Box::new(backend))
|
||||
JsonDatabase::new(Box::new(backend))
|
||||
.read(&mut read_data)
|
||||
.unwrap();
|
||||
|
||||
@ -192,10 +194,10 @@ mod tests {
|
||||
#[test]
|
||||
fn reverse() {
|
||||
let expected = COLLECTION.to_owned();
|
||||
let backend = DatabaseJsonTest {
|
||||
let backend = JsonDatabaseTestBackend {
|
||||
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 mut read_data: Vec<Artist> = vec![];
|
||||
|
@ -20,3 +20,6 @@ pub trait DatabaseWrite {
|
||||
where
|
||||
S: Serialize;
|
||||
}
|
||||
|
||||
/// Trait for database reads and writes.
|
||||
pub trait Database: DatabaseRead + DatabaseWrite {}
|
||||
|
@ -84,14 +84,14 @@ impl QueryArgsBeets for Query {
|
||||
}
|
||||
|
||||
/// Trait for invoking beets commands.
|
||||
pub trait BeetsExecutor {
|
||||
pub trait BeetsLibraryExecutor {
|
||||
/// Invoke beets with the provided arguments.
|
||||
fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error>;
|
||||
}
|
||||
|
||||
/// Struct for interacting with the music library via beets.
|
||||
pub struct Beets {
|
||||
executor: Box<dyn BeetsExecutor + Send>,
|
||||
/// Beets library.
|
||||
pub struct BeetsLibrary {
|
||||
executor: Box<dyn BeetsLibraryExecutor + Send>,
|
||||
}
|
||||
|
||||
trait LibraryPrivate {
|
||||
@ -105,14 +105,14 @@ trait LibraryPrivate {
|
||||
fn list_to_artists(list_output: Vec<String>) -> Result<Vec<Artist>, Error>;
|
||||
}
|
||||
|
||||
impl Beets {
|
||||
/// Create a new beets library instance with the provided executor, e.g. [SystemExecutor].
|
||||
pub fn new(executor: Box<dyn BeetsExecutor + Send>) -> Beets {
|
||||
Beets { executor }
|
||||
impl BeetsLibrary {
|
||||
/// Create a new beets library with the provided executor, e.g. [BeetsLibraryCommandExecutor].
|
||||
pub fn new(executor: Box<dyn BeetsLibraryExecutor + Send>) -> BeetsLibrary {
|
||||
BeetsLibrary { executor }
|
||||
}
|
||||
}
|
||||
|
||||
impl Library for Beets {
|
||||
impl Library for BeetsLibrary {
|
||||
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error> {
|
||||
let cmd = Self::list_cmd_and_args(query);
|
||||
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 LIST_FORMAT_SEPARATOR: &'static str = list_format_separator!();
|
||||
const LIST_FORMAT_ARG: &'static str = concat!(
|
||||
@ -236,42 +236,42 @@ impl LibraryPrivate for Beets {
|
||||
}
|
||||
}
|
||||
|
||||
/// Executor for executing beets commands on the local system.
|
||||
/// Beets library executor that executes beets commands in their own process.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The beets executable is not safe to call concurrently for operations on the same
|
||||
/// 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 is not being concurrently accessed from anywhere else.
|
||||
pub struct SystemExecutor {
|
||||
/// database/library. Therefore, all functions that create a [BeetsLibraryCommandExecutor] or modify
|
||||
/// which 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.
|
||||
pub struct BeetsLibraryCommandExecutor {
|
||||
bin: String,
|
||||
config: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl SystemExecutor {
|
||||
/// Create a new [SystemExecutor] that uses the provided beets executable.
|
||||
impl BeetsLibraryCommandExecutor {
|
||||
/// Create a new [BeetsLibraryCommandExecutor] that uses the provided beets executable.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the library is not being concurrently accessed from anywhere else.
|
||||
pub unsafe fn new(bin: &str) -> Self {
|
||||
SystemExecutor {
|
||||
BeetsLibraryCommandExecutor {
|
||||
bin: bin.to_string(),
|
||||
config: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [SystemExecutor] that uses the system's default beets executable.
|
||||
/// Create a new [BeetsLibraryCommandExecutor] that uses the system's default beets executable.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the library is not being concurrently accessed from anywhere else.
|
||||
pub unsafe fn default() -> Self {
|
||||
SystemExecutor::new("beet")
|
||||
BeetsLibraryCommandExecutor::new("beet")
|
||||
}
|
||||
|
||||
/// Update the configuration file for the beets executable.
|
||||
/// Update the configuration file passed to the beets executable.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
@ -282,7 +282,7 @@ impl SystemExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
impl BeetsExecutor for SystemExecutor {
|
||||
impl BeetsLibraryExecutor for BeetsLibraryCommandExecutor {
|
||||
fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error> {
|
||||
let mut cmd = Command::new(&self.bin);
|
||||
if let Some(ref path) = self.config {
|
||||
@ -306,12 +306,12 @@ mod tests {
|
||||
|
||||
use crate::tests::COLLECTION;
|
||||
|
||||
struct TestExecutor {
|
||||
struct BeetsLibraryTestExecutor {
|
||||
arguments: Option<Vec<String>>,
|
||||
output: Option<Result<Vec<String>, Error>>,
|
||||
}
|
||||
|
||||
impl BeetsExecutor for TestExecutor {
|
||||
impl BeetsLibraryExecutor for BeetsLibraryTestExecutor {
|
||||
fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error> {
|
||||
assert_eq!(&self.arguments.take().unwrap(), arguments);
|
||||
self.output.take().unwrap()
|
||||
@ -332,14 +332,14 @@ mod tests {
|
||||
let track_title = &track.title;
|
||||
let track_artist = &track.artist.join("; ");
|
||||
let track_format = match track.format {
|
||||
TrackFormat::Flac => Beets::TRACK_FORMAT_FLAC,
|
||||
TrackFormat::Mp3 => Beets::TRACK_FORMAT_MP3,
|
||||
TrackFormat::Flac => BeetsLibrary::TRACK_FORMAT_FLAC,
|
||||
TrackFormat::Mp3 => BeetsLibrary::TRACK_FORMAT_MP3,
|
||||
};
|
||||
|
||||
strings.push(format!(
|
||||
"{album_artist}{0}{album_year}{0}{album_title}{0}\
|
||||
{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]
|
||||
fn test_list_empty() {
|
||||
let executor = TestExecutor {
|
||||
arguments: Some(vec!["ls".to_string(), Beets::LIST_FORMAT_ARG.to_string()]),
|
||||
let executor = BeetsLibraryTestExecutor {
|
||||
arguments: Some(vec![
|
||||
"ls".to_string(),
|
||||
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
|
||||
]),
|
||||
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 expected: Vec<Artist> = vec![];
|
||||
@ -395,11 +398,14 @@ mod tests {
|
||||
let expected = COLLECTION.to_owned();
|
||||
let output = artists_to_beets_string(&expected);
|
||||
|
||||
let executor = TestExecutor {
|
||||
arguments: Some(vec!["ls".to_string(), Beets::LIST_FORMAT_ARG.to_string()]),
|
||||
let executor = BeetsLibraryTestExecutor {
|
||||
arguments: Some(vec![
|
||||
"ls".to_string(),
|
||||
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
|
||||
]),
|
||||
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();
|
||||
|
||||
assert_eq!(output, expected);
|
||||
@ -424,11 +430,14 @@ mod tests {
|
||||
// And the (now) second album's tracks first track comes last.
|
||||
expected[1].albums[0].tracks.rotate_left(1);
|
||||
|
||||
let executor = TestExecutor {
|
||||
arguments: Some(vec!["ls".to_string(), Beets::LIST_FORMAT_ARG.to_string()]),
|
||||
let executor = BeetsLibraryTestExecutor {
|
||||
arguments: Some(vec![
|
||||
"ls".to_string(),
|
||||
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
|
||||
]),
|
||||
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();
|
||||
|
||||
assert_eq!(output, expected);
|
||||
@ -442,11 +451,14 @@ mod tests {
|
||||
|
||||
let output = artists_to_beets_string(&expected);
|
||||
|
||||
let executor = TestExecutor {
|
||||
arguments: Some(vec!["ls".to_string(), Beets::LIST_FORMAT_ARG.to_string()]),
|
||||
let executor = BeetsLibraryTestExecutor {
|
||||
arguments: Some(vec![
|
||||
"ls".to_string(),
|
||||
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
|
||||
]),
|
||||
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();
|
||||
|
||||
assert_eq!(output, expected);
|
||||
@ -459,17 +471,17 @@ mod tests {
|
||||
.track_number(QueryOption::Include(5))
|
||||
.track_artist(QueryOption::Include(vec![String::from("some.artist")]));
|
||||
|
||||
let executor = TestExecutor {
|
||||
let executor = BeetsLibraryTestExecutor {
|
||||
arguments: Some(vec![
|
||||
"ls".to_string(),
|
||||
Beets::LIST_FORMAT_ARG.to_string(),
|
||||
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
|
||||
String::from("^album:some.album"),
|
||||
String::from("track:5"),
|
||||
String::from("artist:some.artist"),
|
||||
]),
|
||||
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 expected: Vec<Artist> = vec![];
|
||||
|
14
src/main.rs
14
src/main.rs
@ -4,11 +4,11 @@ use structopt::StructOpt;
|
||||
|
||||
use musichoard::{
|
||||
database::{
|
||||
json::{DatabaseJson, DatabaseJsonFile},
|
||||
json::{JsonDatabase, JsonDatabaseFileBackend},
|
||||
DatabaseWrite,
|
||||
},
|
||||
library::{
|
||||
beets::{Beets, SystemExecutor},
|
||||
beets::{BeetsLibrary, BeetsLibraryCommandExecutor},
|
||||
Library, Query,
|
||||
},
|
||||
};
|
||||
@ -36,15 +36,17 @@ struct Opt {
|
||||
fn main() {
|
||||
let opt = Opt::from_args();
|
||||
|
||||
let mut beets = Beets::new(Box::new(
|
||||
unsafe { SystemExecutor::default().config(opt.beets_config_file_path.as_deref()) },
|
||||
));
|
||||
let mut beets = BeetsLibrary::new(Box::new(unsafe {
|
||||
BeetsLibraryCommandExecutor::default().config(opt.beets_config_file_path.as_deref())
|
||||
}));
|
||||
|
||||
let collection = beets
|
||||
.list(&Query::new())
|
||||
.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
|
||||
.write(&collection)
|
||||
|
@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
|
||||
|
||||
use musichoard::{
|
||||
database::{
|
||||
json::{DatabaseJson, DatabaseJsonFile},
|
||||
json::{JsonDatabase, JsonDatabaseFileBackend},
|
||||
DatabaseRead, DatabaseWrite,
|
||||
},
|
||||
Artist,
|
||||
@ -19,8 +19,8 @@ static DATABASE_TEST_FILE: Lazy<PathBuf> =
|
||||
fn write() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
|
||||
let backend = DatabaseJsonFile::new(file.path());
|
||||
let mut database = DatabaseJson::new(Box::new(backend));
|
||||
let backend = JsonDatabaseFileBackend::new(file.path());
|
||||
let mut database = JsonDatabase::new(Box::new(backend));
|
||||
|
||||
let write_data = COLLECTION.to_owned();
|
||||
database.write(&write_data).unwrap();
|
||||
@ -33,8 +33,8 @@ fn write() {
|
||||
|
||||
#[test]
|
||||
fn read() {
|
||||
let backend = DatabaseJsonFile::new(&*DATABASE_TEST_FILE);
|
||||
let database = DatabaseJson::new(Box::new(backend));
|
||||
let backend = JsonDatabaseFileBackend::new(&*DATABASE_TEST_FILE);
|
||||
let database = JsonDatabase::new(Box::new(backend));
|
||||
|
||||
let mut read_data: Vec<Artist> = vec![];
|
||||
database.read(&mut read_data).unwrap();
|
||||
@ -47,8 +47,8 @@ fn read() {
|
||||
fn reverse() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
|
||||
let backend = DatabaseJsonFile::new(file.path());
|
||||
let mut database = DatabaseJson::new(Box::new(backend));
|
||||
let backend = JsonDatabaseFileBackend::new(file.path());
|
||||
let mut database = JsonDatabase::new(Box::new(backend));
|
||||
|
||||
let write_data = COLLECTION.to_owned();
|
||||
database.write(&write_data).unwrap();
|
||||
|
@ -7,7 +7,7 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use musichoard::{
|
||||
library::{
|
||||
beets::{Beets, SystemExecutor},
|
||||
beets::{BeetsLibrary, BeetsLibraryCommandExecutor},
|
||||
Library, Query, QueryOption,
|
||||
},
|
||||
Artist,
|
||||
@ -15,15 +15,15 @@ use musichoard::{
|
||||
|
||||
use crate::COLLECTION;
|
||||
|
||||
static BEETS_EMPTY_CONFIG: Lazy<Arc<Mutex<Beets>>> = Lazy::new(|| {
|
||||
Arc::new(Mutex::new(Beets::new(Box::new(unsafe {
|
||||
SystemExecutor::default()
|
||||
static BEETS_EMPTY_CONFIG: Lazy<Arc<Mutex<BeetsLibrary>>> = Lazy::new(|| {
|
||||
Arc::new(Mutex::new(BeetsLibrary::new(Box::new(unsafe {
|
||||
BeetsLibraryCommandExecutor::default()
|
||||
}))))
|
||||
});
|
||||
|
||||
static BEETS_TEST_CONFIG: Lazy<Arc<Mutex<Beets>>> = Lazy::new(|| {
|
||||
Arc::new(Mutex::new(Beets::new(Box::new(unsafe {
|
||||
SystemExecutor::default().config(Some(
|
||||
static BEETS_TEST_CONFIG: Lazy<Arc<Mutex<BeetsLibrary>>> = Lazy::new(|| {
|
||||
Arc::new(Mutex::new(BeetsLibrary::new(Box::new(unsafe {
|
||||
BeetsLibraryCommandExecutor::default().config(Some(
|
||||
&fs::canonicalize("./tests/files/library/config.yml").unwrap(),
|
||||
))
|
||||
}))))
|
||||
|
Loading…
Reference in New Issue
Block a user