Daemonize the musicbrainz thread #217
224
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
Normal file
224
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
use std::{collections::VecDeque, sync::mpsc, thread, time};
|
||||||
|
|
||||||
|
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid};
|
||||||
|
|
||||||
|
use crate::tui::{
|
||||||
|
app::MatchStateInfo,
|
||||||
|
event::{Event, EventSender},
|
||||||
|
lib::interface::musicbrainz::api::{Error as ApiError, IMusicBrainz},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum Error {
|
||||||
|
RequestRecv(String),
|
||||||
|
Api(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<mpsc::RecvError> for Error {
|
||||||
|
fn from(value: mpsc::RecvError) -> Self {
|
||||||
|
Error::RequestRecv(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ApiError> for Error {
|
||||||
|
fn from(value: ApiError) -> Self {
|
||||||
|
Error::Api(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MusicBrainzDaemon {
|
||||||
|
api: Box<dyn IMusicBrainz>,
|
||||||
|
request_channel: RequestChannel,
|
||||||
|
job_queue: JobQueue,
|
||||||
|
events: EventSender,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JobQueue {
|
||||||
|
foreground_queue: VecDeque<JobInstance>,
|
||||||
|
background_queue: VecDeque<JobInstance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobQueue {
|
||||||
|
fn new() -> Self {
|
||||||
|
JobQueue {
|
||||||
|
foreground_queue: VecDeque::new(),
|
||||||
|
background_queue: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.foreground_queue.is_empty() && self.background_queue.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn front_mut(&mut self) -> Option<&mut JobInstance> {
|
||||||
|
self.foreground_queue
|
||||||
|
.front_mut()
|
||||||
|
.or_else(|| self.background_queue.front_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_front(&mut self) -> Option<JobInstance> {
|
||||||
|
self.foreground_queue
|
||||||
|
.pop_front()
|
||||||
|
.or_else(|| self.background_queue.pop_front())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_back(&mut self, job: Job) {
|
||||||
|
match job.priority {
|
||||||
|
JobPriority::Foreground => self.foreground_queue.push_back(job.instance),
|
||||||
|
JobPriority::Background => self.background_queue.push_back(job.instance),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Job {
|
||||||
|
priority: JobPriority,
|
||||||
|
instance: JobInstance,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JobPriority {
|
||||||
|
Foreground,
|
||||||
|
Background,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnSender = mpsc::Sender<Result<MatchStateInfo, ApiError>>;
|
||||||
|
struct JobInstance {
|
||||||
|
return_sender: ReturnSender,
|
||||||
|
call_queue: VecDeque<ApiParams>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ApiParams {
|
||||||
|
Search(SearchParams),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SearchParams {
|
||||||
|
Artist(SearchArtistParams),
|
||||||
|
ReleaseGroup(SearchReleaseGroupParams),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchArtistParams {
|
||||||
|
artist: ArtistMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchReleaseGroupParams {
|
||||||
|
arid: Mbid,
|
||||||
|
album: AlbumMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RequestChannel {
|
||||||
|
pub receiver: mpsc::Receiver<Job>,
|
||||||
|
pub sender: mpsc::Sender<Job>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestChannel {
|
||||||
|
fn new() -> Self {
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
RequestChannel { receiver, sender }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MusicBrainzDaemon {
|
||||||
|
pub fn run(api: Box<dyn IMusicBrainz>, events: EventSender) {
|
||||||
|
let daemon = MusicBrainzDaemon {
|
||||||
|
api,
|
||||||
|
request_channel: RequestChannel::new(),
|
||||||
|
job_queue: JobQueue::new(),
|
||||||
|
events,
|
||||||
|
};
|
||||||
|
daemon.main();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_for_jobs(&mut self) -> Result<(), Error> {
|
||||||
|
if self.job_queue.is_empty() {
|
||||||
|
self.job_queue
|
||||||
|
.push_back(self.request_channel.receiver.recv()?);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enqueue_all_pending_jobs(&mut self) -> Result<(), Error> {
|
||||||
|
loop {
|
||||||
|
match self.request_channel.receiver.try_recv() {
|
||||||
|
Ok(job) => self.job_queue.push_back(job),
|
||||||
|
Err(mpsc::TryRecvError::Empty) => return Ok(()),
|
||||||
|
Err(mpsc::TryRecvError::Disconnected) => {
|
||||||
|
return Err(Error::RequestRecv(
|
||||||
|
mpsc::TryRecvError::Disconnected.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_next_job(&mut self) -> Option<()> {
|
||||||
|
while let Some(instance) = self.job_queue.front_mut() {
|
||||||
|
match instance.execute_next(&mut self.api, &mut self.events) {
|
||||||
|
Some(()) => return Some(()),
|
||||||
|
None => {
|
||||||
|
self.job_queue.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(mut self) -> Result<(), Error> {
|
||||||
|
loop {
|
||||||
|
self.wait_for_jobs()?;
|
||||||
|
self.enqueue_all_pending_jobs()?;
|
||||||
|
if let Some(()) = self.execute_next_job() {
|
||||||
|
// Sleep for one second. Required by MB API rate limiting. Assume all other
|
||||||
|
// processing takes negligible time such that regardless of how much other
|
||||||
|
// processing there is to be done, this one second sleep is necessary.
|
||||||
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobInstance {
|
||||||
|
fn execute_next(
|
||||||
|
&mut self,
|
||||||
|
api: &mut Box<dyn IMusicBrainz>,
|
||||||
|
events: &mut EventSender,
|
||||||
|
) -> Option<()> {
|
||||||
|
self.call_queue
|
||||||
|
.pop_front()
|
||||||
|
.map(|api_params| self.execute_call(api, events, api_params))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_call(
|
||||||
|
&mut self,
|
||||||
|
api: &mut Box<dyn IMusicBrainz>,
|
||||||
|
events: &mut EventSender,
|
||||||
|
api_params: ApiParams,
|
||||||
|
) {
|
||||||
|
match api_params {
|
||||||
|
ApiParams::Search(search) => match search {
|
||||||
|
SearchParams::Artist(params) => {
|
||||||
|
let result = api.search_artist(¶ms.artist);
|
||||||
|
let result = result.map(|list| MatchStateInfo::artist(params.artist, list));
|
||||||
|
self.return_result(events, result).ok();
|
||||||
|
}
|
||||||
|
SearchParams::ReleaseGroup(params) => {
|
||||||
|
let result = api.search_release_group(¶ms.arid, ¶ms.album);
|
||||||
|
let result = result.map(|list| MatchStateInfo::album(params.album, list));
|
||||||
|
self.return_result(events, result).ok();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_result(
|
||||||
|
&mut self,
|
||||||
|
events: &mut EventSender,
|
||||||
|
result: Result<MatchStateInfo, ApiError>,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
// If receiver disconnects just drop the rest.
|
||||||
|
self.return_sender.send(result).map_err(|_| ())?;
|
||||||
|
|
||||||
|
// If this send fails the event listener is dead. Don't panic as this function runs in a
|
||||||
|
// detached thread so this might be happening during normal shut down.
|
||||||
|
events.send(Event::FetchResultReady).map_err(|_| ())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
2
src/tui/lib/external/musicbrainz/mod.rs
vendored
2
src/tui/lib/external/musicbrainz/mod.rs
vendored
@ -1 +1,3 @@
|
|||||||
pub mod api;
|
pub mod api;
|
||||||
|
pub mod daemon;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::
|
|||||||
/// Trait for interacting with the MusicBrainz API.
|
/// Trait for interacting with the MusicBrainz API.
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IMusicBrainz {
|
pub trait IMusicBrainz {
|
||||||
fn search_artist(&mut self, name: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error>;
|
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error>;
|
||||||
fn search_release_group(
|
fn search_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
arid: &Mbid,
|
arid: &Mbid,
|
||||||
|
Loading…
Reference in New Issue
Block a user