Hide Job structs behind trait

This commit is contained in:
Wojciech Kozlowski 2024-09-21 15:54:22 +02:00
parent 7d99219f44
commit f4745b5d68
4 changed files with 103 additions and 46 deletions

View File

@ -94,13 +94,13 @@ fn with<Database: IDatabase + 'static, Library: ILibrary + 'static>(
let listener = EventListener::new(listener_sender); let listener = EventListener::new(listener_sender);
let handler = EventHandler::new(channel.receiver()); let handler = EventHandler::new(channel.receiver());
let mb_request_channel = JobChannel::new(); let mb_job_channel = JobChannel::new();
thread::spawn(|| MusicBrainzDaemon::run(musicbrainz, mb_request_channel.receiver, app_sender));
let app = App::new(music_hoard, mb_request_channel.sender); let app = App::new(music_hoard, mb_job_channel.sender());
let ui = Ui; let ui = Ui;
// Run the TUI application. // Run the TUI application.
thread::spawn(|| MusicBrainzDaemon::run(musicbrainz, mb_job_channel.receiver(), app_sender));
Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui"); Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui");
} }

View File

@ -11,7 +11,7 @@ use crate::tui::{
AppPublicState, AppState, IAppEventFetch, IAppInteractFetch, MatchStateInfo, AppPublicState, AppState, IAppEventFetch, IAppInteractFetch, MatchStateInfo,
}, },
lib::{ lib::{
external::musicbrainz::daemon::{ApiParams, Job, JobInstance, JobPriority, ResultSender}, external::musicbrainz::daemon::{MbParams, ResultSender},
interface::musicbrainz::api::Error as MbError, interface::musicbrainz::api::Error as MbError,
}, },
}; };
@ -80,21 +80,18 @@ impl AppMachine<FetchState> {
} }
fn submit_fetch_job(inner: &AppInner, artist: &Artist, tx: ResultSender) { fn submit_fetch_job(inner: &AppInner, artist: &Artist, tx: ResultSender) {
let mut queue = VecDeque::new(); let queue = match artist.meta.musicbrainz {
match artist.meta.musicbrainz {
Some(ref arid) => { Some(ref arid) => {
let arid = arid.mbid(); let arid = arid.mbid();
for album in artist.albums.iter() { artist
let meta = &album.meta; .albums
queue.push_back(ApiParams::search_release_group(arid.clone(), meta.clone())); .iter()
.map(|album| MbParams::search_release_group(arid.clone(), album.meta.clone()))
.collect()
} }
} None => VecDeque::from([MbParams::search_artist(artist.meta.clone())]),
None => { };
queue.push_back(ApiParams::search_artist(artist.meta.clone())); inner.musicbrainz.submit_background_job(tx, queue);
}
}
let job = Job::new(JobPriority::Background, JobInstance::new(tx, queue));
inner.musicbrainz.send(job);
} }
} }

View File

@ -8,14 +8,12 @@ mod match_state;
mod reload_state; mod reload_state;
mod search_state; mod search_state;
use std::sync::mpsc;
use crate::tui::{ use crate::tui::{
app::{ app::{
selection::Selection, AppMode, AppPublic, AppPublicInner, AppPublicState, AppState, IApp, selection::Selection, AppMode, AppPublic, AppPublicInner, AppPublicState, AppState, IApp,
IAppAccess, IAppBase, IAppState, IAppAccess, IAppBase, IAppState,
}, },
lib::{external::musicbrainz::daemon::Job, IMusicHoard}, lib::{external::musicbrainz::daemon::IMbJobSender, IMusicHoard},
}; };
use browse_state::BrowseState; use browse_state::BrowseState;
@ -48,7 +46,7 @@ pub struct AppMachine<STATE> {
pub struct AppInner { pub struct AppInner {
running: bool, running: bool,
music_hoard: Box<dyn IMusicHoard>, music_hoard: Box<dyn IMusicHoard>,
musicbrainz: mpsc::Sender<Job>, musicbrainz: Box<dyn IMbJobSender>,
selection: Selection, selection: Selection,
} }
@ -83,9 +81,9 @@ macro_rules! app_field_mut {
} }
impl App { impl App {
pub fn new<MH: IMusicHoard + 'static>( pub fn new<MH: IMusicHoard + 'static, MB: IMbJobSender + 'static>(
mut music_hoard: MH, mut music_hoard: MH,
musicbrainz: mpsc::Sender<Job>, musicbrainz: MB,
) -> Self { ) -> Self {
let init_result = Self::init(&mut music_hoard); let init_result = Self::init(&mut music_hoard);
let inner = AppInner::new(music_hoard, musicbrainz); let inner = AppInner::new(music_hoard, musicbrainz);
@ -171,12 +169,15 @@ impl IAppAccess for App {
} }
impl AppInner { impl AppInner {
pub fn new<MH: IMusicHoard + 'static>(music_hoard: MH, musicbrainz: mpsc::Sender<Job>) -> Self { pub fn new<MH: IMusicHoard + 'static, MB: IMbJobSender + 'static>(
music_hoard: MH,
musicbrainz: MB,
) -> Self {
let selection = Selection::new(music_hoard.get_collection()); let selection = Selection::new(music_hoard.get_collection());
AppInner { AppInner {
running: true, running: true,
music_hoard: Box::new(music_hoard), music_hoard: Box::new(music_hoard),
musicbrainz, musicbrainz: Box::new(musicbrainz),
selection, selection,
} }
} }

View File

@ -10,7 +10,7 @@ use crate::tui::{
pub enum Error { pub enum Error {
EventChannelDisconnected, EventChannelDisconnected,
RequestChannelDisconnected, JobChannelDisconnected,
} }
pub struct MusicBrainzDaemon { pub struct MusicBrainzDaemon {
@ -30,7 +30,7 @@ struct JobQueue {
background_queue: VecDeque<JobInstance>, background_queue: VecDeque<JobInstance>,
} }
pub struct Job { struct Job {
priority: JobPriority, priority: JobPriority,
instance: JobInstance, instance: JobInstance,
} }
@ -41,15 +41,15 @@ impl Job {
} }
} }
pub enum JobPriority { enum JobPriority {
Foreground, Foreground,
Background, Background,
} }
pub type ResultSender = mpsc::Sender<Result<MatchStateInfo, ApiError>>; pub type ResultSender = mpsc::Sender<Result<MatchStateInfo, ApiError>>;
pub struct JobInstance { struct JobInstance {
result_sender: ResultSender, result_sender: ResultSender,
call_queue: VecDeque<ApiParams>, requests: VecDeque<MbParams>,
} }
enum JobInstanceStatus { enum JobInstanceStatus {
@ -63,25 +63,25 @@ enum JobInstanceError {
} }
impl JobInstance { impl JobInstance {
pub fn new(result_sender: ResultSender, call_queue: VecDeque<ApiParams>) -> Self { fn new(result_sender: ResultSender, requests: VecDeque<MbParams>) -> Self {
JobInstance { JobInstance {
result_sender, result_sender,
call_queue, requests,
} }
} }
} }
pub enum ApiParams { pub enum MbParams {
Search(SearchParams), Search(SearchParams),
} }
impl ApiParams { impl MbParams {
pub fn search_artist(artist: ArtistMeta) -> Self { pub fn search_artist(artist: ArtistMeta) -> Self {
ApiParams::Search(SearchParams::Artist(SearchArtistParams { artist })) MbParams::Search(SearchParams::Artist(SearchArtistParams { artist }))
} }
pub fn search_release_group(arid: Mbid, album: AlbumMeta) -> Self { pub fn search_release_group(arid: Mbid, album: AlbumMeta) -> Self {
ApiParams::Search(SearchParams::ReleaseGroup(SearchReleaseGroupParams { MbParams::Search(SearchParams::ReleaseGroup(SearchReleaseGroupParams {
arid, arid,
album, album,
})) }))
@ -103,8 +103,16 @@ pub struct SearchReleaseGroupParams {
} }
pub struct JobChannel { pub struct JobChannel {
pub receiver: mpsc::Receiver<Job>, sender: mpsc::Sender<Job>,
pub sender: mpsc::Sender<Job>, receiver: mpsc::Receiver<Job>,
}
pub struct JobSender {
sender: mpsc::Sender<Job>,
}
pub struct JobReceiver {
receiver: mpsc::Receiver<Job>,
} }
impl JobChannel { impl JobChannel {
@ -112,17 +120,68 @@ impl JobChannel {
let (sender, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
JobChannel { receiver, sender } JobChannel { receiver, sender }
} }
pub fn sender(&self) -> JobSender {
JobSender {
sender: self.sender.clone(),
}
}
pub fn receiver(self) -> JobReceiver {
JobReceiver {
receiver: self.receiver,
}
}
}
pub trait IMbJobSender {
fn submit_background_job(
&self,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error>;
}
impl IMbJobSender for JobSender {
fn submit_background_job(
&self,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error> {
self.send_background_job(result_sender, requests)
}
}
impl JobSender {
fn send_background_job(
&self,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error> {
self.send_job(JobPriority::Background, result_sender, requests)
}
fn send_job(
&self,
priority: JobPriority,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error> {
let instance = JobInstance::new(result_sender, requests);
let job = Job::new(priority, instance);
self.sender.send(job).or(Err(Error::JobChannelDisconnected))
}
} }
impl MusicBrainzDaemon { impl MusicBrainzDaemon {
pub fn run<MB: IMusicBrainz + 'static, ES: IFetchCompleteEventSender + 'static>( pub fn run<MB: IMusicBrainz + 'static, ES: IFetchCompleteEventSender + 'static>(
musicbrainz: MB, musicbrainz: MB,
job_receiver: mpsc::Receiver<Job>, job_receiver: JobReceiver,
event_sender: ES, event_sender: ES,
) -> Result<(), Error> { ) -> Result<(), Error> {
let daemon = MusicBrainzDaemon { let daemon = MusicBrainzDaemon {
musicbrainz: Box::new(musicbrainz), musicbrainz: Box::new(musicbrainz),
job_receiver, job_receiver: job_receiver.receiver,
job_queue: JobQueue::new(), job_queue: JobQueue::new(),
event_sender: Box::new(event_sender), event_sender: Box::new(event_sender),
}; };
@ -155,7 +214,7 @@ impl MusicBrainzDaemon {
Ok(job) => self.job_queue.push_back(job), Ok(job) => self.job_queue.push_back(job),
Err(mpsc::TryRecvError::Empty) => return Ok(()), Err(mpsc::TryRecvError::Empty) => return Ok(()),
Err(mpsc::TryRecvError::Disconnected) => { Err(mpsc::TryRecvError::Disconnected) => {
return Err(Error::RequestChannelDisconnected); return Err(Error::JobChannelDisconnected);
} }
} }
} }
@ -184,7 +243,7 @@ impl MusicBrainzDaemon {
match self.job_receiver.recv() { match self.job_receiver.recv() {
Ok(job) => self.job_queue.push_back(job), Ok(job) => self.job_queue.push_back(job),
Err(mpsc::RecvError) => return Err(Error::RequestChannelDisconnected), Err(mpsc::RecvError) => return Err(Error::JobChannelDisconnected),
} }
Ok(()) Ok(())
@ -197,12 +256,12 @@ impl JobInstance {
musicbrainz: &mut Box<dyn IMusicBrainz>, musicbrainz: &mut Box<dyn IMusicBrainz>,
event_sender: &mut Box<dyn IFetchCompleteEventSender>, event_sender: &mut Box<dyn IFetchCompleteEventSender>,
) -> Result<JobInstanceStatus, JobInstanceError> { ) -> Result<JobInstanceStatus, JobInstanceError> {
// self.call_queue can be empty if the caller submits an empty job. // self.requests can be empty if the caller submits an empty job.
if let Some(params) = self.call_queue.pop_front() { if let Some(params) = self.requests.pop_front() {
self.execute(musicbrainz, event_sender, params)? self.execute(musicbrainz, event_sender, params)?
} }
if self.call_queue.is_empty() { if self.requests.is_empty() {
Ok(JobInstanceStatus::Complete) Ok(JobInstanceStatus::Complete)
} else { } else {
Ok(JobInstanceStatus::Continue) Ok(JobInstanceStatus::Continue)
@ -213,10 +272,10 @@ impl JobInstance {
&mut self, &mut self,
musicbrainz: &mut Box<dyn IMusicBrainz>, musicbrainz: &mut Box<dyn IMusicBrainz>,
event_sender: &mut Box<dyn IFetchCompleteEventSender>, event_sender: &mut Box<dyn IFetchCompleteEventSender>,
api_params: ApiParams, api_params: MbParams,
) -> Result<(), JobInstanceError> { ) -> Result<(), JobInstanceError> {
match api_params { match api_params {
ApiParams::Search(search) => match search { MbParams::Search(search) => match search {
SearchParams::Artist(params) => { SearchParams::Artist(params) => {
let result = musicbrainz.search_artist(&params.artist); let result = musicbrainz.search_artist(&params.artist);
let result = result.map(|list| MatchStateInfo::artist(params.artist, list)); let result = result.map(|list| MatchStateInfo::artist(params.artist, list));