Integrate browse API into TUI MB daemon #230
@ -3,7 +3,7 @@ use std::{thread, time};
|
|||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::musicbrainz::Mbid,
|
collection::musicbrainz::Mbid,
|
||||||
external::musicbrainz::{
|
external::musicbrainz::{
|
||||||
api::{browse::BrowseReleaseGroupRequest, MusicBrainzClient, NextPage, PageSettings},
|
api::{browse::BrowseReleaseGroupRequest, MusicBrainzClient, PageSettings},
|
||||||
http::MusicBrainzHttp,
|
http::MusicBrainzHttp,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -71,10 +71,10 @@ fn main() {
|
|||||||
|
|
||||||
println!("Release groups in this response: {count}");
|
println!("Release groups in this response: {count}");
|
||||||
|
|
||||||
match response.page.next_page_offset(count) {
|
paging = match response.page.next_page(paging, count) {
|
||||||
NextPage::Offset(next_offset) => paging.set_offset(next_offset),
|
Some(paging) => paging,
|
||||||
NextPage::Complete => break,
|
None => break,
|
||||||
}
|
};
|
||||||
|
|
||||||
thread::sleep(time::Duration::from_secs(1));
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
}
|
}
|
||||||
|
8
src/external/musicbrainz/api/browse.rs
vendored
8
src/external/musicbrainz/api/browse.rs
vendored
@ -6,7 +6,7 @@ use crate::{
|
|||||||
collection::musicbrainz::Mbid,
|
collection::musicbrainz::Mbid,
|
||||||
external::musicbrainz::{
|
external::musicbrainz::{
|
||||||
api::{
|
api::{
|
||||||
ApiDisplay, Error, MbReleaseGroupMeta, MusicBrainzClient, NextPage, PageSettings,
|
ApiDisplay, Error, MbReleaseGroupMeta, MusicBrainzClient, PageSettings,
|
||||||
SerdeMbReleaseGroupMeta, MB_BASE_URL,
|
SerdeMbReleaseGroupMeta, MB_BASE_URL,
|
||||||
},
|
},
|
||||||
IMusicBrainzHttp,
|
IMusicBrainzHttp,
|
||||||
@ -21,8 +21,8 @@ pub struct BrowseReleaseGroupPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BrowseReleaseGroupPage {
|
impl BrowseReleaseGroupPage {
|
||||||
pub fn next_page_offset(&self, page_count: usize) -> NextPage {
|
pub fn next_page(&self, settings: PageSettings, page_count: usize) -> Option<PageSettings> {
|
||||||
NextPage::next_page_offset(
|
settings.next_page(
|
||||||
self.release_group_offset,
|
self.release_group_offset,
|
||||||
self.release_group_count,
|
self.release_group_count,
|
||||||
page_count,
|
page_count,
|
||||||
@ -125,7 +125,7 @@ mod tests {
|
|||||||
release_group_count: 45,
|
release_group_count: 45,
|
||||||
};
|
};
|
||||||
|
|
||||||
next_page_test(|val| page.next_page_offset(val));
|
next_page_test(|val| page.next_page(PageSettings::default(), val));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
28
src/external/musicbrainz/api/mod.rs
vendored
28
src/external/musicbrainz/api/mod.rs
vendored
@ -87,25 +87,13 @@ impl PageSettings {
|
|||||||
pub fn set_offset(&mut self, offset: usize) {
|
pub fn set_offset(&mut self, offset: usize) {
|
||||||
self.offset = Some(offset);
|
self.offset = Some(offset);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
pub fn next_page(self, offset: usize, total_count: usize, page_count: usize) -> Option<Self> {
|
||||||
pub enum NextPage {
|
|
||||||
Offset(usize),
|
|
||||||
Complete,
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
let next_offset = offset + page_count;
|
||||||
if next_offset < total_count {
|
if next_offset < total_count {
|
||||||
NextPage::Offset(next_offset)
|
Some(self.with_offset(next_offset))
|
||||||
} else {
|
} else {
|
||||||
NextPage::Complete
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,21 +358,21 @@ mod tests {
|
|||||||
|
|
||||||
pub fn next_page_test<Fn>(mut f: Fn)
|
pub fn next_page_test<Fn>(mut f: Fn)
|
||||||
where
|
where
|
||||||
Fn: FnMut(usize) -> NextPage,
|
Fn: FnMut(usize) -> Option<PageSettings>,
|
||||||
{
|
{
|
||||||
let next = f(20);
|
let next = f(20);
|
||||||
assert_eq!(next, NextPage::Offset(25));
|
assert_eq!(next.unwrap().offset, Some(25));
|
||||||
|
|
||||||
let next = f(40);
|
let next = f(40);
|
||||||
assert_eq!(next, NextPage::Complete);
|
assert!(next.is_none());
|
||||||
|
|
||||||
let next = f(100);
|
let next = f(100);
|
||||||
assert_eq!(next, NextPage::Complete);
|
assert!(next.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn next_page() {
|
fn next_page() {
|
||||||
next_page_test(|val| NextPage::next_page_offset(5, 45, val));
|
next_page_test(|val| PageSettings::default().next_page(5, 45, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
8
src/external/musicbrainz/api/search/mod.rs
vendored
8
src/external/musicbrainz/api/search/mod.rs
vendored
@ -23,8 +23,6 @@ use crate::external::musicbrainz::{
|
|||||||
IMusicBrainzHttp,
|
IMusicBrainzHttp,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::NextPage;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all(deserialize = "kebab-case"))]
|
#[serde(rename_all(deserialize = "kebab-case"))]
|
||||||
pub struct SearchPage {
|
pub struct SearchPage {
|
||||||
@ -33,8 +31,8 @@ pub struct SearchPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SearchPage {
|
impl SearchPage {
|
||||||
pub fn next_page_offset(&self, page_count: usize) -> NextPage {
|
pub fn next_page(&self, settings: PageSettings, page_count: usize) -> Option<PageSettings> {
|
||||||
NextPage::next_page_offset(self.offset, self.count, page_count)
|
settings.next_page(self.offset, self.count, page_count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +76,6 @@ mod tests {
|
|||||||
count: 45,
|
count: 45,
|
||||||
};
|
};
|
||||||
|
|
||||||
next_page_test(|val| page.next_page_offset(val));
|
next_page_test(|val| page.next_page(PageSettings::default(), val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
src/tui/lib/external/musicbrainz/api/mod.rs
vendored
14
src/tui/lib/external/musicbrainz/api/mod.rs
vendored
@ -25,7 +25,7 @@ use musichoard::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::lib::interface::musicbrainz::api::{Entity, Error, IMusicBrainz, Paged};
|
use crate::tui::lib::interface::musicbrainz::api::{Entity, Error, IMusicBrainz};
|
||||||
|
|
||||||
// GRCOV_EXCL_START
|
// GRCOV_EXCL_START
|
||||||
pub struct MusicBrainz<Http> {
|
pub struct MusicBrainz<Http> {
|
||||||
@ -97,17 +97,17 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
|||||||
fn browse_release_group(
|
fn browse_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist: &Mbid,
|
artist: &Mbid,
|
||||||
paging: &mut PageSettings,
|
paging: &mut Option<PageSettings>,
|
||||||
) -> Result<Paged<Entity<AlbumMeta>>, Error> {
|
) -> Result<Vec<Entity<AlbumMeta>>, Error> {
|
||||||
let request = BrowseReleaseGroupRequest::artist(artist);
|
let request = BrowseReleaseGroupRequest::artist(artist);
|
||||||
|
|
||||||
let mb_response = self.client.browse_release_group(&request, paging)?;
|
let page = paging.take().unwrap_or_default();
|
||||||
|
let mb_response = self.client.browse_release_group(&request, &page)?;
|
||||||
|
|
||||||
let page_count = mb_response.release_groups.len();
|
let page_count = mb_response.release_groups.len();
|
||||||
let next = mb_response.page.next_page_offset(page_count);
|
*paging = mb_response.page.next_page(page, page_count);
|
||||||
let items = from_browse_release_group_response(mb_response);
|
|
||||||
|
|
||||||
Ok(Paged { items, next })
|
Ok(from_browse_release_group_response(mb_response))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
44
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
44
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
@ -1,12 +1,12 @@
|
|||||||
use std::{collections::VecDeque, sync::mpsc, thread, time};
|
use std::{collections::VecDeque, sync::mpsc, thread, time};
|
||||||
|
|
||||||
use musichoard::external::musicbrainz::api::{NextPage, PageSettings};
|
use musichoard::external::musicbrainz::api::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, Paged},
|
api::{Error as ApiError, IMusicBrainz},
|
||||||
daemon::{
|
daemon::{
|
||||||
BrowseParams, EntityList, Error, IMbJobSender, LookupParams, MbParams, MbReturn,
|
BrowseParams, EntityList, Error, IMbJobSender, LookupParams, MbParams, MbReturn,
|
||||||
ResultSender, SearchParams,
|
ResultSender, SearchParams,
|
||||||
@ -228,13 +228,10 @@ 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.
|
||||||
self.paging = match self.requests.front() {
|
if let Some(params) = self.requests.front() {
|
||||||
Some(params) => {
|
let result_sender = &mut self.result_sender;
|
||||||
let result_sender = &mut self.result_sender;
|
let paging = &mut self.paging;
|
||||||
let paging = self.paging.take();
|
Self::execute(musicbrainz, result_sender, event_sender, ¶ms, paging)?;
|
||||||
Self::execute(musicbrainz, result_sender, event_sender, ¶ms, paging)?
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.paging.is_none() {
|
if self.paging.is_none() {
|
||||||
@ -253,13 +250,11 @@ impl JobInstance {
|
|||||||
result_sender: &mut ResultSender,
|
result_sender: &mut ResultSender,
|
||||||
event_sender: &mut dyn IFetchCompleteEventSender,
|
event_sender: &mut dyn IFetchCompleteEventSender,
|
||||||
api_params: &MbParams,
|
api_params: &MbParams,
|
||||||
paging: Option<PageSettings>,
|
paging: &mut Option<PageSettings>,
|
||||||
) -> Result<Option<PageSettings>, JobInstanceError> {
|
) -> Result<(), JobInstanceError> {
|
||||||
let mut paging = match paging {
|
if paging.is_none() {
|
||||||
Some(paging) => paging,
|
*paging = Some(PageSettings::with_max_limit());
|
||||||
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 {
|
||||||
@ -285,22 +280,13 @@ impl JobInstance {
|
|||||||
}
|
}
|
||||||
.map(MbReturn::Match),
|
.map(MbReturn::Match),
|
||||||
MbParams::Browse(browse) => match browse {
|
MbParams::Browse(browse) => match browse {
|
||||||
BrowseParams::ReleaseGroup(params) => {
|
BrowseParams::ReleaseGroup(params) => musicbrainz
|
||||||
let paged = musicbrainz.browse_release_group(¶ms.artist, &mut paging);
|
.browse_release_group(¶ms.artist, paging)
|
||||||
let result = Paged::map_paged_result(paged, |rg| rg.entity, &mut next_page);
|
.map(|rv| EntityList::Album(rv.into_iter().map(|rg| rg.entity).collect())),
|
||||||
result.map(EntityList::Album)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.map(MbReturn::Fetch),
|
.map(MbReturn::Fetch),
|
||||||
};
|
};
|
||||||
Self::return_result(result_sender, event_sender, result)?;
|
Self::return_result(result_sender, event_sender, result)
|
||||||
|
|
||||||
let next_page_settings = match next_page {
|
|
||||||
NextPage::Offset(offset) => Some(paging.with_offset(offset)),
|
|
||||||
NextPage::Complete => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(next_page_settings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn return_result(
|
fn return_result(
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
//! Module for accessing MusicBrainz metadata.
|
//! Module for accessing MusicBrainz metadata.
|
||||||
|
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
|
|
||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid},
|
collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid},
|
||||||
external::musicbrainz::api::{NextPage, PageSettings},
|
external::musicbrainz::api::PageSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IMusicBrainz {
|
pub trait IMusicBrainz {
|
||||||
fn lookup_artist(&mut self, mbid: &Mbid) -> Result<Entity<ArtistMeta>, Error>;
|
fn lookup_artist(&mut self, mbid: &Mbid) -> Result<Entity<ArtistMeta>, Error>;
|
||||||
fn lookup_release_group(&mut self, mbid: &Mbid) -> Result<Entity<AlbumMeta>, Error>;
|
fn lookup_release_group(&mut self, mbid: &Mbid) -> Result<Entity<AlbumMeta>, Error>;
|
||||||
// TODO: Also make it out Paged
|
|
||||||
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Entity<ArtistMeta>>, Error>;
|
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Entity<ArtistMeta>>, Error>;
|
||||||
fn search_release_group(
|
fn search_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -24,8 +21,8 @@ pub trait IMusicBrainz {
|
|||||||
fn browse_release_group(
|
fn browse_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
artist: &Mbid,
|
artist: &Mbid,
|
||||||
paging: &mut PageSettings,
|
paging: &mut Option<PageSettings>,
|
||||||
) -> Result<Paged<Entity<AlbumMeta>>, Error>;
|
) -> Result<Vec<Entity<AlbumMeta>>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -45,24 +42,4 @@ impl<T> Entity<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Paged<T> {
|
|
||||||
pub items: Vec<T>,
|
|
||||||
pub next: NextPage,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Paged<T> {
|
|
||||||
pub fn map_paged_result<E, U, F: FnMut(T) -> U>(
|
|
||||||
result: Result<Paged<T>, E>,
|
|
||||||
op: F,
|
|
||||||
next: &mut NextPage,
|
|
||||||
) -> Result<Vec<U>, E> {
|
|
||||||
match result {
|
|
||||||
Ok(paged) => {
|
|
||||||
_ = mem::replace(next, paged.next);
|
|
||||||
Ok(paged.items.into_iter().map(op).collect())
|
|
||||||
}
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub type Error = musichoard::external::musicbrainz::api::Error;
|
pub type Error = musichoard::external::musicbrainz::api::Error;
|
||||||
|
Loading…
Reference in New Issue
Block a user