Daemon draft
This commit is contained in:
parent
f4472ee95d
commit
a980086fe0
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 daemon;
|
||||
|
||||
|
@ -8,7 +8,7 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::
|
||||
/// Trait for interacting with the MusicBrainz API.
|
||||
#[cfg_attr(test, automock)]
|
||||
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(
|
||||
&mut self,
|
||||
arid: &Mbid,
|
||||
|
Loading…
Reference in New Issue
Block a user