Integrate browse API into TUI MB daemon #230
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
src/external/musicbrainz/api/mod.rs
vendored
17
src/external/musicbrainz/api/mod.rs
vendored
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
88
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
88
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
@ -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, ¶ms, 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(¶ms.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(¶ms.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(¶ms.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(¶ms.artist_mbid, ¶ms.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(¶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(
|
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)?;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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 }))
|
||||||
|
Loading…
Reference in New Issue
Block a user