Generic beets executor

This commit is contained in:
Wojciech Kozlowski 2023-04-13 14:44:30 +02:00
parent 5edcceeae5
commit 73b4cc8b28
3 changed files with 67 additions and 71 deletions

View File

@ -16,6 +16,33 @@ use crate::{Album, AlbumId, Artist, ArtistId, Track, TrackFormat};
use super::{Error, Library, Query, QueryOption};
macro_rules! list_format_separator {
() => {
" -*^- "
};
}
const CMD_LIST: &str = "ls";
const LIST_FORMAT_SEPARATOR: &str = list_format_separator!();
const LIST_FORMAT_ARG: &str = concat!(
"--format=",
"$albumartist",
list_format_separator!(),
"$year",
list_format_separator!(),
"$album",
list_format_separator!(),
"$track",
list_format_separator!(),
"$title",
list_format_separator!(),
"$artist",
list_format_separator!(),
"$format"
);
const TRACK_FORMAT_FLAC: &str = "FLAC";
const TRACK_FORMAT_MP3: &str = "MP3";
trait QueryOptionArgBeets {
fn to_arg(&self, option_name: &str) -> Option<String>;
}
@ -94,29 +121,23 @@ pub trait BeetsLibraryExecutor {
}
/// Beets library.
pub struct BeetsLibrary {
executor: Box<dyn BeetsLibraryExecutor + Send + Sync>,
pub struct BeetsLibrary<BLE> {
executor: BLE,
}
trait LibraryPrivate {
const CMD_LIST: &'static str;
const LIST_FORMAT_SEPARATOR: &'static str;
const LIST_FORMAT_ARG: &'static str;
const TRACK_FORMAT_FLAC: &'static str;
const TRACK_FORMAT_MP3: &'static str;
fn list_cmd_and_args(query: &Query) -> Vec<String>;
fn list_to_artists(list_output: Vec<String>) -> Result<Vec<Artist>, Error>;
}
impl BeetsLibrary {
impl<BLE: BeetsLibraryExecutor> BeetsLibrary<BLE> {
/// Create a new beets library with the provided executor, e.g. [`BeetsLibraryCommandExecutor`].
pub fn new(executor: Box<dyn BeetsLibraryExecutor + Send + Sync>) -> BeetsLibrary {
pub fn new(executor: BLE) -> Self {
BeetsLibrary { executor }
}
}
impl Library for BeetsLibrary {
impl<BLE: BeetsLibraryExecutor> Library for BeetsLibrary<BLE> {
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error> {
let cmd = Self::list_cmd_and_args(query);
let output = self.executor.exec(&cmd)?;
@ -124,37 +145,10 @@ impl Library for BeetsLibrary {
}
}
macro_rules! list_format_separator {
() => {
" -*^- "
};
}
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!(
"--format=",
"$albumartist",
list_format_separator!(),
"$year",
list_format_separator!(),
"$album",
list_format_separator!(),
"$track",
list_format_separator!(),
"$title",
list_format_separator!(),
"$artist",
list_format_separator!(),
"$format"
);
const TRACK_FORMAT_FLAC: &'static str = "FLAC";
const TRACK_FORMAT_MP3: &'static str = "MP3";
impl<BLE: BeetsLibraryExecutor> LibraryPrivate for BeetsLibrary<BLE> {
fn list_cmd_and_args(query: &Query) -> Vec<String> {
let mut cmd: Vec<String> = vec![String::from(Self::CMD_LIST)];
cmd.push(Self::LIST_FORMAT_ARG.to_string());
let mut cmd: Vec<String> = vec![String::from(CMD_LIST)];
cmd.push(LIST_FORMAT_ARG.to_string());
cmd.append(&mut query.to_args());
cmd
}
@ -168,7 +162,7 @@ impl LibraryPrivate for BeetsLibrary {
continue;
}
let split: Vec<&str> = line.split(Self::LIST_FORMAT_SEPARATOR).collect();
let split: Vec<&str> = line.split(LIST_FORMAT_SEPARATOR).collect();
if split.len() != 7 {
return Err(Error::InvalidData(line.to_string()));
}
@ -193,8 +187,8 @@ impl LibraryPrivate for BeetsLibrary {
title: track_title,
artist: track_artist.split("; ").map(|s| s.to_owned()).collect(),
format: match track_format.as_ref() {
Self::TRACK_FORMAT_FLAC => TrackFormat::Flac,
Self::TRACK_FORMAT_MP3 => TrackFormat::Mp3,
TRACK_FORMAT_FLAC => TrackFormat::Flac,
TRACK_FORMAT_MP3 => TrackFormat::Mp3,
_ => return Err(Error::InvalidData(track_format)),
},
};
@ -309,14 +303,14 @@ mod tests {
let track_title = &track.title;
let track_artist = &track.artist.join("; ");
let track_format = match track.format {
TrackFormat::Flac => BeetsLibrary::TRACK_FORMAT_FLAC,
TrackFormat::Mp3 => BeetsLibrary::TRACK_FORMAT_MP3,
TrackFormat::Flac => TRACK_FORMAT_FLAC,
TrackFormat::Mp3 => 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}",
BeetsLibrary::LIST_FORMAT_SEPARATOR,
LIST_FORMAT_SEPARATOR,
));
}
}
@ -356,7 +350,7 @@ mod tests {
#[test]
fn test_list_empty() {
let arguments = vec!["ls".to_string(), BeetsLibrary::LIST_FORMAT_ARG.to_string()];
let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()];
let result = Ok(vec![]);
let mut executor = MockBeetsLibraryExecutor::new();
@ -366,7 +360,7 @@ mod tests {
.times(1)
.return_once(|_| result);
let mut beets = BeetsLibrary::new(Box::new(executor));
let mut beets = BeetsLibrary::new(executor);
let output = beets.list(&Query::default()).unwrap();
let expected: Vec<Artist> = vec![];
@ -375,7 +369,7 @@ mod tests {
#[test]
fn test_list_ordered() {
let arguments = vec!["ls".to_string(), BeetsLibrary::LIST_FORMAT_ARG.to_string()];
let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()];
let expected = COLLECTION.to_owned();
let result = Ok(artists_to_beets_string(&expected));
@ -386,7 +380,7 @@ mod tests {
.times(1)
.return_once(|_| result);
let mut beets = BeetsLibrary::new(Box::new(executor));
let mut beets = BeetsLibrary::new(executor);
let output = beets.list(&Query::default()).unwrap();
assert_eq!(output, expected);
@ -394,7 +388,7 @@ mod tests {
#[test]
fn test_list_unordered() {
let arguments = vec!["ls".to_string(), BeetsLibrary::LIST_FORMAT_ARG.to_string()];
let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()];
let mut expected = COLLECTION.to_owned();
let mut output = artists_to_beets_string(&expected);
let last = output.len() - 1;
@ -421,7 +415,7 @@ mod tests {
.times(1)
.return_once(|_| result);
let mut beets = BeetsLibrary::new(Box::new(executor));
let mut beets = BeetsLibrary::new(executor);
let output = beets.list(&Query::default()).unwrap();
assert_eq!(output, expected);
@ -429,7 +423,7 @@ mod tests {
#[test]
fn test_list_album_title_year_clash() {
let arguments = vec!["ls".to_string(), BeetsLibrary::LIST_FORMAT_ARG.to_string()];
let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()];
let mut expected = COLLECTION.to_owned();
expected[0].albums[0].id.year = expected[1].albums[0].id.year;
expected[0].albums[0].id.title = expected[1].albums[0].id.title.clone();
@ -443,7 +437,7 @@ mod tests {
.times(1)
.return_once(|_| result);
let mut beets = BeetsLibrary::new(Box::new(executor));
let mut beets = BeetsLibrary::new(executor);
let output = beets.list(&Query::default()).unwrap();
assert_eq!(output, expected);
@ -458,7 +452,7 @@ mod tests {
let arguments = vec![
"ls".to_string(),
BeetsLibrary::LIST_FORMAT_ARG.to_string(),
LIST_FORMAT_ARG.to_string(),
String::from("^album:some.album"),
String::from("track:5"),
String::from("artist:some.artist"),
@ -472,7 +466,7 @@ mod tests {
.times(1)
.return_once(|_| result);
let mut beets = BeetsLibrary::new(Box::new(executor));
let mut beets = BeetsLibrary::new(executor);
let output = beets.list(&query).unwrap();
let expected: Vec<Artist> = vec![];

View File

@ -41,9 +41,9 @@ fn main() {
// Create the application.
let opt = Opt::from_args();
let beets = BeetsLibrary::new(Box::new(
let beets = BeetsLibrary::new(
BeetsLibraryCommandExecutor::default().config(opt.beets_config_file_path.as_deref()),
));
);
let database = JsonDatabase::new(Box::new(JsonDatabaseFileBackend::new(
&opt.database_file_path,

View File

@ -15,18 +15,20 @@ use musichoard::{
use crate::COLLECTION;
static BEETS_EMPTY_CONFIG: Lazy<Arc<Mutex<BeetsLibrary>>> = Lazy::new(|| {
Arc::new(Mutex::new(BeetsLibrary::new(Box::new(
static BEETS_EMPTY_CONFIG: Lazy<Arc<Mutex<BeetsLibrary<BeetsLibraryCommandExecutor>>>> =
Lazy::new(|| {
Arc::new(Mutex::new(BeetsLibrary::new(
BeetsLibraryCommandExecutor::default(),
))))
)))
});
static BEETS_TEST_CONFIG: Lazy<Arc<Mutex<BeetsLibrary>>> = Lazy::new(|| {
Arc::new(Mutex::new(BeetsLibrary::new(Box::new(
static BEETS_TEST_CONFIG: Lazy<Arc<Mutex<BeetsLibrary<BeetsLibraryCommandExecutor>>>> =
Lazy::new(|| {
Arc::new(Mutex::new(BeetsLibrary::new(
BeetsLibraryCommandExecutor::default().config(Some(
&fs::canonicalize("./tests/files/library/config.yml").unwrap(),
)),
))))
)))
});
#[test]