Generic beets executor
This commit is contained in:
parent
5edcceeae5
commit
73b4cc8b28
@ -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![];
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user