Integrate browse API into TUI MB daemon #230

Merged
wojtek merged 15 commits from 160---provide-a-keyboard-shortcut-to-pull-all-release-groups-of-an-artist into main 2024-10-06 15:32:46 +02:00
5 changed files with 113 additions and 30 deletions
Showing only changes of commit b01af5ff18 - Show all commits

View File

@ -72,7 +72,7 @@ fn main() {
println!("Release groups in this response: {count}"); println!("Release groups in this response: {count}");
match response.page.next_page_offset(count) { match response.page.next_page_offset(count) {
NextPage::Offset(next_offset) => paging.with_offset(next_offset), NextPage::Offset(next_offset) => paging.set_offset(next_offset),
NextPage::Complete => break, NextPage::Complete => break,
} }

View File

@ -61,7 +61,7 @@ impl<Http> MusicBrainzClient<Http> {
} }
} }
#[derive(Default)] #[derive(Debug, Default)]
pub struct PageSettings { pub struct PageSettings {
limit: Option<usize>, limit: Option<usize>,
offset: Option<usize>, offset: Option<usize>,
@ -79,7 +79,12 @@ impl PageSettings {
Self::with_limit(MB_MAX_PAGE_LIMIT) Self::with_limit(MB_MAX_PAGE_LIMIT)
} }
pub fn with_offset(&mut self, offset: usize) { pub fn with_offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn set_offset(&mut self, offset: usize) {
self.offset = Some(offset); self.offset = Some(offset);
} }
} }
@ -91,6 +96,10 @@ pub enum NextPage {
} }
impl NextPage { impl NextPage {
pub fn new() -> NextPage {
NextPage::Offset(0)
}
pub fn next_page_offset(offset: usize, total_count: usize, page_count: usize) -> NextPage { pub fn next_page_offset(offset: usize, total_count: usize, page_count: usize) -> NextPage {
let next_offset = offset + page_count; let next_offset = offset + page_count;
if next_offset < total_count { if next_offset < total_count {
@ -387,14 +396,14 @@ mod tests {
assert_eq!(ApiDisplay::format_page_settings(&paging), "&limit=100"); assert_eq!(ApiDisplay::format_page_settings(&paging), "&limit=100");
let mut paging = PageSettings::with_limit(45); let mut paging = PageSettings::with_limit(45);
paging.with_offset(145); paging.set_offset(145);
assert_eq!( assert_eq!(
ApiDisplay::format_page_settings(&paging), ApiDisplay::format_page_settings(&paging),
"&limit=45&offset=145" "&limit=45&offset=145"
); );
let mut paging = PageSettings::default(); let mut paging = PageSettings::default();
paging.with_offset(26); paging.set_offset(26);
assert_eq!(ApiDisplay::format_page_settings(&paging), "&offset=26"); assert_eq!(ApiDisplay::format_page_settings(&paging), "&offset=26");
} }

View File

@ -1,12 +1,15 @@
use std::{collections::VecDeque, sync::mpsc, thread, time}; use std::{collections::VecDeque, sync::mpsc, thread, time};
use musichoard::external::musicbrainz::api::{NextPage, PageSettings};
use crate::tui::{ use crate::tui::{
app::EntityMatches, app::EntityMatches,
event::IFetchCompleteEventSender, event::IFetchCompleteEventSender,
lib::interface::musicbrainz::{ lib::interface::musicbrainz::{
api::{Error as ApiError, IMusicBrainz}, api::{Error as ApiError, IMusicBrainz, Paged},
daemon::{ daemon::{
Error, IMbJobSender, LookupParams, MbParams, MbReturn, ResultSender, SearchParams, BrowseParams, EntityList, Error, IMbJobSender, LookupParams, MbParams, MbReturn,
ResultSender, SearchParams,
}, },
}, },
}; };
@ -45,6 +48,7 @@ enum JobPriority {
struct JobInstance { struct JobInstance {
result_sender: ResultSender, result_sender: ResultSender,
requests: VecDeque<MbParams>, requests: VecDeque<MbParams>,
paging: Option<PageSettings>,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@ -64,6 +68,7 @@ impl JobInstance {
JobInstance { JobInstance {
result_sender, result_sender,
requests, requests,
paging: None,
} }
} }
} }
@ -223,8 +228,17 @@ impl JobInstance {
event_sender: &mut dyn IFetchCompleteEventSender, event_sender: &mut dyn IFetchCompleteEventSender,
) -> Result<JobInstanceStatus, JobInstanceError> { ) -> Result<JobInstanceStatus, JobInstanceError> {
// self.requests 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.requests.pop_front() { self.paging = match self.requests.front() {
self.execute(musicbrainz, event_sender, params)? Some(params) => {
let result_sender = &mut self.result_sender;
let paging = self.paging.take();
Self::execute(musicbrainz, result_sender, event_sender, &params, paging)?
}
None => None,
};
if self.paging.is_none() {
self.requests.pop_front();
} }
if self.requests.is_empty() { if self.requests.is_empty() {
@ -235,38 +249,64 @@ impl JobInstance {
} }
fn execute( fn execute(
&mut self,
musicbrainz: &mut dyn IMusicBrainz, musicbrainz: &mut dyn IMusicBrainz,
result_sender: &mut ResultSender,
event_sender: &mut dyn IFetchCompleteEventSender, event_sender: &mut dyn IFetchCompleteEventSender,
api_params: MbParams, api_params: &MbParams,
) -> Result<(), JobInstanceError> { paging: Option<PageSettings>,
) -> Result<Option<PageSettings>, JobInstanceError> {
let mut paging = match paging {
Some(paging) => paging,
None => PageSettings::with_max_limit(),
};
let mut next_page = NextPage::Complete;
let result = match api_params { let result = match api_params {
MbParams::Lookup(lookup) => match lookup { MbParams::Lookup(lookup) => match lookup {
LookupParams::Artist(params) => musicbrainz LookupParams::Artist(p) => musicbrainz
.lookup_artist(&params.mbid) .lookup_artist(&p.mbid)
.map(|rv| EntityMatches::artist_lookup(params.artist, rv)), .map(|rv| EntityMatches::artist_lookup(p.artist.clone(), rv)),
LookupParams::ReleaseGroup(params) => musicbrainz LookupParams::ReleaseGroup(p) => {
.lookup_release_group(&params.mbid) musicbrainz.lookup_release_group(&p.mbid).map(|rv| {
.map(|rv| EntityMatches::album_lookup(params.artist_id, params.album, rv)), EntityMatches::album_lookup(p.artist_id.clone(), p.album.clone(), rv)
}, })
}
}
.map(MbReturn::Match),
MbParams::Search(search) => match search { MbParams::Search(search) => match search {
SearchParams::Artist(params) => musicbrainz SearchParams::Artist(p) => musicbrainz
.search_artist(&params.artist) .search_artist(&p.artist)
.map(|rv| EntityMatches::artist_search(params.artist, rv)), .map(|rv| EntityMatches::artist_search(p.artist.clone(), rv)),
SearchParams::ReleaseGroup(params) => musicbrainz SearchParams::ReleaseGroup(p) => musicbrainz
.search_release_group(&params.artist_mbid, &params.album) .search_release_group(&p.artist_mbid, &p.album)
.map(|rv| EntityMatches::album_search(params.artist_id, params.album, rv)), .map(|rv| {
}, EntityMatches::album_search(p.artist_id.clone(), p.album.clone(), rv)
}),
}
.map(MbReturn::Match),
MbParams::Browse(browse) => match browse {
BrowseParams::ReleaseGroup(params) => Paged::map_paged_result(
musicbrainz.browse_release_group(&params.artist, &mut paging),
|ents| EntityList::Album(ents.into_iter().map(|rg| rg.entity).collect()),
&mut next_page,
),
}
.map(MbReturn::Fetch),
}; };
self.return_result(event_sender, result.map(MbReturn::Match)) Self::return_result(result_sender, event_sender, result)?;
Ok(match next_page {
NextPage::Offset(offset) => Some(paging.with_offset(offset)),
NextPage::Complete => None,
})
} }
fn return_result( fn return_result(
&mut self, result_sender: &mut ResultSender,
event_sender: &mut dyn IFetchCompleteEventSender, event_sender: &mut dyn IFetchCompleteEventSender,
result: Result<MbReturn, ApiError>, result: Result<MbReturn, ApiError>,
) -> Result<(), JobInstanceError> { ) -> Result<(), JobInstanceError> {
self.result_sender result_sender
.send(result) .send(result)
.map_err(|_| JobInstanceError::ReturnChannelDisconnected)?; .map_err(|_| JobInstanceError::ReturnChannelDisconnected)?;

View File

@ -1,5 +1,7 @@
//! Module for accessing MusicBrainz metadata. //! Module for accessing MusicBrainz metadata.
use std::mem;
#[cfg(test)] #[cfg(test)]
use mockall::automock; use mockall::automock;
@ -47,4 +49,26 @@ pub struct Paged<T> {
pub next: NextPage, pub next: NextPage,
} }
// pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> {
// match self {
// Ok(t) => Ok(op(t)),
// Err(e) => Err(e),
// }
// }
impl<T> Paged<T> {
pub fn map_paged_result<E, U, F: FnOnce(T) -> U>(
result: Result<Paged<T>, E>,
op: F,
next: &mut NextPage,
) -> Result<U, E> {
match result {
Ok(paged) => {
_ = mem::replace(next, paged.next);
Ok(op(paged.item))
}
Err(err) => Err(err),
}
}
}
pub type Error = musichoard::external::musicbrainz::api::Error; pub type Error = musichoard::external::musicbrainz::api::Error;

View File

@ -37,7 +37,6 @@ pub enum MbReturn {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum EntityList { pub enum EntityList {
Artist(Vec<ArtistMeta>),
Album(Vec<AlbumMeta>), Album(Vec<AlbumMeta>),
} }
@ -60,6 +59,7 @@ pub trait IMbJobSender {
pub enum MbParams { pub enum MbParams {
Lookup(LookupParams), Lookup(LookupParams),
Search(SearchParams), Search(SearchParams),
Browse(BrowseParams),
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -99,6 +99,16 @@ pub struct SearchReleaseGroupParams {
pub album: AlbumMeta, pub album: AlbumMeta,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BrowseParams {
ReleaseGroup(BrowseReleaseGroupParams),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BrowseReleaseGroupParams {
pub artist: Mbid,
}
impl MbParams { impl MbParams {
pub fn lookup_artist(artist: ArtistMeta, mbid: Mbid) -> Self { pub fn lookup_artist(artist: ArtistMeta, mbid: Mbid) -> Self {
MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid })) MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid }))