From b01af5ff18b7b077e30a1ac2f8276b1235147283 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 5 Oct 2024 23:17:59 +0200 Subject: [PATCH] Implement browse in daemon --- examples/musicbrainz_api/browse.rs | 2 +- src/external/musicbrainz/api/mod.rs | 17 +++- .../lib/external/musicbrainz/daemon/mod.rs | 88 ++++++++++++++----- src/tui/lib/interface/musicbrainz/api/mod.rs | 24 +++++ .../lib/interface/musicbrainz/daemon/mod.rs | 12 ++- 5 files changed, 113 insertions(+), 30 deletions(-) diff --git a/examples/musicbrainz_api/browse.rs b/examples/musicbrainz_api/browse.rs index 20dcc5c..0bb288e 100644 --- a/examples/musicbrainz_api/browse.rs +++ b/examples/musicbrainz_api/browse.rs @@ -72,7 +72,7 @@ fn main() { println!("Release groups in this response: {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, } diff --git a/src/external/musicbrainz/api/mod.rs b/src/external/musicbrainz/api/mod.rs index 9f374da..daad181 100644 --- a/src/external/musicbrainz/api/mod.rs +++ b/src/external/musicbrainz/api/mod.rs @@ -61,7 +61,7 @@ impl MusicBrainzClient { } } -#[derive(Default)] +#[derive(Debug, Default)] pub struct PageSettings { limit: Option, offset: Option, @@ -79,7 +79,12 @@ impl PageSettings { 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); } } @@ -91,6 +96,10 @@ pub enum NextPage { } impl NextPage { + pub fn new() -> NextPage { + NextPage::Offset(0) + } + pub fn next_page_offset(offset: usize, total_count: usize, page_count: usize) -> NextPage { let next_offset = offset + page_count; if next_offset < total_count { @@ -387,14 +396,14 @@ mod tests { assert_eq!(ApiDisplay::format_page_settings(&paging), "&limit=100"); let mut paging = PageSettings::with_limit(45); - paging.with_offset(145); + paging.set_offset(145); assert_eq!( ApiDisplay::format_page_settings(&paging), "&limit=45&offset=145" ); let mut paging = PageSettings::default(); - paging.with_offset(26); + paging.set_offset(26); assert_eq!(ApiDisplay::format_page_settings(&paging), "&offset=26"); } diff --git a/src/tui/lib/external/musicbrainz/daemon/mod.rs b/src/tui/lib/external/musicbrainz/daemon/mod.rs index 02a5440..f168e54 100644 --- a/src/tui/lib/external/musicbrainz/daemon/mod.rs +++ b/src/tui/lib/external/musicbrainz/daemon/mod.rs @@ -1,12 +1,15 @@ use std::{collections::VecDeque, sync::mpsc, thread, time}; +use musichoard::external::musicbrainz::api::{NextPage, PageSettings}; + use crate::tui::{ app::EntityMatches, event::IFetchCompleteEventSender, lib::interface::musicbrainz::{ - api::{Error as ApiError, IMusicBrainz}, + api::{Error as ApiError, IMusicBrainz, Paged}, 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 { result_sender: ResultSender, requests: VecDeque, + paging: Option, } #[derive(Debug, PartialEq, Eq)] @@ -64,6 +68,7 @@ impl JobInstance { JobInstance { result_sender, requests, + paging: None, } } } @@ -223,8 +228,17 @@ impl JobInstance { event_sender: &mut dyn IFetchCompleteEventSender, ) -> Result { // self.requests can be empty if the caller submits an empty job. - if let Some(params) = self.requests.pop_front() { - self.execute(musicbrainz, event_sender, params)? + self.paging = match self.requests.front() { + Some(params) => { + let result_sender = &mut self.result_sender; + let paging = self.paging.take(); + Self::execute(musicbrainz, result_sender, event_sender, ¶ms, paging)? + } + None => None, + }; + + if self.paging.is_none() { + self.requests.pop_front(); } if self.requests.is_empty() { @@ -235,38 +249,64 @@ impl JobInstance { } fn execute( - &mut self, musicbrainz: &mut dyn IMusicBrainz, + result_sender: &mut ResultSender, event_sender: &mut dyn IFetchCompleteEventSender, - api_params: MbParams, - ) -> Result<(), JobInstanceError> { + api_params: &MbParams, + paging: Option, + ) -> Result, 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 { MbParams::Lookup(lookup) => match lookup { - LookupParams::Artist(params) => musicbrainz - .lookup_artist(¶ms.mbid) - .map(|rv| EntityMatches::artist_lookup(params.artist, rv)), - LookupParams::ReleaseGroup(params) => musicbrainz - .lookup_release_group(¶ms.mbid) - .map(|rv| EntityMatches::album_lookup(params.artist_id, params.album, rv)), - }, + LookupParams::Artist(p) => musicbrainz + .lookup_artist(&p.mbid) + .map(|rv| EntityMatches::artist_lookup(p.artist.clone(), rv)), + LookupParams::ReleaseGroup(p) => { + musicbrainz.lookup_release_group(&p.mbid).map(|rv| { + EntityMatches::album_lookup(p.artist_id.clone(), p.album.clone(), rv) + }) + } + } + .map(MbReturn::Match), MbParams::Search(search) => match search { - SearchParams::Artist(params) => musicbrainz - .search_artist(¶ms.artist) - .map(|rv| EntityMatches::artist_search(params.artist, rv)), - SearchParams::ReleaseGroup(params) => musicbrainz - .search_release_group(¶ms.artist_mbid, ¶ms.album) - .map(|rv| EntityMatches::album_search(params.artist_id, params.album, rv)), - }, + SearchParams::Artist(p) => musicbrainz + .search_artist(&p.artist) + .map(|rv| EntityMatches::artist_search(p.artist.clone(), rv)), + SearchParams::ReleaseGroup(p) => musicbrainz + .search_release_group(&p.artist_mbid, &p.album) + .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(¶ms.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( - &mut self, + result_sender: &mut ResultSender, event_sender: &mut dyn IFetchCompleteEventSender, result: Result, ) -> Result<(), JobInstanceError> { - self.result_sender + result_sender .send(result) .map_err(|_| JobInstanceError::ReturnChannelDisconnected)?; diff --git a/src/tui/lib/interface/musicbrainz/api/mod.rs b/src/tui/lib/interface/musicbrainz/api/mod.rs index cf70913..8b7c431 100644 --- a/src/tui/lib/interface/musicbrainz/api/mod.rs +++ b/src/tui/lib/interface/musicbrainz/api/mod.rs @@ -1,5 +1,7 @@ //! Module for accessing MusicBrainz metadata. +use std::mem; + #[cfg(test)] use mockall::automock; @@ -47,4 +49,26 @@ pub struct Paged { pub next: NextPage, } +// pub fn map U>(self, op: F) -> Result { +// match self { +// Ok(t) => Ok(op(t)), +// Err(e) => Err(e), +// } +// } + +impl Paged { + pub fn map_paged_result U>( + result: Result, E>, + op: F, + next: &mut NextPage, + ) -> Result { + 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; diff --git a/src/tui/lib/interface/musicbrainz/daemon/mod.rs b/src/tui/lib/interface/musicbrainz/daemon/mod.rs index bfb414f..63dd915 100644 --- a/src/tui/lib/interface/musicbrainz/daemon/mod.rs +++ b/src/tui/lib/interface/musicbrainz/daemon/mod.rs @@ -37,7 +37,6 @@ pub enum MbReturn { #[derive(Clone, Debug, PartialEq, Eq)] pub enum EntityList { - Artist(Vec), Album(Vec), } @@ -60,6 +59,7 @@ pub trait IMbJobSender { pub enum MbParams { Lookup(LookupParams), Search(SearchParams), + Browse(BrowseParams), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -99,6 +99,16 @@ pub struct SearchReleaseGroupParams { 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 { pub fn lookup_artist(artist: ArtistMeta, mbid: Mbid) -> Self { MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid }))