Clean up interfaces #62

Merged
wojtek merged 2 commits from 61---clean-up-interfaces into main 2023-05-10 22:52:03 +02:00
16 changed files with 128 additions and 128 deletions
Showing only changes of commit bf25393711 - Show all commits

View File

@ -17,7 +17,7 @@ env CARGO_TARGET_DIR=codecov \
env RUSTFLAGS="-C instrument-coverage" \ env RUSTFLAGS="-C instrument-coverage" \
LLVM_PROFILE_FILE="codecov/debug/profraw/musichoard-%p-%m.profraw" \ LLVM_PROFILE_FILE="codecov/debug/profraw/musichoard-%p-%m.profraw" \
CARGO_TARGET_DIR=codecov \ CARGO_TARGET_DIR=codecov \
cargo test --all-features cargo test --all-features --all-targets
grcov codecov/debug/profraw \ grcov codecov/debug/profraw \
--binary-path ./codecov/debug/ \ --binary-path ./codecov/debug/ \
--output-types html \ --output-types html \

View File

@ -3,7 +3,7 @@
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use super::JsonDatabaseBackend; use super::IJsonDatabaseBackend;
/// JSON database backend that uses a local file for persistent storage. /// JSON database backend that uses a local file for persistent storage.
pub struct JsonDatabaseFileBackend { pub struct JsonDatabaseFileBackend {
@ -17,7 +17,7 @@ impl JsonDatabaseFileBackend {
} }
} }
impl JsonDatabaseBackend for JsonDatabaseFileBackend { impl IJsonDatabaseBackend 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

View File

@ -6,7 +6,7 @@ use serde::Serialize;
#[cfg(test)] #[cfg(test)]
use mockall::automock; use mockall::automock;
use super::{Database, Error}; use super::{Error, IDatabase};
pub mod backend; pub mod backend;
@ -18,7 +18,7 @@ impl From<serde_json::Error> for Error {
/// Trait for the JSON database backend. /// Trait for the JSON database backend.
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait JsonDatabaseBackend { pub trait IJsonDatabaseBackend {
/// 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>;
@ -31,7 +31,7 @@ pub struct JsonDatabase<JDB> {
backend: JDB, backend: JDB,
} }
impl<JDB: JsonDatabaseBackend> JsonDatabase<JDB> { impl<JDB: IJsonDatabaseBackend> JsonDatabase<JDB> {
/// Create a new JSON database with the provided backend, e.g. /// Create a new JSON database with the provided backend, e.g.
/// [`backend::JsonDatabaseFileBackend`]. /// [`backend::JsonDatabaseFileBackend`].
pub fn new(backend: JDB) -> Self { pub fn new(backend: JDB) -> Self {
@ -39,7 +39,7 @@ impl<JDB: JsonDatabaseBackend> JsonDatabase<JDB> {
} }
} }
impl<JDB: JsonDatabaseBackend> Database for JsonDatabase<JDB> { impl<JDB: IJsonDatabaseBackend> IDatabase for JsonDatabase<JDB> {
fn read<D: DeserializeOwned>(&self, collection: &mut D) -> Result<(), Error> { fn read<D: DeserializeOwned>(&self, collection: &mut D) -> Result<(), Error> {
let serialized = self.backend.read()?; let serialized = self.backend.read()?;
*collection = serde_json::from_str(&serialized)?; *collection = serde_json::from_str(&serialized)?;
@ -132,7 +132,7 @@ mod tests {
let write_data = COLLECTION.to_owned(); let write_data = COLLECTION.to_owned();
let input = artists_to_json(&write_data); let input = artists_to_json(&write_data);
let mut backend = MockJsonDatabaseBackend::new(); let mut backend = MockIJsonDatabaseBackend::new();
backend backend
.expect_write() .expect_write()
.with(predicate::eq(input)) .with(predicate::eq(input))
@ -147,7 +147,7 @@ mod tests {
let expected = COLLECTION.to_owned(); let expected = COLLECTION.to_owned();
let result = Ok(artists_to_json(&expected)); let result = Ok(artists_to_json(&expected));
let mut backend = MockJsonDatabaseBackend::new(); let mut backend = MockIJsonDatabaseBackend::new();
backend.expect_read().times(1).return_once(|| result); backend.expect_read().times(1).return_once(|| result);
let mut read_data: Vec<Artist> = vec![]; let mut read_data: Vec<Artist> = vec![];
@ -162,7 +162,7 @@ mod tests {
let input = artists_to_json(&expected); let input = artists_to_json(&expected);
let result = Ok(input.clone()); let result = Ok(input.clone());
let mut backend = MockJsonDatabaseBackend::new(); let mut backend = MockIJsonDatabaseBackend::new();
backend backend
.expect_write() .expect_write()
.with(predicate::eq(input)) .with(predicate::eq(input))

View File

@ -10,6 +10,16 @@ use mockall::automock;
#[cfg(feature = "database-json")] #[cfg(feature = "database-json")]
pub mod json; pub mod json;
/// Trait for interacting with the database.
#[cfg_attr(test, automock)]
pub trait IDatabase {
/// Read collection from the database.
fn read<D: DeserializeOwned + 'static>(&self, collection: &mut D) -> Result<(), Error>;
/// Write collection to the database.
fn write<S: Serialize + 'static>(&mut self, collection: &S) -> Result<(), Error>;
}
/// Error type for database calls. /// Error type for database calls.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -36,16 +46,6 @@ impl From<std::io::Error> for Error {
} }
} }
/// Trait for interacting with the database.
#[cfg_attr(test, automock)]
pub trait Database {
/// Read collection from the database.
fn read<D: DeserializeOwned + 'static>(&self, collection: &mut D) -> Result<(), Error>;
/// Write collection to the database.
fn write<S: Serialize + 'static>(&mut self, collection: &S) -> Result<(), Error>;
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::io; use std::io;

View File

@ -5,8 +5,8 @@ pub mod library;
use std::fmt; use std::fmt;
use database::Database; use database::IDatabase;
use library::{Library, Query}; use library::{ILibrary, Query};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
@ -99,8 +99,8 @@ pub struct MusicHoard<LIB, DB> {
collection: Collection, collection: Collection,
} }
impl<LIB: Library, DB: Database> MusicHoard<LIB, DB> { impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
/// Create a new [`MusicHoard`] with the provided [`Library`] and [`Database`]. /// Create a new [`MusicHoard`] with the provided [`ILibrary`] and [`IDatabase`].
pub fn new(library: LIB, database: DB) -> Self { pub fn new(library: LIB, database: DB) -> Self {
MusicHoard { MusicHoard {
library, library,
@ -133,7 +133,7 @@ mod tests {
use mockall::predicate; use mockall::predicate;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::{database::MockDatabase, library::MockLibrary}; use crate::{database::MockIDatabase, library::MockILibrary};
use super::*; use super::*;
@ -141,8 +141,8 @@ mod tests {
#[test] #[test]
fn read_get_write() { fn read_get_write() {
let mut library = MockLibrary::new(); let mut library = MockILibrary::new();
let mut database = MockDatabase::new(); let mut database = MockIDatabase::new();
let library_input = Query::new(); let library_input = Query::new();
let library_result = Ok(COLLECTION.to_owned()); let library_result = Ok(COLLECTION.to_owned());
@ -171,8 +171,8 @@ mod tests {
#[test] #[test]
fn library_error() { fn library_error() {
let mut library = MockLibrary::new(); let mut library = MockILibrary::new();
let database = MockDatabase::new(); let database = MockIDatabase::new();
let library_result = Err(library::Error::Invalid(String::from("invalid data"))); let library_result = Err(library::Error::Invalid(String::from("invalid data")));
@ -193,8 +193,8 @@ mod tests {
#[test] #[test]
fn database_error() { fn database_error() {
let library = MockLibrary::new(); let library = MockILibrary::new();
let mut database = MockDatabase::new(); let mut database = MockIDatabase::new();
let database_result = Err(database::Error::IoError(String::from("I/O error"))); let database_result = Err(database::Error::IoError(String::from("I/O error")));

View File

@ -8,11 +8,11 @@ use std::{
str, str,
}; };
use super::{BeetsLibraryExecutor, Error}; use super::{IBeetsLibraryExecutor, Error};
const BEET_DEFAULT: &str = "beet"; const BEET_DEFAULT: &str = "beet";
trait BeetsLibraryExecutorPrivate { trait IBeetsLibraryExecutorPrivate {
fn output(output: Output) -> Result<Vec<String>, Error> { fn output(output: Output) -> Result<Vec<String>, Error> {
if !output.status.success() { if !output.status.success() {
return Err(Error::Executor( return Err(Error::Executor(
@ -59,7 +59,7 @@ impl Default for BeetsLibraryProcessExecutor {
} }
} }
impl BeetsLibraryExecutor for BeetsLibraryProcessExecutor { impl IBeetsLibraryExecutor for BeetsLibraryProcessExecutor {
fn exec<S: AsRef<str> + 'static>(&mut self, arguments: &[S]) -> Result<Vec<String>, Error> { fn exec<S: AsRef<str> + 'static>(&mut self, arguments: &[S]) -> 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 {
@ -71,7 +71,7 @@ impl BeetsLibraryExecutor for BeetsLibraryProcessExecutor {
} }
} }
impl BeetsLibraryExecutorPrivate for BeetsLibraryProcessExecutor {} impl IBeetsLibraryExecutorPrivate for BeetsLibraryProcessExecutor {}
// GRCOV_EXCL_START // GRCOV_EXCL_START
#[cfg(feature = "ssh-library")] #[cfg(feature = "ssh-library")]
@ -128,7 +128,7 @@ pub mod ssh {
} }
} }
impl BeetsLibraryExecutor for BeetsLibrarySshExecutor { impl IBeetsLibraryExecutor for BeetsLibrarySshExecutor {
fn exec<S: AsRef<str> + 'static>(&mut self, arguments: &[S]) -> Result<Vec<String>, Error> { fn exec<S: AsRef<str> + 'static>(&mut self, arguments: &[S]) -> Result<Vec<String>, Error> {
let mut cmd = self.session.command(&self.bin); let mut cmd = self.session.command(&self.bin);
if let Some(ref path) = self.config { if let Some(ref path) = self.config {
@ -141,6 +141,6 @@ pub mod ssh {
} }
} }
impl BeetsLibraryExecutorPrivate for BeetsLibrarySshExecutor {} impl IBeetsLibraryExecutorPrivate for BeetsLibrarySshExecutor {}
} }
// GRCOV_EXCL_STOP // GRCOV_EXCL_STOP

View File

@ -11,7 +11,7 @@ use mockall::automock;
use crate::{Album, AlbumId, Artist, ArtistId, Track, TrackFormat}; use crate::{Album, AlbumId, Artist, ArtistId, Track, TrackFormat};
use super::{Error, Field, Library, Query}; use super::{Error, Field, ILibrary, Query};
pub mod executor; pub mod executor;
@ -78,7 +78,7 @@ impl ToBeetsArgs for Query {
/// Trait for invoking beets commands. /// Trait for invoking beets commands.
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait BeetsLibraryExecutor { pub trait IBeetsLibraryExecutor {
/// Invoke beets with the provided arguments. /// Invoke beets with the provided arguments.
fn exec<S: AsRef<str> + 'static>(&mut self, arguments: &[S]) -> Result<Vec<String>, Error>; fn exec<S: AsRef<str> + 'static>(&mut self, arguments: &[S]) -> Result<Vec<String>, Error>;
} }
@ -88,12 +88,12 @@ pub struct BeetsLibrary<BLE> {
executor: BLE, executor: BLE,
} }
trait LibraryPrivate { trait ILibraryPrivate {
fn list_cmd_and_args(query: &Query) -> Vec<String>; fn list_cmd_and_args(query: &Query) -> Vec<String>;
fn list_to_artists<S: AsRef<str>>(list_output: &[S]) -> Result<Vec<Artist>, Error>; fn list_to_artists<S: AsRef<str>>(list_output: &[S]) -> Result<Vec<Artist>, Error>;
} }
impl<BLE: BeetsLibraryExecutor> BeetsLibrary<BLE> { impl<BLE: IBeetsLibraryExecutor> BeetsLibrary<BLE> {
/// Create a new beets library with the provided executor, e.g. /// Create a new beets library with the provided executor, e.g.
/// [`executor::BeetsLibraryProcessExecutor`]. /// [`executor::BeetsLibraryProcessExecutor`].
pub fn new(executor: BLE) -> Self { pub fn new(executor: BLE) -> Self {
@ -101,7 +101,7 @@ impl<BLE: BeetsLibraryExecutor> BeetsLibrary<BLE> {
} }
} }
impl<BLE: BeetsLibraryExecutor> Library for BeetsLibrary<BLE> { impl<BLE: IBeetsLibraryExecutor> ILibrary for BeetsLibrary<BLE> {
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)?;
@ -109,7 +109,7 @@ impl<BLE: BeetsLibraryExecutor> Library for BeetsLibrary<BLE> {
} }
} }
impl<BLE: BeetsLibraryExecutor> LibraryPrivate for BeetsLibrary<BLE> { impl<BLE: IBeetsLibraryExecutor> ILibraryPrivate for BeetsLibrary<BLE> {
fn list_cmd_and_args(query: &Query) -> Vec<String> { fn list_cmd_and_args(query: &Query) -> Vec<String> {
let mut cmd: Vec<String> = vec![String::from(CMD_LIST)]; let mut cmd: Vec<String> = vec![String::from(CMD_LIST)];
cmd.push(LIST_FORMAT_ARG.to_string()); cmd.push(LIST_FORMAT_ARG.to_string());
@ -293,7 +293,7 @@ mod tests {
let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()]; let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()];
let result = Ok(vec![]); let result = Ok(vec![]);
let mut executor = MockBeetsLibraryExecutor::new(); let mut executor = MockIBeetsLibraryExecutor::new();
executor executor
.expect_exec() .expect_exec()
.with(predicate::eq(arguments)) .with(predicate::eq(arguments))
@ -313,7 +313,7 @@ mod tests {
let expected = COLLECTION.to_owned(); let expected = COLLECTION.to_owned();
let result = Ok(artists_to_beets_string(&expected)); let result = Ok(artists_to_beets_string(&expected));
let mut executor = MockBeetsLibraryExecutor::new(); let mut executor = MockIBeetsLibraryExecutor::new();
executor executor
.expect_exec() .expect_exec()
.with(predicate::eq(arguments)) .with(predicate::eq(arguments))
@ -348,7 +348,7 @@ mod tests {
// track comes last. // track comes last.
expected[1].albums[0].tracks.rotate_left(1); expected[1].albums[0].tracks.rotate_left(1);
let mut executor = MockBeetsLibraryExecutor::new(); let mut executor = MockIBeetsLibraryExecutor::new();
executor executor
.expect_exec() .expect_exec()
.with(predicate::eq(arguments)) .with(predicate::eq(arguments))
@ -370,7 +370,7 @@ mod tests {
let output = artists_to_beets_string(&expected); let output = artists_to_beets_string(&expected);
let result = Ok(output); let result = Ok(output);
let mut executor = MockBeetsLibraryExecutor::new(); let mut executor = MockIBeetsLibraryExecutor::new();
executor executor
.expect_exec() .expect_exec()
.with(predicate::eq(arguments)) .with(predicate::eq(arguments))
@ -400,7 +400,7 @@ mod tests {
]; ];
let result = Ok(vec![]); let result = Ok(vec![]);
let mut executor = MockBeetsLibraryExecutor::new(); let mut executor = MockIBeetsLibraryExecutor::new();
executor executor
.expect_exec() .expect_exec()
.with(predicate::function(move |x: &[String]| { .with(predicate::function(move |x: &[String]| {
@ -431,7 +431,7 @@ mod tests {
output[2] = invalid_string.clone(); output[2] = invalid_string.clone();
let result = Ok(output); let result = Ok(output);
let mut executor = MockBeetsLibraryExecutor::new(); let mut executor = MockIBeetsLibraryExecutor::new();
executor executor
.expect_exec() .expect_exec()
.with(predicate::eq(arguments)) .with(predicate::eq(arguments))
@ -462,7 +462,7 @@ mod tests {
output[2] = invalid_string.clone(); output[2] = invalid_string.clone();
let result = Ok(output); let result = Ok(output);
let mut executor = MockBeetsLibraryExecutor::new(); let mut executor = MockIBeetsLibraryExecutor::new();
executor executor
.expect_exec() .expect_exec()
.with(predicate::eq(arguments)) .with(predicate::eq(arguments))

View File

@ -10,6 +10,13 @@ use crate::Artist;
#[cfg(feature = "library-beets")] #[cfg(feature = "library-beets")]
pub mod beets; pub mod beets;
/// Trait for interacting with the music library.
#[cfg_attr(test, automock)]
pub trait ILibrary {
/// List lirbary items that match the a specific query.
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error>;
}
/// Individual fields that can be queried on. /// Individual fields that can be queried on.
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]
pub enum Field { pub enum Field {
@ -103,13 +110,6 @@ impl From<Utf8Error> for Error {
} }
} }
/// Trait for interacting with the music library.
#[cfg_attr(test, automock)]
pub trait Library {
/// List lirbary items that match the a specific query.
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error>;
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::io; use std::io;

View File

@ -7,21 +7,21 @@ use structopt::StructOpt;
use musichoard::{ use musichoard::{
database::{ database::{
json::{backend::JsonDatabaseFileBackend, JsonDatabase}, json::{backend::JsonDatabaseFileBackend, JsonDatabase},
Database, IDatabase,
}, },
library::{ library::{
beets::{ beets::{
executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor}, executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor},
BeetsLibrary, BeetsLibrary,
}, },
Library, ILibrary,
}, },
MusicHoard, MusicHoard,
}; };
mod tui; mod tui;
use tui::ui::MhUi; use tui::ui::Ui;
use tui::{event::EventChannel, handler::TuiEventHandler, listener::TuiEventListener, Tui}; use tui::{event::EventChannel, handler::EventHandler, listener::EventListener, Tui};
#[derive(StructOpt)] #[derive(StructOpt)]
struct Opt { struct Opt {
@ -39,7 +39,7 @@ struct Opt {
database_file_path: PathBuf, database_file_path: PathBuf,
} }
fn with<LIB: Library, DB: Database>(lib: LIB, db: DB) { fn with<LIB: ILibrary, DB: IDatabase>(lib: LIB, db: DB) {
let music_hoard = MusicHoard::new(lib, db); let music_hoard = MusicHoard::new(lib, db);
// Initialize the terminal user interface. // Initialize the terminal user interface.
@ -47,10 +47,10 @@ fn with<LIB: Library, DB: Database>(lib: LIB, db: DB) {
let terminal = Terminal::new(backend).expect("failed to initialise terminal"); let terminal = Terminal::new(backend).expect("failed to initialise terminal");
let channel = EventChannel::new(); let channel = EventChannel::new();
let listener = TuiEventListener::new(channel.sender()); let listener = EventListener::new(channel.sender());
let handler = TuiEventHandler::new(channel.receiver()); let handler = EventHandler::new(channel.receiver());
let ui = MhUi::new(music_hoard).expect("failed to initialise ui"); let ui = Ui::new(music_hoard).expect("failed to initialise ui");
// Run the TUI application. // Run the TUI application.
Tui::run(terminal, ui, handler, listener).expect("failed to run tui"); Tui::run(terminal, ui, handler, listener).expect("failed to run tui");

View File

@ -5,30 +5,30 @@ use mockall::automock;
use super::{ use super::{
event::{Event, EventError, EventReceiver}, event::{Event, EventError, EventReceiver},
ui::Ui, ui::IUi,
}; };
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait EventHandler<UI> { pub trait IEventHandler<UI> {
fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError>; fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError>;
} }
trait EventHandlerPrivate<UI> { trait IEventHandlerPrivate<UI> {
fn handle_key_event(ui: &mut UI, key_event: KeyEvent); fn handle_key_event(ui: &mut UI, key_event: KeyEvent);
} }
pub struct TuiEventHandler { pub struct EventHandler {
events: EventReceiver, events: EventReceiver,
} }
// GRCOV_EXCL_START // GRCOV_EXCL_START
impl TuiEventHandler { impl EventHandler {
pub fn new(events: EventReceiver) -> Self { pub fn new(events: EventReceiver) -> Self {
TuiEventHandler { events } EventHandler { events }
} }
} }
impl<UI: Ui> EventHandler<UI> for TuiEventHandler { impl<UI: IUi> IEventHandler<UI> for EventHandler {
fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError> { fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError> {
match self.events.recv()? { match self.events.recv()? {
Event::Key(key_event) => Self::handle_key_event(ui, key_event), Event::Key(key_event) => Self::handle_key_event(ui, key_event),
@ -39,7 +39,7 @@ impl<UI: Ui> EventHandler<UI> for TuiEventHandler {
} }
} }
impl<UI: Ui> EventHandlerPrivate<UI> for TuiEventHandler { impl<UI: IUi> IEventHandlerPrivate<UI> for EventHandler {
fn handle_key_event(ui: &mut UI, key_event: KeyEvent) { fn handle_key_event(ui: &mut UI, key_event: KeyEvent) {
match key_event.code { match key_event.code {
// Exit application on `ESC` or `q`. // Exit application on `ESC` or `q`.

View File

@ -1,4 +1,4 @@
use musichoard::{MusicHoard, library::Library, database::Database, Collection}; use musichoard::{database::IDatabase, library::ILibrary, Collection, MusicHoard};
#[cfg(test)] #[cfg(test)]
use mockall::automock; use mockall::automock;
@ -18,7 +18,7 @@ impl From<musichoard::Error> for Error {
} }
// GRCOV_EXCL_START // GRCOV_EXCL_START
impl<LIB: Library, DB: Database> IMusicHoard for MusicHoard<LIB, DB> { impl<LIB: ILibrary, DB: IDatabase> IMusicHoard for MusicHoard<LIB, DB> {
fn rescan_library(&mut self) -> Result<(), Error> { fn rescan_library(&mut self) -> Result<(), Error> {
Ok(MusicHoard::rescan_library(self)?) Ok(MusicHoard::rescan_library(self)?)
} }

View File

@ -7,22 +7,22 @@ use mockall::automock;
use super::event::{Event, EventError, EventSender}; use super::event::{Event, EventError, EventSender};
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait EventListener { pub trait IEventListener {
fn spawn(self) -> thread::JoinHandle<EventError>; fn spawn(self) -> thread::JoinHandle<EventError>;
} }
pub struct TuiEventListener { pub struct EventListener {
events: EventSender, events: EventSender,
} }
// GRCOV_EXCL_START // GRCOV_EXCL_START
impl TuiEventListener { impl EventListener {
pub fn new(events: EventSender) -> Self { pub fn new(events: EventSender) -> Self {
TuiEventListener { events } EventListener { events }
} }
} }
impl EventListener for TuiEventListener { impl IEventListener for EventListener {
fn spawn(self) -> thread::JoinHandle<EventError> { fn spawn(self) -> thread::JoinHandle<EventError> {
thread::spawn(move || { thread::spawn(move || {
loop { loop {

View File

@ -13,9 +13,9 @@ use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use self::event::EventError; use self::event::EventError;
use self::handler::EventHandler; use self::handler::IEventHandler;
use self::listener::EventListener; use self::listener::IEventListener;
use self::ui::Ui; use self::ui::IUi;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Error { pub enum Error {
@ -42,7 +42,7 @@ pub struct Tui<B: Backend, UI> {
_phantom: PhantomData<UI>, _phantom: PhantomData<UI>,
} }
impl<B: Backend, UI: Ui> Tui<B, UI> { impl<B: Backend, UI: IUi> Tui<B, UI> {
fn init(&mut self) -> Result<(), Error> { fn init(&mut self) -> Result<(), Error> {
self.terminal.hide_cursor()?; self.terminal.hide_cursor()?;
self.terminal.clear()?; self.terminal.clear()?;
@ -59,7 +59,7 @@ impl<B: Backend, UI: Ui> Tui<B, UI> {
self.exit(); self.exit();
} }
fn main_loop(&mut self, mut ui: UI, handler: impl EventHandler<UI>) -> Result<(), Error> { fn main_loop(&mut self, mut ui: UI, handler: impl IEventHandler<UI>) -> Result<(), Error> {
while ui.is_running() { while ui.is_running() {
self.terminal.draw(|frame| ui.render(frame))?; self.terminal.draw(|frame| ui.render(frame))?;
handler.handle_next_event(&mut ui)?; handler.handle_next_event(&mut ui)?;
@ -71,8 +71,8 @@ impl<B: Backend, UI: Ui> Tui<B, UI> {
fn main( fn main(
term: Terminal<B>, term: Terminal<B>,
ui: UI, ui: UI,
handler: impl EventHandler<UI>, handler: impl IEventHandler<UI>,
listener: impl EventListener, listener: impl IEventListener,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut tui = Tui { let mut tui = Tui {
terminal: term, terminal: term,
@ -130,8 +130,8 @@ impl<B: Backend, UI: Ui> Tui<B, UI> {
pub fn run( pub fn run(
term: Terminal<B>, term: Terminal<B>,
ui: UI, ui: UI,
handler: impl EventHandler<UI>, handler: impl IEventHandler<UI>,
listener: impl EventListener, listener: impl IEventListener,
) -> Result<(), Error> { ) -> Result<(), Error> {
Self::enable()?; Self::enable()?;
let result = Self::main(term, ui, handler, listener); let result = Self::main(term, ui, handler, listener);
@ -162,10 +162,10 @@ mod tests {
use super::{ use super::{
event::EventError, event::EventError,
handler::MockEventHandler, handler::MockIEventHandler,
lib::MockIMusicHoard, lib::MockIMusicHoard,
listener::MockEventListener, listener::MockIEventListener,
ui::{MhUi, Ui}, ui::{IUi, Ui},
Error, Tui, Error, Tui,
}; };
@ -174,17 +174,17 @@ mod tests {
Terminal::new(backend).unwrap() Terminal::new(backend).unwrap()
} }
pub fn ui(collection: Collection) -> MhUi<MockIMusicHoard> { pub fn ui(collection: Collection) -> Ui<MockIMusicHoard> {
let mut music_hoard = MockIMusicHoard::new(); let mut music_hoard = MockIMusicHoard::new();
music_hoard.expect_rescan_library().returning(|| Ok(())); music_hoard.expect_rescan_library().returning(|| Ok(()));
music_hoard.expect_get_collection().return_const(collection); music_hoard.expect_get_collection().return_const(collection);
MhUi::new(music_hoard).unwrap() Ui::new(music_hoard).unwrap()
} }
fn listener() -> MockEventListener { fn listener() -> MockIEventListener {
let mut listener = MockEventListener::new(); let mut listener = MockIEventListener::new();
listener.expect_spawn().return_once(|| { listener.expect_spawn().return_once(|| {
thread::spawn(|| { thread::spawn(|| {
thread::park(); thread::park();
@ -194,11 +194,11 @@ mod tests {
listener listener
} }
fn handler() -> MockEventHandler<MhUi<MockIMusicHoard>> { fn handler() -> MockIEventHandler<Ui<MockIMusicHoard>> {
let mut handler = MockEventHandler::new(); let mut handler = MockIEventHandler::new();
handler handler
.expect_handle_next_event() .expect_handle_next_event()
.return_once(|ui: &mut MhUi<MockIMusicHoard>| { .return_once(|ui: &mut Ui<MockIMusicHoard>| {
ui.quit(); ui.quit();
Ok(()) Ok(())
}); });
@ -224,7 +224,7 @@ mod tests {
let listener = listener(); let listener = listener();
let mut handler = MockEventHandler::new(); let mut handler = MockIEventHandler::new();
handler handler
.expect_handle_next_event() .expect_handle_next_event()
.return_once(|_| Err(EventError::Recv)); .return_once(|_| Err(EventError::Recv));
@ -246,10 +246,10 @@ mod tests {
let listener_handle: thread::JoinHandle<EventError> = thread::spawn(|| error); let listener_handle: thread::JoinHandle<EventError> = thread::spawn(|| error);
while !listener_handle.is_finished() {} while !listener_handle.is_finished() {}
let mut listener = MockEventListener::new(); let mut listener = MockIEventListener::new();
listener.expect_spawn().return_once(|| listener_handle); listener.expect_spawn().return_once(|| listener_handle);
let mut handler = MockEventHandler::new(); let mut handler = MockIEventHandler::new();
handler handler
.expect_handle_next_event() .expect_handle_next_event()
.return_once(|_| Err(EventError::Recv)); .return_once(|_| Err(EventError::Recv));
@ -269,10 +269,10 @@ mod tests {
let listener_handle: thread::JoinHandle<EventError> = thread::spawn(|| panic!()); let listener_handle: thread::JoinHandle<EventError> = thread::spawn(|| panic!());
while !listener_handle.is_finished() {} while !listener_handle.is_finished() {}
let mut listener = MockEventListener::new(); let mut listener = MockIEventListener::new();
listener.expect_spawn().return_once(|| listener_handle); listener.expect_spawn().return_once(|| listener_handle);
let mut handler = MockEventHandler::new(); let mut handler = MockIEventHandler::new();
handler handler
.expect_handle_next_event() .expect_handle_next_event()
.return_once(|_| Err(EventError::Recv)); .return_once(|_| Err(EventError::Recv));

View File

@ -9,6 +9,19 @@ use ratatui::{
use super::{lib::IMusicHoard, Error}; use super::{lib::IMusicHoard, Error};
pub trait IUi {
fn is_running(&self) -> bool;
fn quit(&mut self);
fn increment_category(&mut self);
fn decrement_category(&mut self);
fn increment_selection(&mut self);
fn decrement_selection(&mut self);
fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>);
}
struct TrackSelection { struct TrackSelection {
state: ListState, state: ListState,
} }
@ -236,7 +249,7 @@ impl Selection {
} }
} }
pub struct MhUi<MH> { pub struct Ui<MH> {
music_hoard: MH, music_hoard: MH,
selection: Selection, selection: Selection,
running: bool, running: bool,
@ -425,11 +438,11 @@ impl<'a, 'b> TrackState<'a, 'b> {
} }
} }
impl<MH: IMusicHoard> MhUi<MH> { impl<MH: IMusicHoard> Ui<MH> {
pub fn new(mut music_hoard: MH) -> Result<Self, Error> { pub fn new(mut music_hoard: MH) -> Result<Self, Error> {
music_hoard.rescan_library()?; music_hoard.rescan_library()?;
let selection = Selection::new(Some(music_hoard.get_collection())); let selection = Selection::new(Some(music_hoard.get_collection()));
Ok(MhUi { Ok(Ui {
music_hoard, music_hoard,
selection, selection,
running: true, running: true,
@ -509,20 +522,7 @@ impl<MH: IMusicHoard> MhUi<MH> {
} }
} }
pub trait Ui { impl<MH: IMusicHoard> IUi for Ui<MH> {
fn is_running(&self) -> bool;
fn quit(&mut self);
fn increment_category(&mut self);
fn decrement_category(&mut self);
fn increment_selection(&mut self);
fn decrement_selection(&mut self);
fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>);
}
impl<MH: IMusicHoard> Ui for MhUi<MH> {
fn is_running(&self) -> bool { fn is_running(&self) -> bool {
self.running self.running
} }
@ -767,7 +767,7 @@ mod tests {
.expect_get_collection() .expect_get_collection()
.return_const(COLLECTION.to_owned()); .return_const(COLLECTION.to_owned());
let mut ui = MhUi::new(music_hoard).unwrap(); let mut ui = Ui::new(music_hoard).unwrap();
assert!(ui.is_running()); assert!(ui.is_running());
ui.quit(); ui.quit();
@ -786,7 +786,7 @@ mod tests {
.expect_get_collection() .expect_get_collection()
.return_const(COLLECTION.to_owned()); .return_const(COLLECTION.to_owned());
let mut ui = MhUi::new(music_hoard).unwrap(); let mut ui = Ui::new(music_hoard).unwrap();
assert!(ui.is_running()); assert!(ui.is_running());
assert_eq!(ui.selection.active, Category::Artist); assert_eq!(ui.selection.active, Category::Artist);
@ -885,7 +885,7 @@ mod tests {
.return_once(|| Ok(())); .return_once(|| Ok(()));
music_hoard.expect_get_collection().return_const(collection); music_hoard.expect_get_collection().return_const(collection);
let mut app = MhUi::new(music_hoard).unwrap(); let mut app = Ui::new(music_hoard).unwrap();
assert!(app.is_running()); assert!(app.is_running());
assert_eq!(app.selection.active, Category::Artist); assert_eq!(app.selection.active, Category::Artist);
@ -921,7 +921,7 @@ mod tests {
.return_once(|| Ok(())); .return_once(|| Ok(()));
music_hoard.expect_get_collection().return_const(collection); music_hoard.expect_get_collection().return_const(collection);
let mut app = MhUi::new(music_hoard).unwrap(); let mut app = Ui::new(music_hoard).unwrap();
assert!(app.is_running()); assert!(app.is_running());
assert_eq!(app.selection.active, Category::Artist); assert_eq!(app.selection.active, Category::Artist);
@ -969,7 +969,7 @@ mod tests {
.return_once(|| Ok(())); .return_once(|| Ok(()));
music_hoard.expect_get_collection().return_const(collection); music_hoard.expect_get_collection().return_const(collection);
let mut app = MhUi::new(music_hoard).unwrap(); let mut app = Ui::new(music_hoard).unwrap();
assert!(app.is_running()); assert!(app.is_running());
assert_eq!(app.selection.active, Category::Artist); assert_eq!(app.selection.active, Category::Artist);

View File

@ -3,7 +3,7 @@ use std::{fs, path::PathBuf};
use musichoard::{ use musichoard::{
database::{ database::{
json::{backend::JsonDatabaseFileBackend, JsonDatabase}, json::{backend::JsonDatabaseFileBackend, JsonDatabase},
Database, IDatabase,
}, },
Artist, Artist,
}; };

View File

@ -9,7 +9,7 @@ use once_cell::sync::Lazy;
use musichoard::{ use musichoard::{
library::{ library::{
beets::{executor::BeetsLibraryProcessExecutor, BeetsLibrary}, beets::{executor::BeetsLibraryProcessExecutor, BeetsLibrary},
Field, Library, Query, Field, ILibrary, Query,
}, },
Artist, Artist,
}; };